Инструмент для создания вопросов CodeReview

Чтобы загрузить инструмент и инструкции по использованию, см. продолжение этого вопроса


Описание

Я понял, что есть целая куча кода, который я хочу просмотреть, но копирование файла по файлу, выбор кода и нажатие Ctrl + K - медленный процесс, и я хочу помнить, какие другие части включать в мой вопрос, поэтому я решил сделать для него инструмент. Я думал, что это может быть полезно, тем более что большинство моих вопросов структурированы очень схожими способами уже ...

Этот код предназначен для ввода нескольких файлов и вывода заглушки CR, готовых к заполнению подробностями. Код автоматически форматирует входные файлы в соответствии с форматированием StackExchange с четырьмя пробелами, добавленными перед каждой строкой (не более Ctrl + K !!)

Очень вероятно, что вы увидите больше вопросов от меня, структурированных так же, как этот вопрос. И если вы хотите использовать его самостоятельно, не стесняйтесь это делать.

Связанные ранее существующие инструменты:

Загрузка кода

Для вашего удобства этот код можно найти на GitHub (большое спасибо @amon, который преподавал мне гораздо больше нравится как использовать git )

Резюме класса (10079 байт в 342 строках в 4 файлах)

  • CountingStream: OutputStream, который отслеживает количество записанных байтов в нем
  • ReviewPrepareFrame: JFrame позволяет пользователям выбирать файлы, которые должны быть просмотрены.
  • ReviewPreparer: Самый важный класс, заботится о большей части работы. Ожидает список файлов в конструкторе и OutputStream при вызове.
  • TextAreaOutputStream: OutputStream для вывода в JTextArea.

Код

CountingStream: (приблизительно 679 байт в 27 строках)

/**
 * An output stream that keeps track of how many bytes that has been written to it.
 */
public class CountingStream extends FilterOutputStream {
    private final AtomicInteger bytesWritten;

    public CountingStream(OutputStream out) {
        super(out);
        this.bytesWritten = new AtomicInteger();
    }

    @Override
    public void write(int b) throws IOException {
        bytesWritten.incrementAndGet();
        super.write(b);
    }
    public int getBytesWritten() {
        return bytesWritten.get();
    }
}

ReviewPrepareFrame: (приблизительно 3178 байт в 109 строках)

public class ReviewPrepareFrame extends JFrame {

    private static final long   serialVersionUID    = 2050188992596669693L;
    private JPanel  contentPane;
    private final JTextArea result = new JTextArea();

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    new ReviewPrepareFrame().setVisible(true);
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public ReviewPrepareFrame() {
        setTitle("Prepare code for Code Review");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        setContentPane(contentPane);

        JPanel panel = new JPanel();
        contentPane.add(panel, BorderLayout.NORTH);

        final DefaultListModel<File> model = new DefaultListModel<>();
        final JList<File> list = new JList<File>();
        panel.add(list);
        list.setModel(model);

        JButton btnAddFiles = new JButton("Add files");
        btnAddFiles.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JFileChooser dialog = new JFileChooser();
                dialog.setMultiSelectionEnabled(true);
                if (dialog.showOpenDialog(ReviewPrepareFrame.this) == JFileChooser.APPROVE_OPTION) {
                    for (File file : dialog.getSelectedFiles()) {
                        model.addElement(file);
                    }
                }
            }
        });
        panel.add(btnAddFiles);

        JButton btnRemoveFiles = new JButton("Remove files");
        btnRemoveFiles.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                for (File file : new ArrayList<>(list.getSelectedValuesList())) {
                    model.removeElement(file);
                }
            }
        });
        panel.add(btnRemoveFiles);

        JButton performButton = new JButton("Create Question stub with code included");
        performButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                result.setText("");
                ReviewPreparer preparer = new ReviewPreparer(filesToList(model));
                TextAreaOutputStream outputStream = new TextAreaOutputStream(result);
                preparer.createFormattedQuestion(outputStream);
            }
        });
        contentPane.add(performButton, BorderLayout.SOUTH);
        contentPane.add(result, BorderLayout.CENTER);
    }

    public List<File> filesToList(DefaultListModel<File> model) {
        List<File> files = new ArrayList<>();
        for (int i = 0; i < model.getSize(); i++) {
            files.add(model.get(i));
        }
        return files;
    }


}

ReviewPreparer: (приблизительно 5416 байт в 165 строках)

public class ReviewPreparer {
    private final List<File> files;

    public ReviewPreparer(List<File> files) {
        this.files = new ArrayList<>();

        for (File file : files) {
            if (file.getName().lastIndexOf('.') == -1)
                continue;

            if (file.length() < 10)
                continue;

            this.files.add(file);
        }
    }

    public int createFormattedQuestion(OutputStream out) {
        CountingStream counter = new CountingStream(out);
        PrintStream ps = new PrintStream(counter);
        outputHeader(ps);
        outputFileNames(ps);
        outputFileContents(ps);
        outputDependencies(ps);
        outputFooter(ps);
        ps.print("Question Length: ");
        ps.println(counter.getBytesWritten());
        return counter.getBytesWritten();
    }

    private void outputFooter(PrintStream ps) {
        ps.println("#Usage / Test");
        ps.println();
        ps.println();
        ps.println("#Questions");
        ps.println();
        ps.println();
        ps.println();
    }

    private void outputDependencies(PrintStream ps) {
        List<String> dependencies = new ArrayList<>();
        for (File file : files) {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
                String line;
                while ((line = in.readLine()) != null) {
                    if (!line.startsWith("import ")) continue;
                    if (line.startsWith("import java.")) continue;
                    if (line.startsWith("import javax.")) continue;
                    String importStatement = line.substring("import ".length());
                    importStatement = importStatement.substring(0, importStatement.length() - 1); // cut the semicolon
                    dependencies.add(importStatement);
                }
            }
            catch (IOException e) {
                ps.println("Could not read " + file.getAbsolutePath());
                ps.println();
                // this will be handled by another function
            }

        }
        if (!dependencies.isEmpty()) {
            ps.println("#Dependencies");
            ps.println();
            for (String str : dependencies)
                ps.println("- " + str + ": ");
        }
        ps.println();
    }

    private int countLines(File file) throws IOException {
        return Files.readAllLines(file.toPath(), StandardCharsets.UTF_8).size();
    }

    private void outputFileContents(PrintStream ps) {
        ps.println();
        for (File file : files) {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
                ps.printf("**%s:** (approximately %d bytes in %d lines)", className(file), file.length(), countLines(file));
                ps.println();
                ps.println();
                String line;
                int importStatementsFinished = 0;
                while ((line = in.readLine()) != null) {
                    // skip package and import declarations
                    if (line.startsWith("package ")) continue;
                    if (line.startsWith("import ")) {
                        importStatementsFinished = 1;
                        continue;
                    }
                    if (importStatementsFinished >= 0) importStatementsFinished = -1;
                    if (importStatementsFinished == -1 && line.trim().isEmpty()) continue;
                    importStatementsFinished = -2;
                    line = line.replaceAll("    ", "\t"); // replace four spaces with tabs, since that takes less space
                    ps.print("  "); // format as code for StackExchange, this needs to be spaces.
                    ps.println(line);
                }
            }
            catch (IOException e) {
                ps.print("> ");
                e.printStackTrace(ps);
            }
            ps.println();
        }
    }

    private void outputFileNames(PrintStream ps) {
        int totalLength = 0;
        int totalLines = 0;
        for (File file : files) {
            totalLength += file.length();
            try {
                totalLines += countLines(file);
            }
            catch (IOException e) {
                ps.println("Unable to determine line count for " + file.getAbsolutePath());
            }
        }
        ps.printf("###Class Summary (%d bytes in %d lines in %d files)", totalLength, totalLines, files.size());
        ps.println();
        ps.println();
        for (File file : files) {
            ps.println("- " + className(file) + ": ");
        }
    }

    private String className(File file) {
        String str = file.getName();
        return str.substring(0, str.lastIndexOf('.'));
    }

    private void outputHeader(PrintStream ps) {
        ps.println("#Description");
        ps.println();
        ps.println("- Add some [description for what the code does](http://meta.codereview.stackexchange.com/questions/1226/code-should-include-a-description-of-what-the-code-does)");
        ps.println("- Is this a follow-up question? Answer [What has changed, Which question was the previous one, and why you are looking for another review](http://meta.codereview.stackexchange.com/questions/1065/how-to-post-a-follow-up-question)");
        ps.println();
        ps.println("#Code download");
        ps.println();
        ps.println("For convenience, this code can be downloaded from [somewhere](http://github.com repository perhaps?)");
        ps.println();
    }

    public static void main(String[] args) throws IOException {
//      List<File> fileList = Arrays.asList(new File("C:/_zomisnet/_reviewtest").listFiles());
        List<File> fileList = Arrays.asList(new File("./src/net/zomis/reviewprep").listFiles());
        new ReviewPreparer(fileList).createFormattedQuestion(System.out);
    }

}

TextAreaOutputStream: (приблизительно 806 байт в 41 строке)

public class TextAreaOutputStream extends OutputStream {

    private final JTextArea textArea;
    private final StringBuilder sb = new StringBuilder();

    public TextAreaOutputStream(final JTextArea textArea) {
        this.textArea = textArea;
    }

    @Override
    public void flush() {
    }

    @Override
    public void close() {
    }

    @Override
    public void write(int b) throws IOException {
        if (b == '\n') {
            final String text = sb.toString() + "\n";
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    textArea.append(text);
                }
            });
            sb.setLength(0);
            return;
        }

        sb.append((char) b);
    }
}

Использование /Тест

Вход: Запустите JFrame, выберите файлы, которые необходимо просмотреть, нажмите кнопку.

Выход: заглушка на этот вопрос, который я сейчас пишу:)

Вопросы

Ищем общие комментарии к моему коду. Код является Open-Source, если вы хотите продолжить работу, которую я начал. В настоящее время я занимаюсь только Java, потому что это то, что я больше всего использую в коде, но я считаю, что более поздняя поддержка других языков может быть добавлена ​​позже (я не спрашиваю , как это сделать, или для вас сделайте это, я просто говорю, что с некоторыми изменениями, я считаю, что это возможно).

40 голосов | спросил Simon Forsberg 8 FebruaryEurope/MoscowbSat, 08 Feb 2014 05:42:26 +0400000000amSat, 08 Feb 2014 05:42:26 +040014 2014, 05:42:26

2 ответа


16

Я бы посоветовал вам производить более явный вывод, особенно с именами файлов. Если бы я хотел обратить вспять процесс и очистить код в файлы на моей машине, используя скрипт Python, например следующий:

 import json
from lxml import html
import re
import requests

FILENAME_HINT_XPATH = "../preceding-sibling::p[1]/strong/text()"

def code_for_post(site, post):
    r = requests.get('https://api.stackexchange.com/2.1/posts/{1}?site={0}&filter=withbody'.format(site, post))
    j = json.loads(r.text)
    body = j['items'][0]['body']
    tree = html.fromstring(body)

    code_elements = tree.xpath("//pre/code[%s]" % (FILENAME_HINT_XPATH))
    return dict((c.xpath(FILENAME_HINT_XPATH)[0], c.findtext(".")) for c in code_elements)

def write_files(code):
    extension = '.java'     # <-- Yuck, due to @Simon.
    for filename_hint, content in code.iteritems():
        filename = re.sub(r'[^A-Za-z0-9]', '', filename_hint) + extension
        with open(filename, 'w') as f:
            print >>f, content

write_files(code_for_post('codereview', 41198))

â € |, тогда я должен был бы сделать предположения о расширении имени файла.


Метод вызова может быть улучшен. Вместо жесткого кодирования конкретного каталога для поиска исходных файлов я бы предложил ...

  • Если файлы явно переданы программе в качестве аргументов командной строки, используйте эти файлы.
  • Если указан каталог, используйте все файлы, содержащиеся в нем, за исключением файлов со значительным содержанием, отличным от ASCII.
  • Если аргументы командной строки не используются, тогда работайте с текущим каталогом.

Было бы неплохо сказать java ReviewPreparer *.java | pbcopy .

ответил 200_success 8 FebruaryEurope/MoscowbSat, 08 Feb 2014 13:16:34 +0400000000pmSat, 08 Feb 2014 13:16:34 +040014 2014, 13:16:34
12

Гораздо важнее передать код добросовестно, чем сохранить несколько байтов вывода, заменив пробелы на вкладки:

line = line.replaceAll("    ", "\t"); // replace four spaces with tabs, since that takes less space

Фактически, поскольку вы использовали свою программу для создания этого вопроса, я подозреваю, что ваш инструмент отвечает за введение ошибки в следующую строку:

ps.print("  "); // format as code for StackExchange, this needs to be spaces.

Markdown требует четырех ведущих пробелов для создания кода, а не двух.

ответил 200_success 8 FebruaryEurope/MoscowbSat, 08 Feb 2014 13:53:51 +0400000000pmSat, 08 Feb 2014 13:53:51 +040014 2014, 13:53:51

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132