要下载该工具和使用说明,请参见此问题的后续操作。进行了审查,但是逐个文件地复制文件,选择代码并按Ctrl + K是一个缓慢的过程,我想记住我的问题中还包括哪些其他部分,因此我决定为此提供工具。我认为这很有用,尤其是因为我的大多数问题已经以非常相似的方式进行结构化了。充满细节。代码自动格式化输入文件以匹配StackExchange格式,并在每行前面添加四个空格(不再按Ctrl + K !!)

您很可能会看到我的更多问题结构与该问题相同。如果您想自己使用它,请随时使用。

相关的先前存在的工具:


自动校正CR和SO代码
用于在代码审阅中选择代码片段的书签

代码下载

为了方便起见,可以在GitHub上找到此代码(非常感谢@amon教会了我很多关于如何使用git的知识

类摘要(10079字节,在342行中,共4个文件)
写入的字节数
ReviewPrepareFrame:JFrame,用于让用户选择应进行审查的文件
ReviewPreparer:最重要的类,负责大部分工作。
TextAreaOutputStream:OutputStream用于输出到JTextArea

Code

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:(在109行中约为3178字节)

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:(165行中约5416字节)

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:(41行中约806字节)
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,选择应检查的文件,单击按钮。

输出:我当前正在编写此问题的存根:)

问题

寻找关于我的代码的一般注释。如果您要继续我已经开始的工作,该代码是开源的。我目前只专门处理Java,因为这是我最常编写的代码,但是我相信以后可以添加对其他语言的更好支持(我不是在问怎么做,或者不是在问你这样做,我只是说经过一些修改,我相信是有可能的。)

评论

现在,此问题进行了跟进:codereview.stackexchange.com/questions/41225/…

#1 楼

我鼓励您产生更明确的输出,尤其是文件名。如果我想反向进行此过程并将代码刮到机器上的文件中,请使用如下所示的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很高兴。

评论


\ $ \ begingroup \ $
你的意思是我应该打印出它是.java文件?
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年2月8日在9:20



\ $ \ begingroup \ $
是的,那会让我删除#Yuck行。
\ $ \ endgroup \ $
– 200_success
2014年2月8日在9:25

#2 楼

忠实地传达代码比通过用制表符替换空格来节省一些输出字节要重要得多:

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

这个问题本身,我怀疑您的工具负责在接下来的一行中引入该错误:两个。

评论


\ $ \ begingroup \ $
哎呀!确实正确。我也在考虑是否用空格代替制表符会更好呢? (唯一的问题是我想要查看的代码往往很大:))
\ $ \ endgroup \ $
–西蒙·福斯伯格
14年2月8日在9:58

\ $ \ begingroup \ $
我认为,忠实地传达代码的必要性超出了内容的处理范围。
\ $ \ endgroup \ $
– 200_success
2014年2月8日在10:13

\ $ \ begingroup \ $
那里很重要。最好只在每行的开头添加四个空格,然后将其留在那。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年2月8日在10:19

\ $ \ begingroup \ $
@SimonAndréForsberg为错误发现+1到200_success,为“不要乱扔...”而另外+1。 (不过上限为+1)。另外,除非是Python,否则应该只在行首/末尾加上空格,在这种情况下,唯一安全的修改是在前面添加4个空格。
\ $ \ endgroup \ $
–rolfl
2014年2月8日在12:47