Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions cmd/bibtex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cmd

import (
"fmt"
"sort"
"strconv"
"strings"

"github.com/nickng/bibtex"
)

// formatBibTeX aligns fields while preserving their contents verbatim.
func formatBibTeX(entry *bibtex.BibEntry) string {
keys := make([]string, 0, len(entry.Fields))
width := 0
for key := range entry.Fields {
keys = append(keys, key)
if len(key) > width {
width = len(key)
}
}
priority := map[string]int{"title": -3, "author": -2, "url": -1}
sort.Slice(keys, func(i, j int) bool {
pi, pj := priority[keys[i]], priority[keys[j]]
return pi < pj || (pi == pj && keys[i] < keys[j])
})

var output strings.Builder
fmt.Fprintf(&output, "@%s{%s,\n", entry.Type, entry.CiteName)
for _, key := range keys {
value := entry.Fields[key].String()
if _, err := strconv.Atoi(value); err != nil {
// BibTeX delimiters preserve LaTeX commands and newlines; Go's %q escapes them.
if strings.ContainsAny(value, "\"{}") {
value = "{" + value + "}"
} else {
value = "\"" + value + "\""
}
}
fmt.Fprintf(&output, " %-*s = %s,\n", width, key, value)
}
output.WriteString("}\n")
return output.String()
}
71 changes: 71 additions & 0 deletions cmd/bibtex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cmd

import (
"strings"
"testing"

"github.com/nickng/bibtex"
)

func TestFormatBibTeXPreservesFieldContents(t *testing.T) {
for _, value := range []string{
"A simple title",
"First line\n\tSecond line",
`Ducas, L\'eo`,
`10.1007/example\_23`,
`[2022] \copyright 2022`,
`Literal \n and \t commands`,
`A LaTeX \\ line break`,
`The "quoted" title`,
`A {nested {title}} with \alpha`,
"Léo and Göttingen",
" leading and trailing spaces ",
"",
"00123",
} {
t.Run(value, func(t *testing.T) {
entry := bibtex.NewBibEntry("article", "test")
entry.AddField("title", bibtex.NewBibConst(value))
output := formatBibTeX(entry)
parsed, err := bibtex.Parse(strings.NewReader(output))
if err != nil {
t.Fatalf("parse output: %v\n%s", err, output)
}
if len(parsed.Entries) != 1 {
t.Fatalf("output entry count = %d, want 1", len(parsed.Entries))
}
title, ok := parsed.Entries[0].Fields["title"]
if !ok {
t.Fatal("output is missing title")
}
if got := title.String(); got != value {
t.Errorf("title = %q, want %q", got, value)
}
})
}
}

func TestFormatBibTeXFieldOrderAndAlignment(t *testing.T) {
entry := bibtex.NewBibEntry("article", "test")
for key, value := range map[string]string{
"year": "2022",
"author": `Ducas, L\'eo`,
"booktitle": "{EUROCRYPT} 2022",
"title": "First line\n second line",
"url": "https://example.com",
} {
entry.AddField(key, bibtex.NewBibConst(value))
}
const want = `@article{test,
title = "First line
second line",
author = "Ducas, L\'eo",
url = "https://example.com",
booktitle = {{EUROCRYPT} 2022},
year = 2022,
}
`
if got := formatBibTeX(entry); got != want {
t.Errorf("BibTeX output:\n%s\nwant:\n%s", got, want)
}
}
2 changes: 1 addition & 1 deletion cmd/hexhex.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func runZB(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("create BibTeX entry: %w", err)
}
if _, err := fmt.Fprint(cmd.OutOrStdout(), entry.PrettyString()); err != nil {
if _, err := fmt.Fprint(cmd.OutOrStdout(), formatBibTeX(entry)); err != nil {
return fmt.Errorf("write BibTeX entry: %w", err)
}
return nil
Expand Down
2 changes: 1 addition & 1 deletion cmd/mr.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func runMR(cmd *cobra.Command, args []string) error {
if entry == nil || entry.BibTeX == nil {
return fmt.Errorf("MR result %d has no BibTeX entry", selected+1)
}
if _, err := fmt.Fprint(cmd.OutOrStdout(), entry.BibTeX.PrettyString()); err != nil {
if _, err := fmt.Fprint(cmd.OutOrStdout(), formatBibTeX(entry.BibTeX)); err != nil {
return fmt.Errorf("write BibTeX entry: %w", err)
}
return nil
Expand Down
41 changes: 41 additions & 0 deletions cmd/mr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,47 @@ func TestRunMRWritesOneBibTeXEntry(t *testing.T) {
assertMRBibTeX(t, output.String(), "MR1")
}

func TestRunMRPreservesBibTeXFields(t *testing.T) {
const source = `@incollection{MR4485627,
AUTHOR = {Ducas, L\'eo and van Woerden, Wessel},
BOOKTITLE = {Advances in cryptology---{EUROCRYPT} 2022. {P}art {III}},
DOI = {10.1007/978-3-031-07082-2\_23},
TITLE = {On the lattice isomorphism problem, quadratic forms,
remarkable lattices, and cryptography},
YEAR = {[2022] \copyright 2022},
}`
parsed, err := bibtex.Parse(strings.NewReader(source))
if err != nil {
t.Fatalf("parse fixture: %v", err)
}
bib := parsed.Entries[0]
useMRQuery(t, func(string, string, string) ([]*mr.Entry, error) {
return []*mr.Entry{{BibTeX: bib}}, nil
})

command := &cobra.Command{}
var output bytes.Buffer
command.SetOut(&output)
if err := runMR(command, []string{"ducas", "On the lattice isomorphism problem", "-"}); err != nil {
t.Fatalf("runMR() error = %v", err)
}
printed, err := bibtex.Parse(strings.NewReader(output.String()))
if err != nil {
t.Fatalf("parse output: %v\n%s", err, output.String())
}
if len(printed.Entries) != 1 {
t.Fatalf("output entry count = %d, want 1", len(printed.Entries))
}
for key, value := range bib.Fields {
got, ok := printed.Entries[0].Fields[key]
if !ok {
t.Errorf("output is missing field %s", key)
} else if got.String() != value.String() {
t.Errorf("field %s = %q, want %q", key, got.String(), value.String())
}
}
}

func TestRunMRChoosesAndHandlesCancellation(t *testing.T) {
entries := []*mr.Entry{
mrTestEntry("MR1", "First result", "2025"),
Expand Down
2 changes: 1 addition & 1 deletion cmd/phd.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func runPhD(cmd *cobra.Command, args []string) error {
if bib == nil {
return fmt.Errorf("selected PhD result has no BibTeX entry")
}
if _, err := fmt.Fprint(cmd.OutOrStdout(), bib.PrettyString()); err != nil {
if _, err := fmt.Fprint(cmd.OutOrStdout(), formatBibTeX(bib)); err != nil {
return fmt.Errorf("write BibTeX entry: %w", err)
}
return nil
Expand Down
Loading