119 lines
3.3 KiB
Go
119 lines
3.3 KiB
Go
|
// Package paperformatter provides functionality to convert JSON files containing paper reviews
|
||
|
// into formatted Markdown documents. It organizes papers into accepted and rejected categories,
|
||
|
// with each entry containing a title, arXiv link, abstract, decision, and explanation.
|
||
|
//
|
||
|
// The package provides robust error handling with custom error types for validation,
|
||
|
// file operations, and JSON parsing issues. It supports case-insensitive decision values
|
||
|
// and properly formats multiline abstracts.
|
||
|
//
|
||
|
// Basic usage:
|
||
|
//
|
||
|
// err := paperformatter.FormatPapers("input.json", "output.md")
|
||
|
// if err != nil {
|
||
|
// log.Fatal(err)
|
||
|
// }
|
||
|
//
|
||
|
// For detailed examples, see the examples in the documentation.
|
||
|
package paperformatter
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// Paper represents a research paper with its basic metadata.
|
||
|
type Paper struct {
|
||
|
Title string `json:"title"`
|
||
|
Abstract string `json:"abstract"`
|
||
|
ArxivID string `json:"arxiv_id"`
|
||
|
}
|
||
|
|
||
|
// Entry represents a paper review entry containing the paper details,
|
||
|
// the review decision, and an explanation for the decision.
|
||
|
type Entry struct {
|
||
|
Paper Paper `json:"paper"`
|
||
|
Decision string `json:"decision"`
|
||
|
Explanation string `json:"explanation"`
|
||
|
}
|
||
|
|
||
|
// FormatPapers reads paper reviews from a JSON file and generates a formatted Markdown document.
|
||
|
// The output document organizes papers into "Accepted Papers" and "Rejected Papers" sections.
|
||
|
//
|
||
|
// The input JSON file should contain an array of Entry objects. Each entry must have:
|
||
|
// - A paper with a title, abstract, and arXiv ID
|
||
|
// - A decision ("accept" or "reject", case-insensitive)
|
||
|
// - An explanation for the decision
|
||
|
//
|
||
|
// The function returns an error if:
|
||
|
// - The input file cannot be read (FileError)
|
||
|
// - The JSON is invalid (JSONError)
|
||
|
// - Any entry fails validation (ValidationError)
|
||
|
// - The output file cannot be written (FileError)
|
||
|
func FormatPapers(inputFile, outputFile string) error {
|
||
|
data, err := os.ReadFile(inputFile)
|
||
|
if err != nil {
|
||
|
return &FileError{
|
||
|
Path: inputFile,
|
||
|
Op: "read",
|
||
|
Wrapped: err,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var entries []Entry
|
||
|
if err := json.Unmarshal(data, &entries); err != nil {
|
||
|
return &JSONError{Wrapped: err}
|
||
|
}
|
||
|
|
||
|
// Validate all entries before processing
|
||
|
for i, entry := range entries {
|
||
|
if err := validateEntry(entry); err != nil {
|
||
|
return fmt.Errorf("entry %d: %w", i+1, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var accepted, rejected []Entry
|
||
|
for _, entry := range entries {
|
||
|
if strings.ToUpper(entry.Decision) == "ACCEPT" {
|
||
|
accepted = append(accepted, entry)
|
||
|
} else {
|
||
|
rejected = append(rejected, entry)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
output := "# Accepted Papers\n\n"
|
||
|
for _, entry := range accepted {
|
||
|
output += formatEntry(entry)
|
||
|
}
|
||
|
|
||
|
output += "# Rejected Papers\n\n"
|
||
|
for _, entry := range rejected {
|
||
|
output += formatEntry(entry)
|
||
|
}
|
||
|
|
||
|
if err := os.WriteFile(outputFile, []byte(output), 0644); err != nil {
|
||
|
return &FileError{
|
||
|
Path: outputFile,
|
||
|
Op: "write",
|
||
|
Wrapped: err,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func formatEntry(entry Entry) string {
|
||
|
return fmt.Sprintf("## %s\n\n"+
|
||
|
"[arXiv:%s](https://arxiv.org/abs/%s)\n\n"+
|
||
|
"> %s\n\n"+
|
||
|
"**Decision:** %s\n\n"+
|
||
|
"**Explanation:** %s\n\n",
|
||
|
entry.Paper.Title,
|
||
|
entry.Paper.ArxivID,
|
||
|
entry.Paper.ArxivID,
|
||
|
strings.ReplaceAll(entry.Paper.Abstract, "\n", "\n> "),
|
||
|
entry.Decision,
|
||
|
entry.Explanation)
|
||
|
}
|