From 4be5f7bfb23e363f54aff494e8c6bd77b5937839 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Sat, 9 Mar 2024 16:28:23 -0600 Subject: [PATCH 1/7] Add new build tool. bibliogra.py requires Python 2, which is a pain to deal with. This commit adds a Go tool that compiles CensorBib from BibTeX to HTML. The tool does the bare minimum and is quite strict in the BibTeX format it expects. --- src/decode.go | 55 ++ src/decode_test.go | 81 +++ src/footer.go | 11 + src/go.mod | 5 + src/go.sum | 2 + src/header.go | 231 +++++++ src/html.go | 130 ++++ src/main.go | 113 +++ src/main_test.go | 43 ++ .../github.com/nickng/bibtex/.gitignore | 24 + src/vendor/github.com/nickng/bibtex/LICENSE | 201 ++++++ src/vendor/github.com/nickng/bibtex/README.md | 17 + src/vendor/github.com/nickng/bibtex/bibtex.go | 355 ++++++++++ src/vendor/github.com/nickng/bibtex/bibtex.y | 89 +++ .../github.com/nickng/bibtex/bibtex.y.go | 645 ++++++++++++++++++ src/vendor/github.com/nickng/bibtex/docs.go | 23 + src/vendor/github.com/nickng/bibtex/error.go | 23 + src/vendor/github.com/nickng/bibtex/lexer.go | 38 ++ .../github.com/nickng/bibtex/scanner.go | 237 +++++++ src/vendor/github.com/nickng/bibtex/token.go | 55 ++ src/vendor/modules.txt | 3 + 21 files changed, 2381 insertions(+) create mode 100644 src/decode.go create mode 100644 src/decode_test.go create mode 100644 src/footer.go create mode 100644 src/go.mod create mode 100644 src/go.sum create mode 100644 src/header.go create mode 100644 src/html.go create mode 100644 src/main.go create mode 100644 src/main_test.go create mode 100644 src/vendor/github.com/nickng/bibtex/.gitignore create mode 100644 src/vendor/github.com/nickng/bibtex/LICENSE create mode 100644 src/vendor/github.com/nickng/bibtex/README.md create mode 100644 src/vendor/github.com/nickng/bibtex/bibtex.go create mode 100644 src/vendor/github.com/nickng/bibtex/bibtex.y create mode 100644 src/vendor/github.com/nickng/bibtex/bibtex.y.go create mode 100644 src/vendor/github.com/nickng/bibtex/docs.go create mode 100644 src/vendor/github.com/nickng/bibtex/error.go create mode 100644 src/vendor/github.com/nickng/bibtex/lexer.go create mode 100644 src/vendor/github.com/nickng/bibtex/scanner.go create mode 100644 src/vendor/github.com/nickng/bibtex/token.go create mode 100644 src/vendor/modules.txt diff --git a/src/decode.go b/src/decode.go new file mode 100644 index 0000000..53cebe4 --- /dev/null +++ b/src/decode.go @@ -0,0 +1,55 @@ +package main + +import ( + "log" + "strings" +) + +type conversion struct { + from string + to string +} + +func decodeTitle(title string) string { + for _, convert := range []conversion{ + {`\#`, "#"}, + {`--`, `–`}, + {"``", "“"}, + {"''", "”"}, + {"'", "’"}, // U+2019 + {`$\cdot$`, `·`}, // U+00B7. + } { + title = strings.Replace(title, convert.from, convert.to, -1) + } + + // Get rid of all curly brackets. We're displaying titles without changing + // their casing. + title = strings.ReplaceAll(title, "{", "") + title = strings.ReplaceAll(title, "}", "") + + return title +} + +func decodeAuthors(authors string) string { + for _, convert := range []conversion{ + {"'", "’"}, + } { + authors = strings.Replace(authors, convert.from, convert.to, -1) + } + // For simplicity, we expect authors to be formatted as "John Doe" instead + // of "Doe, John". + if strings.Contains(authors, ",") { + log.Fatalf("author %q contains a comma", authors) + } + authorSlice := strings.Split(authors, " and ") + return strings.Join(authorSlice, ", ") +} + +func decodeProceedings(proceedings string) string { + for _, convert := range []conversion{ + {`\&`, "&"}, + } { + proceedings = strings.Replace(proceedings, convert.from, convert.to, -1) + } + return proceedings +} diff --git a/src/decode_test.go b/src/decode_test.go new file mode 100644 index 0000000..8276555 --- /dev/null +++ b/src/decode_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "testing" +) + +func TestToString(t *testing.T) { + testCases := []conversion{ + { + from: "Title", + to: "Title", + }, + { + from: "This is a {Title}", + to: "This is a Title", + }, + { + from: "This is a {Title}", + to: "This is a Title", + }, + { + from: `{\#h00t}: Censorship Resistant Microblogging`, + to: `#h00t: Censorship Resistant Microblogging`, + }, + { + from: "``Good'' Worms and Human Rights", + to: "“Good” Worms and Human Rights", + }, + { + from: "An Analysis of {China}'s ``{Great Cannon}''", + to: "An Analysis of China’s “Great Cannon”", + }, + { + from: `lib$\cdot$erate, (n):`, + to: `lib·erate, (n):`, + }, + { + from: "Well -- Exploring the {Great} {Firewall}'s Poisoned {DNS}", + to: "Well – Exploring the Great Firewall’s Poisoned DNS", + }, + } + + for _, test := range testCases { + to := decodeTitle(test.from) + if to != test.to { + t.Errorf("Expected\n%s\ngot\n%s", test.to, to) + } + } +} + +func TestDecodeAuthors(t *testing.T) { + testCases := []conversion{ + { // Multiple authors should be separated by commas. + from: "John Doe and Jane Doe", + to: "John Doe, Jane Doe", + }, + { // Single authors should remain as-is. + from: "John Doe", + to: "John Doe", + }, + { // Single-name authors should remain as-is. + from: "John and Jane", + to: "John, Jane", + }, + { // Non-ASCII characters should be unaffected. + from: "Jóhn Doe", + to: "Jóhn Doe", + }, + { // Apostrophes should be replaced with the right single quote. + from: "John O'Brian", + to: "John O’Brian", + }, + } + + for _, test := range testCases { + to := decodeAuthors(test.from) + if to != test.to { + t.Errorf("Expected\n%s\ngot\n%s", test.to, to) + } + } +} diff --git a/src/footer.go b/src/footer.go new file mode 100644 index 0000000..796c9e1 --- /dev/null +++ b/src/footer.go @@ -0,0 +1,11 @@ +package main + +func footer() string { + return ` + + +` +} diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..3f4fb15 --- /dev/null +++ b/src/go.mod @@ -0,0 +1,5 @@ +module censorbib-go + +go 1.21.3 + +require github.com/nickng/bibtex v1.3.0 diff --git a/src/go.sum b/src/go.sum new file mode 100644 index 0000000..44be713 --- /dev/null +++ b/src/go.sum @@ -0,0 +1,2 @@ +github.com/nickng/bibtex v1.3.0 h1:iv0408z8Xe+FEVquJUo8eraXnhrAF0e+2/WayPcism8= +github.com/nickng/bibtex v1.3.0/go.mod h1:4BJ3ka/ZjGVXcHOlkzlRonex6U17L3kW6ICEsygP2bg= diff --git a/src/header.go b/src/header.go new file mode 100644 index 0000000..0376f86 --- /dev/null +++ b/src/header.go @@ -0,0 +1,231 @@ +package main + +import ( + "bytes" + "log" + "text/template" + "time" +) + +const headerTemplate = ` + + + + + + The Internet censorship bibliography + + + + + + +
+ +
+ +
+

Selected Research Papers
in Internet Censorship

+
+ +
+ +
+ CensorBib is an online archive of selected research papers in the field + of Internet censorship. Most papers on CensorBib approach the topic + from a technical angle, by proposing designs that circumvent censorship + systems, or by measuring how censorship works. The icons next to each + paper make it easy to download, cite, and link to papers. If you think + I missed a paper, + let me know. + You can sort papers by + year, + reverse year (default), + author, and + reverse author. + Finally, the + net4people/bbs forum + has reading groups for many of the papers listed below. +
+ + + +
+ +
+ +
+ +
+
+
+ +
+ Are you a researcher? If so, you may like my book + Research Power Tools. +
+
+ +
+ +
` + +func header() string { + tmpl, err := template.New("header").Parse(headerTemplate) + if err != nil { + log.Fatal(err) + } + i := struct { + Date string + }{ + Date: time.Now().UTC().Format(time.DateOnly), + } + buf := bytes.NewBufferString("") + if err = tmpl.Execute(buf, i); err != nil { + log.Fatalf("Error executing template: %v", err) + } + return buf.String() +} diff --git a/src/html.go b/src/html.go new file mode 100644 index 0000000..623d484 --- /dev/null +++ b/src/html.go @@ -0,0 +1,130 @@ +package main + +import ( + "fmt" + "io" + "sort" + "strings" + + "github.com/nickng/bibtex" +) + +func sortByYear(yearToEntries map[string][]string) []string { + keys := make([]string, 0, len(yearToEntries)) + for k := range yearToEntries { + keys = append(keys, k) + } + sort.Sort(sort.Reverse(sort.StringSlice(keys))) + return keys +} + +func appendIfNotEmpty(slice []string, s string) []string { + if s != "" { + return append(slice, s) + } + return slice +} + +func makeBib(to io.Writer, bibEntries []bibEntry) { + yearToEntries := make(map[string][]string) + + for _, entry := range bibEntries { + y := entry.Fields["year"].String() + yearToEntries[y] = append(yearToEntries[y], makeBibEntry(&entry)) + } + + sortedYears := sortByYear(yearToEntries) + for _, year := range sortedYears { + fmt.Fprintln(to, "") + } +} + +func makeBibEntry(entry *bibEntry) string { + s := []string{ + fmt.Sprintf("
  • ", entry.CiteName), + `
    `, + makeBibEntryTitle(entry), + `
    `, + `
    `, + makeBibEntryAuthors(entry), + `
    `, + ``, + makeBibEntryMisc(entry), + ``, + `
  • `, + } + return strings.Join(s, "\n") +} + +func makeBibEntryTitle(entry *bibEntry) string { + // Paper title is on the left side. + title := []string{ + ``, + decodeTitle(entry.Fields["title"].String()), + ``, + } + // Icons are on the right side. + icons := []string{ + ``, + fmt.Sprintf("", entry.Fields["url"].String()), + `Download icon`, + ``, + fmt.Sprintf("", entry.CiteName), + `Cached download icon`, + ``, + fmt.Sprintf("", entry.lineNum), + `BibTeX download icon`, + ``, + fmt.Sprintf("", entry.CiteName), + `Paper link icon`, + ``, + ``, + } + return strings.Join(append(title, icons...), "\n") +} + +func makeBibEntryAuthors(entry *bibEntry) string { + s := []string{ + ``, + decodeAuthors(entry.Fields["author"].String()), + ``, + } + return strings.Join(s, "\n") +} + +func makeBibEntryMisc(entry *bibEntry) string { + s := []string{} + s = appendIfNotEmpty(s, makeBibEntryVenue(entry)) + s = appendIfNotEmpty(s, toStr(entry.Fields["year"])) + s = appendIfNotEmpty(s, toStr(entry.Fields["publisher"])) + return strings.Join(s, ", ") +} + +func makeBibEntryVenue(entry *bibEntry) string { + var ( + prefix string + bs bibtex.BibString + ok bool + ) + + if bs, ok = entry.Fields["booktitle"]; ok { + prefix = "In Proc. of: " + } else if bs, ok = entry.Fields["journal"]; ok { + prefix = "In: " + } else { + return "" // Some entries are self-published. + } + + s := []string{ + prefix, + ``, + decodeProceedings(toStr(bs)), + ``, + } + + return strings.Join(s, "") +} diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..893db24 --- /dev/null +++ b/src/main.go @@ -0,0 +1,113 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "io" + "log" + "os" + "regexp" + "strings" + + "github.com/nickng/bibtex" +) + +// Matches e.g.: @inproceedings{Doe2024a, +var re = regexp.MustCompile(`@[a-z]*\{([A-Za-z\-]*[0-9]{4}[a-z]),`) + +// Map a cite name (e.g., Doe2024a) to its line number in the .bib file. All +// cite names are unique. +type entryToLineFunc func(string) int + +// Augment bibtex.BibEntry with the entry's line number in the .bib file. +type bibEntry struct { + bibtex.BibEntry + lineNum int +} + +func toStr(b bibtex.BibString) string { + if b == nil { + return "" + } + return b.String() +} + +func parseBibFile(path string) []bibEntry { + file, err := os.Open(path) + if err != nil { + log.Fatal(err) + } + bib, err := bibtex.Parse(file) + if err != nil { + log.Fatal(err) + } + + // Augment our BibTeX entries with their respective line numbers in the .bib + // file. This is necessary to create the "Download BibTeX" links. + lineOf := buildEntryToLineFunc(path) + bibEntries := []bibEntry{} + for _, entry := range bib.Entries { + bibEntries = append(bibEntries, bibEntry{ + BibEntry: *entry, + lineNum: lineOf(entry.CiteName), + }) + } + + return bibEntries +} + +func buildEntryToLineFunc(path string) entryToLineFunc { + file, err := os.Open(path) + if err != nil { + log.Fatal(err) + } + + sc := bufio.NewScanner(file) + entryToLine := make(map[string]int) + line := 0 + for sc.Scan() { + line++ + s := sc.Text() + if !strings.HasPrefix(s, "@") { + continue + } + entry := parseCiteName(s) // E.g., Doe2024a + entryToLine[entry] = line + } + if err := sc.Err(); err != nil { + log.Fatalf("scan file error: %v", err) + } + + return func(entry string) int { + if line, ok := entryToLine[entry]; ok { + return line + } + log.Fatalf("could not find line number for cite name: %s", entry) + return -1 + } +} + +func parseCiteName(line string) string { + matches := re.FindStringSubmatch(line) + if len(matches) != 2 { + log.Fatalf("failed to extract cite name of: %s", line) + } + return matches[1] +} + +func run(w io.Writer, bibEntries []bibEntry) { + fmt.Fprint(w, header()) + makeBib(w, bibEntries) + fmt.Fprint(w, footer()) +} + +func main() { + path := flag.String("path", "", "Path to .bib file.") + flag.Parse() + if *path == "" { + log.Fatal("No path to .bib file provided.") + } + run(os.Stdout, parseBibFile(*path)) + log.Println("Successfully created bibliography.") +} diff --git a/src/main_test.go b/src/main_test.go new file mode 100644 index 0000000..bd4929f --- /dev/null +++ b/src/main_test.go @@ -0,0 +1,43 @@ +package main + +import ( + "bytes" + "strings" + "testing" + + "github.com/nickng/bibtex" +) + +func mustParse(t *testing.T, s string) bibEntry { + t.Helper() + bib, err := bibtex.Parse(strings.NewReader(s)) + if err != nil { + t.Fatalf("failed to parse bibtex: %v", err) + } + return bibEntry{ + BibEntry: *bib.Entries[0], + lineNum: 0, + } +} + +func TestRun(t *testing.T) { + buf := bytes.NewBufferString("") + entry := mustParse(t, `@inproceedings{Almutairi2024a, + author = {Sultan Almutairi and Yogev Neumann and Khaled Harfoush}, + title = {Fingerprinting {VPNs} with Custom Router Firmware: A New Censorship Threat Model}, + booktitle = {Consumer Communications \& Networking Conference}, + publisher = {IEEE}, + year = {2024}, + url = {https://censorbib.nymity.ch/pdf/Almutairi2024a.pdf}, + }`) + + makeBib(buf, []bibEntry{entry}) + + bufStr := buf.String() + if !strings.HasPrefix(bufStr, " but got %q", bufStr[len(bufStr)-10:]) + } +} diff --git a/src/vendor/github.com/nickng/bibtex/.gitignore b/src/vendor/github.com/nickng/bibtex/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/src/vendor/github.com/nickng/bibtex/LICENSE b/src/vendor/github.com/nickng/bibtex/LICENSE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/vendor/github.com/nickng/bibtex/README.md b/src/vendor/github.com/nickng/bibtex/README.md new file mode 100644 index 0000000..0457c7f --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/README.md @@ -0,0 +1,17 @@ +# bibtex ![Build Status](https://github.com/nickng/bibtex/actions/workflows/test.yml/badge.svg) [![Go Reference](https://pkg.go.dev/badge/github.com/nickng/bibtex.svg)](https://pkg.go.dev/github.com/nickng/bibtex) + +## `nickng/bibtex` is a bibtex parser and library for Go. + +The bibtex format is not standardised, this parser follows the descriptions found +[here](http://maverick.inria.fr/~Xavier.Decoret/resources/xdkbibtex/bibtex_summary.html). +Please file any issues with a minimal working example. + +To get: + + go get -u github.com/nickng/bibtex/... + +This will also install `prettybib`, a bibtex pretty printer. +To parse and pretty print a bibtex file, for example: + + cd $GOPATH/src/github.com/nickng/bibtex + prettybib -in example/simple.bib diff --git a/src/vendor/github.com/nickng/bibtex/bibtex.go b/src/vendor/github.com/nickng/bibtex/bibtex.go new file mode 100644 index 0000000..8de3390 --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/bibtex.go @@ -0,0 +1,355 @@ +package bibtex + +import ( + "bytes" + "fmt" + "log" + "sort" + "strconv" + "strings" + "text/tabwriter" + "time" +) + +// BibString is a segment of a bib string. +type BibString interface { + RawString() string // Internal representation. + String() string // Displayed string. +} + +// BibVar is a string variable. +type BibVar struct { + Key string // Variable key. + Value BibString // Variable actual value. +} + +// RawString is the internal representation of the variable. +func (v *BibVar) RawString() string { + return v.Key +} + +func (v *BibVar) String() string { + return v.Value.String() +} + +// BibConst is a string constant. +type BibConst string + +// NewBibConst converts a constant string to BibConst. +func NewBibConst(c string) BibConst { + return BibConst(c) +} + +// RawString is the internal representation of the constant (i.e. the string). +func (c BibConst) RawString() string { + return fmt.Sprintf("{%s}", string(c)) +} + +func (c BibConst) String() string { + return string(c) +} + +// BibComposite is a composite string, may contain both variable and string. +type BibComposite []BibString + +// NewBibComposite creates a new composite with one element. +func NewBibComposite(s BibString) *BibComposite { + comp := &BibComposite{} + return comp.Append(s) +} + +// Append adds a BibString to the composite +func (c *BibComposite) Append(s BibString) *BibComposite { + comp := append(*c, s) + return &comp +} + +func (c *BibComposite) String() string { + var buf bytes.Buffer + for _, s := range *c { + buf.WriteString(s.String()) + } + return buf.String() +} + +// RawString returns a raw (bibtex) representation of the composite string. +func (c *BibComposite) RawString() string { + var buf bytes.Buffer + for i, comp := range *c { + if i > 0 { + buf.WriteString(" # ") + } + switch comp := comp.(type) { + case *BibConst: + buf.WriteString(comp.RawString()) + case *BibVar: + buf.WriteString(comp.RawString()) + case *BibComposite: + buf.WriteString(comp.RawString()) + } + } + return buf.String() +} + +// BibEntry is a record of BibTeX record. +type BibEntry struct { + Type string + CiteName string + Fields map[string]BibString +} + +// NewBibEntry creates a new BibTeX entry. +func NewBibEntry(entryType string, citeName string) *BibEntry { + spaceStripper := strings.NewReplacer(" ", "") + cleanedType := strings.ToLower(spaceStripper.Replace(entryType)) + cleanedName := spaceStripper.Replace(citeName) + return &BibEntry{ + Type: cleanedType, + CiteName: cleanedName, + Fields: map[string]BibString{}, + } +} + +// AddField adds a field (key-value) to a BibTeX entry. +func (entry *BibEntry) AddField(name string, value BibString) { + entry.Fields[strings.TrimSpace(name)] = value +} + +// prettyStringConfig controls the formatting/printing behaviour of the BibTex's and BibEntry's PrettyPrint functions +type prettyStringConfig struct { + // priority controls the order in which fields are printed. Keys with lower values are printed earlier. + //See keyOrderToPriorityMap + priority map[string]int +} + +// keyOrderToPriorityMap is a helper function for WithKeyOrder, converting the user facing key order slice +// into the map format that is internally used by the sort function +func keyOrderToPriorityMap(keyOrder []string) map[string]int { + priority := make(map[string]int) + offset := len(keyOrder) + for i, v := range keyOrder { + priority[v] = i - offset + } + return priority +} + +var defaultPrettyStringConfig = prettyStringConfig{priority: keyOrderToPriorityMap([]string{"title", "author", "url"})} + +// PrettyStringOpt allows to change the pretty print format for BibEntry and BibTex +type PrettyStringOpt func(config *prettyStringConfig) + +// WithKeyOrder changes the order in which BibEntry keys are printed to the order in which they appear in keyOrder +func WithKeyOrder(keyOrder []string) PrettyStringOpt { + return func(config *prettyStringConfig) { + config.priority = make(map[string]int) + offset := len(keyOrder) + for i, v := range keyOrder { + config.priority[v] = i - offset + } + } +} + +// prettyStringAppend appends the pretty print string for BibEntry using config to configure the formatting +func (entry *BibEntry) prettyStringAppend(buf *bytes.Buffer, config prettyStringConfig) { + fmt.Fprintf(buf, "@%s{%s,\n", entry.Type, entry.CiteName) + + // Determine key order. + keys := []string{} + for key := range entry.Fields { + keys = append(keys, key) + } + + sort.Slice(keys, func(i, j int) bool { + pi, pj := config.priority[keys[i]], config.priority[keys[j]] + return pi < pj || (pi == pj && keys[i] < keys[j]) + }) + + // Write fields. + tw := tabwriter.NewWriter(buf, 1, 4, 1, ' ', 0) + for _, key := range keys { + value := entry.Fields[key].String() + format := stringformat(value) + fmt.Fprintf(tw, " %s\t=\t"+format+",\n", key, value) + } + tw.Flush() + buf.WriteString("}\n") + +} + +// PrettyString pretty prints a BibEntry +func (entry *BibEntry) PrettyString(options ...PrettyStringOpt) string { + config := defaultPrettyStringConfig + for _, option := range options { + option(&config) + } + var buf bytes.Buffer + entry.prettyStringAppend(&buf, config) + + return buf.String() +} + +// String returns a BibTex entry as a simplified BibTex string. +func (entry *BibEntry) String() string { + var bibtex bytes.Buffer + bibtex.WriteString(fmt.Sprintf("@%s{%s,\n", entry.Type, entry.CiteName)) + for key, val := range entry.Fields { + if i, err := strconv.Atoi(strings.TrimSpace(val.String())); err == nil { + bibtex.WriteString(fmt.Sprintf(" %s = %d,\n", key, i)) + } else { + bibtex.WriteString(fmt.Sprintf(" %s = {%s},\n", key, strings.TrimSpace(val.String()))) + } + } + bibtex.Truncate(bibtex.Len() - 2) + bibtex.WriteString(fmt.Sprintf("\n}\n")) + return bibtex.String() +} + +// RawString returns a BibTex entry data structure in its internal representation. +func (entry *BibEntry) RawString() string { + var bibtex bytes.Buffer + bibtex.WriteString(fmt.Sprintf("@%s{%s,\n", entry.Type, entry.CiteName)) + for key, val := range entry.Fields { + if i, err := strconv.Atoi(strings.TrimSpace(val.String())); err == nil { + bibtex.WriteString(fmt.Sprintf(" %s = %d,\n", key, i)) + } else { + bibtex.WriteString(fmt.Sprintf(" %s = %s,\n", key, val.RawString())) + } + } + bibtex.Truncate(bibtex.Len() - 2) + bibtex.WriteString(fmt.Sprintf("\n}\n")) + return bibtex.String() +} + +// BibTex is a list of BibTeX entries. +type BibTex struct { + Preambles []BibString // List of Preambles + Entries []*BibEntry // Items in a bibliography. + StringVar map[string]*BibVar // Map from string variable to string. + + // A list of default BibVars that are implicitly + // defined and can be used without defining + defaultVars map[string]string +} + +// NewBibTex creates a new BibTex data structure. +func NewBibTex() *BibTex { + // Sets up some default vars + months := map[string]time.Month{ + "jan": 1, "feb": 2, "mar": 3, + "apr": 4, "may": 5, "jun": 6, + "jul": 7, "aug": 8, "sep": 9, + "oct": 10, "nov": 11, "dec": 12, + } + + defaultVars := make(map[string]string) + for mth, month := range months { + // TODO(nickng): i10n of month name in user's local language + defaultVars[mth] = month.String() + } + + return &BibTex{ + Preambles: []BibString{}, + Entries: []*BibEntry{}, + StringVar: make(map[string]*BibVar), + + defaultVars: defaultVars, + } +} + +// AddPreamble adds a preamble to a bibtex. +func (bib *BibTex) AddPreamble(p BibString) { + bib.Preambles = append(bib.Preambles, p) +} + +// AddEntry adds an entry to the BibTeX data structure. +func (bib *BibTex) AddEntry(entry *BibEntry) { + bib.Entries = append(bib.Entries, entry) +} + +// AddStringVar adds a new string var (if does not exist). +func (bib *BibTex) AddStringVar(key string, val BibString) { + bib.StringVar[key] = &BibVar{Key: key, Value: val} +} + +// GetStringVar looks up a string by its key. +func (bib *BibTex) GetStringVar(key string) *BibVar { + if bv, ok := bib.StringVar[key]; ok { + return bv + } + if v, ok := bib.getDefaultVar(key); ok { + return v + } + // This is undefined. + log.Fatalf("%s: %s", ErrUnknownStringVar, key) + return nil +} + +// getDefaultVar is a fallback for looking up keys (e.g. 3-character month) +// and use them even though it hasn't been defined in the bib. +func (bib *BibTex) getDefaultVar(key string) (*BibVar, bool) { + if v, ok := bib.defaultVars[key]; ok { + // if found, add this to the BibTex + bib.StringVar[key] = &BibVar{Key: key, Value: NewBibConst(v)} + return bib.StringVar[key], true + } + + return nil, false +} + +// String returns a BibTex data structure as a simplified BibTex string. +func (bib *BibTex) String() string { + var bibtex bytes.Buffer + for _, entry := range bib.Entries { + bibtex.WriteString(entry.String()) + } + return bibtex.String() +} + +// RawString returns a BibTex data structure in its internal representation. +func (bib *BibTex) RawString() string { + var bibtex bytes.Buffer + for k, strvar := range bib.StringVar { + bibtex.WriteString(fmt.Sprintf("@string{%s = {%s}}\n", k, strvar.String())) + } + for _, preamble := range bib.Preambles { + bibtex.WriteString(fmt.Sprintf("@preamble{%s}\n", preamble.RawString())) + } + for _, entry := range bib.Entries { + bibtex.WriteString(entry.RawString()) + } + return bibtex.String() +} + +// PrettyString pretty prints a BibTex +func (bib *BibTex) PrettyString(options ...PrettyStringOpt) string { + config := defaultPrettyStringConfig + for _, option := range options { + option(&config) + } + + var buf bytes.Buffer + for i, entry := range bib.Entries { + if i != 0 { + fmt.Fprint(&buf, "\n") + } + entry.prettyStringAppend(&buf, config) + + } + return buf.String() +} + +// stringformat determines the correct formatting verb for the given BibTeX field value. +func stringformat(v string) string { + // Numbers may be represented unquoted. + if _, err := strconv.Atoi(v); err == nil { + return "%s" + } + + // Strings with certain characters must be brace quoted. + if strings.ContainsAny(v, "\"{}") { + return "{%s}" + } + + // Default to quoted string. + return "%q" +} diff --git a/src/vendor/github.com/nickng/bibtex/bibtex.y b/src/vendor/github.com/nickng/bibtex/bibtex.y new file mode 100644 index 0000000..2664cb0 --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/bibtex.y @@ -0,0 +1,89 @@ +%{ +package bibtex + +import ( + "io" +) + +type bibTag struct { + key string + val BibString +} + +var bib *BibTex // Only for holding current bib +%} + +%union { + bibtex *BibTex + strval string + bibentry *BibEntry + bibtag *bibTag + bibtags []*bibTag + strings BibString +} + +%token tCOMMENT tSTRING tPREAMBLE +%token tATSIGN tCOLON tEQUAL tCOMMA tPOUND tLBRACE tRBRACE tDQUOTE tLPAREN tRPAREN +%token tBAREIDENT tIDENT tCOMMENTBODY +%type bibtex +%type bibentry +%type tag stringentry +%type tags +%type longstring preambleentry + +%% + +top : bibtex { } + ; + +bibtex : /* empty */ { $$ = NewBibTex(); bib = $$ } + | bibtex bibentry { $$ = $1; $$.AddEntry($2) } + | bibtex commententry { $$ = $1 } + | bibtex stringentry { $$ = $1; $$.AddStringVar($2.key, $2.val) } + | bibtex preambleentry { $$ = $1; $$.AddPreamble($2) } + ; + +bibentry : tATSIGN tBAREIDENT tLBRACE tBAREIDENT tCOMMA tags tRBRACE { $$ = NewBibEntry($2, $4); for _, t := range $6 { $$.AddField(t.key, t.val) } } + | tATSIGN tBAREIDENT tLPAREN tBAREIDENT tCOMMA tags tRPAREN { $$ = NewBibEntry($2, $4); for _, t := range $6 { $$.AddField(t.key, t.val) } } + ; + +commententry : tATSIGN tCOMMENT tCOMMENTBODY { } + ; + +stringentry : tATSIGN tSTRING tLBRACE tBAREIDENT tEQUAL longstring tRBRACE { $$ = &bibTag{key: $4, val: $6 } } + | tATSIGN tSTRING tLPAREN tBAREIDENT tEQUAL longstring tRBRACE { $$ = &bibTag{key: $4, val: $6 } } + ; + +preambleentry : tATSIGN tPREAMBLE tLBRACE longstring tRBRACE { $$ = $4 } + | tATSIGN tPREAMBLE tLPAREN longstring tRPAREN { $$ = $4 } + ; + +longstring : tIDENT { $$ = NewBibConst($1) } + | tBAREIDENT { $$ = bib.GetStringVar($1) } + | longstring tPOUND tIDENT { $$ = NewBibComposite($1); $$.(*BibComposite).Append(NewBibConst($3))} + | longstring tPOUND tBAREIDENT { $$ = NewBibComposite($1); $$.(*BibComposite).Append(bib.GetStringVar($3)) } + ; + +tag : /* empty */ { } + | tBAREIDENT tEQUAL longstring { $$ = &bibTag{key: $1, val: $3} } + ; + +tags : tag { if $1 != nil { $$ = []*bibTag{$1}; } } + | tags tCOMMA tag { if $3 == nil { $$ = $1 } else { $$ = append($1, $3) } } + ; + +%% + +// Parse is the entry point to the bibtex parser. +func Parse(r io.Reader) (*BibTex, error) { + l := newLexer(r) + bibtexParse(l) + switch { + case len(l.Errors) > 0: // Non-yacc errors + return nil, l.Errors[0] + case len(l.ParseErrors) > 0: + return nil, l.ParseErrors[0] + default: + return bib, nil + } +} diff --git a/src/vendor/github.com/nickng/bibtex/bibtex.y.go b/src/vendor/github.com/nickng/bibtex/bibtex.y.go new file mode 100644 index 0000000..a394cb3 --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/bibtex.y.go @@ -0,0 +1,645 @@ +// Code generated by goyacc -p bibtex -o bibtex.y.go bibtex.y. DO NOT EDIT. + +//line bibtex.y:2 +package bibtex + +import __yyfmt__ "fmt" + +//line bibtex.y:2 + +import ( + "io" +) + +type bibTag struct { + key string + val BibString +} + +var bib *BibTex // Only for holding current bib + +//line bibtex.y:16 +type bibtexSymType struct { + yys int + bibtex *BibTex + strval string + bibentry *BibEntry + bibtag *bibTag + bibtags []*bibTag + strings BibString +} + +const tCOMMENT = 57346 +const tSTRING = 57347 +const tPREAMBLE = 57348 +const tATSIGN = 57349 +const tCOLON = 57350 +const tEQUAL = 57351 +const tCOMMA = 57352 +const tPOUND = 57353 +const tLBRACE = 57354 +const tRBRACE = 57355 +const tDQUOTE = 57356 +const tLPAREN = 57357 +const tRPAREN = 57358 +const tBAREIDENT = 57359 +const tIDENT = 57360 +const tCOMMENTBODY = 57361 + +var bibtexToknames = [...]string{ + "$end", + "error", + "$unk", + "tCOMMENT", + "tSTRING", + "tPREAMBLE", + "tATSIGN", + "tCOLON", + "tEQUAL", + "tCOMMA", + "tPOUND", + "tLBRACE", + "tRBRACE", + "tDQUOTE", + "tLPAREN", + "tRPAREN", + "tBAREIDENT", + "tIDENT", + "tCOMMENTBODY", +} +var bibtexStatenames = [...]string{} + +const bibtexEofCode = 1 +const bibtexErrCode = 2 +const bibtexInitialStackSize = 16 + +//line bibtex.y:75 + +// Parse is the entry point to the bibtex parser. +func Parse(r io.Reader) (*BibTex, error) { + l := newLexer(r) + bibtexParse(l) + switch { + case len(l.Errors) > 0: // Non-yacc errors + return nil, l.Errors[0] + case len(l.ParseErrors) > 0: + return nil, l.ParseErrors[0] + default: + return bib, nil + } +} + +//line yacctab:1 +var bibtexExca = [...]int{ + -1, 1, + 1, -1, + -2, 0, +} + +const bibtexPrivate = 57344 + +const bibtexLast = 54 + +var bibtexAct = [...]int{ + + 23, 14, 35, 34, 9, 10, 11, 25, 24, 41, + 40, 36, 43, 22, 21, 32, 20, 8, 45, 26, + 33, 17, 19, 15, 18, 12, 16, 32, 13, 47, + 38, 39, 37, 32, 43, 46, 32, 42, 31, 32, + 28, 27, 44, 30, 29, 49, 48, 7, 4, 1, + 6, 5, 3, 2, +} +var bibtexPact = [...]int{ + + -1000, -1000, 40, -1000, -1000, -1000, -1000, 0, 13, -18, + 11, 9, 5, -1, -1000, -3, -4, -10, -10, 31, + 30, 35, 34, 25, -1000, -1000, 4, -6, -6, -10, + -10, -1000, -8, -1000, 24, -1000, 33, 2, 22, 16, + -1000, -1000, -1000, -6, -10, -1000, -1000, -1000, -1000, 28, +} +var bibtexPgo = [...]int{ + + 0, 53, 52, 2, 51, 3, 0, 50, 49, 48, +} +var bibtexR1 = [...]int{ + + 0, 8, 1, 1, 1, 1, 1, 2, 2, 9, + 4, 4, 7, 7, 6, 6, 6, 6, 3, 3, + 5, 5, +} +var bibtexR2 = [...]int{ + + 0, 1, 0, 2, 2, 2, 2, 7, 7, 3, + 7, 7, 5, 5, 1, 1, 3, 3, 0, 3, + 1, 3, +} +var bibtexChk = [...]int{ + + -1000, -8, -1, -2, -9, -4, -7, 7, 17, 4, + 5, 6, 12, 15, 19, 12, 15, 12, 15, 17, + 17, 17, 17, -6, 18, 17, -6, 10, 10, 9, + 9, 13, 11, 16, -5, -3, 17, -5, -6, -6, + 18, 17, 13, 10, 9, 16, 13, 13, -3, -6, +} +var bibtexDef = [...]int{ + + 2, -2, 1, 3, 4, 5, 6, 0, 0, 0, + 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 14, 15, 0, 18, 18, 0, + 0, 12, 0, 13, 0, 20, 0, 0, 0, 0, + 16, 17, 7, 18, 0, 8, 10, 11, 21, 19, +} +var bibtexTok1 = [...]int{ + + 1, +} +var bibtexTok2 = [...]int{ + + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, +} +var bibtexTok3 = [...]int{ + 0, +} + +var bibtexErrorMessages = [...]struct { + state int + token int + msg string +}{} + +//line yaccpar:1 + +/* parser for yacc output */ + +var ( + bibtexDebug = 0 + bibtexErrorVerbose = false +) + +type bibtexLexer interface { + Lex(lval *bibtexSymType) int + Error(s string) +} + +type bibtexParser interface { + Parse(bibtexLexer) int + Lookahead() int +} + +type bibtexParserImpl struct { + lval bibtexSymType + stack [bibtexInitialStackSize]bibtexSymType + char int +} + +func (p *bibtexParserImpl) Lookahead() int { + return p.char +} + +func bibtexNewParser() bibtexParser { + return &bibtexParserImpl{} +} + +const bibtexFlag = -1000 + +func bibtexTokname(c int) string { + if c >= 1 && c-1 < len(bibtexToknames) { + if bibtexToknames[c-1] != "" { + return bibtexToknames[c-1] + } + } + return __yyfmt__.Sprintf("tok-%v", c) +} + +func bibtexStatname(s int) string { + if s >= 0 && s < len(bibtexStatenames) { + if bibtexStatenames[s] != "" { + return bibtexStatenames[s] + } + } + return __yyfmt__.Sprintf("state-%v", s) +} + +func bibtexErrorMessage(state, lookAhead int) string { + const TOKSTART = 4 + + if !bibtexErrorVerbose { + return "syntax error" + } + + for _, e := range bibtexErrorMessages { + if e.state == state && e.token == lookAhead { + return "syntax error: " + e.msg + } + } + + res := "syntax error: unexpected " + bibtexTokname(lookAhead) + + // To match Bison, suggest at most four expected tokens. + expected := make([]int, 0, 4) + + // Look for shiftable tokens. + base := bibtexPact[state] + for tok := TOKSTART; tok-1 < len(bibtexToknames); tok++ { + if n := base + tok; n >= 0 && n < bibtexLast && bibtexChk[bibtexAct[n]] == tok { + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + } + + if bibtexDef[state] == -2 { + i := 0 + for bibtexExca[i] != -1 || bibtexExca[i+1] != state { + i += 2 + } + + // Look for tokens that we accept or reduce. + for i += 2; bibtexExca[i] >= 0; i += 2 { + tok := bibtexExca[i] + if tok < TOKSTART || bibtexExca[i+1] == 0 { + continue + } + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + + // If the default action is to accept or reduce, give up. + if bibtexExca[i+1] != 0 { + return res + } + } + + for i, tok := range expected { + if i == 0 { + res += ", expecting " + } else { + res += " or " + } + res += bibtexTokname(tok) + } + return res +} + +func bibtexlex1(lex bibtexLexer, lval *bibtexSymType) (char, token int) { + token = 0 + char = lex.Lex(lval) + if char <= 0 { + token = bibtexTok1[0] + goto out + } + if char < len(bibtexTok1) { + token = bibtexTok1[char] + goto out + } + if char >= bibtexPrivate { + if char < bibtexPrivate+len(bibtexTok2) { + token = bibtexTok2[char-bibtexPrivate] + goto out + } + } + for i := 0; i < len(bibtexTok3); i += 2 { + token = bibtexTok3[i+0] + if token == char { + token = bibtexTok3[i+1] + goto out + } + } + +out: + if token == 0 { + token = bibtexTok2[1] /* unknown char */ + } + if bibtexDebug >= 3 { + __yyfmt__.Printf("lex %s(%d)\n", bibtexTokname(token), uint(char)) + } + return char, token +} + +func bibtexParse(bibtexlex bibtexLexer) int { + return bibtexNewParser().Parse(bibtexlex) +} + +func (bibtexrcvr *bibtexParserImpl) Parse(bibtexlex bibtexLexer) int { + var bibtexn int + var bibtexVAL bibtexSymType + var bibtexDollar []bibtexSymType + _ = bibtexDollar // silence set and not used + bibtexS := bibtexrcvr.stack[:] + + Nerrs := 0 /* number of errors */ + Errflag := 0 /* error recovery flag */ + bibtexstate := 0 + bibtexrcvr.char = -1 + bibtextoken := -1 // bibtexrcvr.char translated into internal numbering + defer func() { + // Make sure we report no lookahead when not parsing. + bibtexstate = -1 + bibtexrcvr.char = -1 + bibtextoken = -1 + }() + bibtexp := -1 + goto bibtexstack + +ret0: + return 0 + +ret1: + return 1 + +bibtexstack: + /* put a state and value onto the stack */ + if bibtexDebug >= 4 { + __yyfmt__.Printf("char %v in %v\n", bibtexTokname(bibtextoken), bibtexStatname(bibtexstate)) + } + + bibtexp++ + if bibtexp >= len(bibtexS) { + nyys := make([]bibtexSymType, len(bibtexS)*2) + copy(nyys, bibtexS) + bibtexS = nyys + } + bibtexS[bibtexp] = bibtexVAL + bibtexS[bibtexp].yys = bibtexstate + +bibtexnewstate: + bibtexn = bibtexPact[bibtexstate] + if bibtexn <= bibtexFlag { + goto bibtexdefault /* simple state */ + } + if bibtexrcvr.char < 0 { + bibtexrcvr.char, bibtextoken = bibtexlex1(bibtexlex, &bibtexrcvr.lval) + } + bibtexn += bibtextoken + if bibtexn < 0 || bibtexn >= bibtexLast { + goto bibtexdefault + } + bibtexn = bibtexAct[bibtexn] + if bibtexChk[bibtexn] == bibtextoken { /* valid shift */ + bibtexrcvr.char = -1 + bibtextoken = -1 + bibtexVAL = bibtexrcvr.lval + bibtexstate = bibtexn + if Errflag > 0 { + Errflag-- + } + goto bibtexstack + } + +bibtexdefault: + /* default state action */ + bibtexn = bibtexDef[bibtexstate] + if bibtexn == -2 { + if bibtexrcvr.char < 0 { + bibtexrcvr.char, bibtextoken = bibtexlex1(bibtexlex, &bibtexrcvr.lval) + } + + /* look through exception table */ + xi := 0 + for { + if bibtexExca[xi+0] == -1 && bibtexExca[xi+1] == bibtexstate { + break + } + xi += 2 + } + for xi += 2; ; xi += 2 { + bibtexn = bibtexExca[xi+0] + if bibtexn < 0 || bibtexn == bibtextoken { + break + } + } + bibtexn = bibtexExca[xi+1] + if bibtexn < 0 { + goto ret0 + } + } + if bibtexn == 0 { + /* error ... attempt to resume parsing */ + switch Errflag { + case 0: /* brand new error */ + bibtexlex.Error(bibtexErrorMessage(bibtexstate, bibtextoken)) + Nerrs++ + if bibtexDebug >= 1 { + __yyfmt__.Printf("%s", bibtexStatname(bibtexstate)) + __yyfmt__.Printf(" saw %s\n", bibtexTokname(bibtextoken)) + } + fallthrough + + case 1, 2: /* incompletely recovered error ... try again */ + Errflag = 3 + + /* find a state where "error" is a legal shift action */ + for bibtexp >= 0 { + bibtexn = bibtexPact[bibtexS[bibtexp].yys] + bibtexErrCode + if bibtexn >= 0 && bibtexn < bibtexLast { + bibtexstate = bibtexAct[bibtexn] /* simulate a shift of "error" */ + if bibtexChk[bibtexstate] == bibtexErrCode { + goto bibtexstack + } + } + + /* the current p has no shift on "error", pop stack */ + if bibtexDebug >= 2 { + __yyfmt__.Printf("error recovery pops state %d\n", bibtexS[bibtexp].yys) + } + bibtexp-- + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1 + + case 3: /* no shift yet; clobber input char */ + if bibtexDebug >= 2 { + __yyfmt__.Printf("error recovery discards %s\n", bibtexTokname(bibtextoken)) + } + if bibtextoken == bibtexEofCode { + goto ret1 + } + bibtexrcvr.char = -1 + bibtextoken = -1 + goto bibtexnewstate /* try again in the same state */ + } + } + + /* reduction by production bibtexn */ + if bibtexDebug >= 2 { + __yyfmt__.Printf("reduce %v in:\n\t%v\n", bibtexn, bibtexStatname(bibtexstate)) + } + + bibtexnt := bibtexn + bibtexpt := bibtexp + _ = bibtexpt // guard against "declared and not used" + + bibtexp -= bibtexR2[bibtexn] + // bibtexp is now the index of $0. Perform the default action. Iff the + // reduced production is ε, $1 is possibly out of range. + if bibtexp+1 >= len(bibtexS) { + nyys := make([]bibtexSymType, len(bibtexS)*2) + copy(nyys, bibtexS) + bibtexS = nyys + } + bibtexVAL = bibtexS[bibtexp+1] + + /* consult goto table to find next state */ + bibtexn = bibtexR1[bibtexn] + bibtexg := bibtexPgo[bibtexn] + bibtexj := bibtexg + bibtexS[bibtexp].yys + 1 + + if bibtexj >= bibtexLast { + bibtexstate = bibtexAct[bibtexg] + } else { + bibtexstate = bibtexAct[bibtexj] + if bibtexChk[bibtexstate] != -bibtexn { + bibtexstate = bibtexAct[bibtexg] + } + } + // dummy call; replaced with literal code + switch bibtexnt { + + case 1: + bibtexDollar = bibtexS[bibtexpt-1 : bibtexpt+1] +//line bibtex.y:36 + { + } + case 2: + bibtexDollar = bibtexS[bibtexpt-0 : bibtexpt+1] +//line bibtex.y:39 + { + bibtexVAL.bibtex = NewBibTex() + bib = bibtexVAL.bibtex + } + case 3: + bibtexDollar = bibtexS[bibtexpt-2 : bibtexpt+1] +//line bibtex.y:40 + { + bibtexVAL.bibtex = bibtexDollar[1].bibtex + bibtexVAL.bibtex.AddEntry(bibtexDollar[2].bibentry) + } + case 4: + bibtexDollar = bibtexS[bibtexpt-2 : bibtexpt+1] +//line bibtex.y:41 + { + bibtexVAL.bibtex = bibtexDollar[1].bibtex + } + case 5: + bibtexDollar = bibtexS[bibtexpt-2 : bibtexpt+1] +//line bibtex.y:42 + { + bibtexVAL.bibtex = bibtexDollar[1].bibtex + bibtexVAL.bibtex.AddStringVar(bibtexDollar[2].bibtag.key, bibtexDollar[2].bibtag.val) + } + case 6: + bibtexDollar = bibtexS[bibtexpt-2 : bibtexpt+1] +//line bibtex.y:43 + { + bibtexVAL.bibtex = bibtexDollar[1].bibtex + bibtexVAL.bibtex.AddPreamble(bibtexDollar[2].strings) + } + case 7: + bibtexDollar = bibtexS[bibtexpt-7 : bibtexpt+1] +//line bibtex.y:46 + { + bibtexVAL.bibentry = NewBibEntry(bibtexDollar[2].strval, bibtexDollar[4].strval) + for _, t := range bibtexDollar[6].bibtags { + bibtexVAL.bibentry.AddField(t.key, t.val) + } + } + case 8: + bibtexDollar = bibtexS[bibtexpt-7 : bibtexpt+1] +//line bibtex.y:47 + { + bibtexVAL.bibentry = NewBibEntry(bibtexDollar[2].strval, bibtexDollar[4].strval) + for _, t := range bibtexDollar[6].bibtags { + bibtexVAL.bibentry.AddField(t.key, t.val) + } + } + case 9: + bibtexDollar = bibtexS[bibtexpt-3 : bibtexpt+1] +//line bibtex.y:50 + { + } + case 10: + bibtexDollar = bibtexS[bibtexpt-7 : bibtexpt+1] +//line bibtex.y:53 + { + bibtexVAL.bibtag = &bibTag{key: bibtexDollar[4].strval, val: bibtexDollar[6].strings} + } + case 11: + bibtexDollar = bibtexS[bibtexpt-7 : bibtexpt+1] +//line bibtex.y:54 + { + bibtexVAL.bibtag = &bibTag{key: bibtexDollar[4].strval, val: bibtexDollar[6].strings} + } + case 12: + bibtexDollar = bibtexS[bibtexpt-5 : bibtexpt+1] +//line bibtex.y:57 + { + bibtexVAL.strings = bibtexDollar[4].strings + } + case 13: + bibtexDollar = bibtexS[bibtexpt-5 : bibtexpt+1] +//line bibtex.y:58 + { + bibtexVAL.strings = bibtexDollar[4].strings + } + case 14: + bibtexDollar = bibtexS[bibtexpt-1 : bibtexpt+1] +//line bibtex.y:61 + { + bibtexVAL.strings = NewBibConst(bibtexDollar[1].strval) + } + case 15: + bibtexDollar = bibtexS[bibtexpt-1 : bibtexpt+1] +//line bibtex.y:62 + { + bibtexVAL.strings = bib.GetStringVar(bibtexDollar[1].strval) + } + case 16: + bibtexDollar = bibtexS[bibtexpt-3 : bibtexpt+1] +//line bibtex.y:63 + { + bibtexVAL.strings = NewBibComposite(bibtexDollar[1].strings) + bibtexVAL.strings.(*BibComposite).Append(NewBibConst(bibtexDollar[3].strval)) + } + case 17: + bibtexDollar = bibtexS[bibtexpt-3 : bibtexpt+1] +//line bibtex.y:64 + { + bibtexVAL.strings = NewBibComposite(bibtexDollar[1].strings) + bibtexVAL.strings.(*BibComposite).Append(bib.GetStringVar(bibtexDollar[3].strval)) + } + case 18: + bibtexDollar = bibtexS[bibtexpt-0 : bibtexpt+1] +//line bibtex.y:67 + { + } + case 19: + bibtexDollar = bibtexS[bibtexpt-3 : bibtexpt+1] +//line bibtex.y:68 + { + bibtexVAL.bibtag = &bibTag{key: bibtexDollar[1].strval, val: bibtexDollar[3].strings} + } + case 20: + bibtexDollar = bibtexS[bibtexpt-1 : bibtexpt+1] +//line bibtex.y:71 + { + if bibtexDollar[1].bibtag != nil { + bibtexVAL.bibtags = []*bibTag{bibtexDollar[1].bibtag} + } + } + case 21: + bibtexDollar = bibtexS[bibtexpt-3 : bibtexpt+1] +//line bibtex.y:72 + { + if bibtexDollar[3].bibtag == nil { + bibtexVAL.bibtags = bibtexDollar[1].bibtags + } else { + bibtexVAL.bibtags = append(bibtexDollar[1].bibtags, bibtexDollar[3].bibtag) + } + } + } + goto bibtexstack /* stack new state and value */ +} diff --git a/src/vendor/github.com/nickng/bibtex/docs.go b/src/vendor/github.com/nickng/bibtex/docs.go new file mode 100644 index 0000000..7b2883e --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/docs.go @@ -0,0 +1,23 @@ +// Package bibtex is a bibtex parser written in Go. +// +// The package contains a simple parser and data structure to represent bibtex +// records. +// +// # Supported syntax +// +// The basic syntax is: +// +// @BIBTYPE{IDENT, +// key1 = word, +// key2 = "quoted", +// key3 = {quoted}, +// } +// +// where BIBTYPE is the type of document (e.g. inproceedings, article, etc.) +// and IDENT is a string identifier. +// +// The bibtex format is not standardised, this parser follows the descriptions +// found in the link below. If there are any problems, please file any issues +// with a minimal working example at the GitHub repository. +// http://maverick.inria.fr/~Xavier.Decoret/resources/xdkbibtex/bibtex_summary.html +package bibtex // import "github.com/nickng/bibtex" diff --git a/src/vendor/github.com/nickng/bibtex/error.go b/src/vendor/github.com/nickng/bibtex/error.go new file mode 100644 index 0000000..5119d74 --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/error.go @@ -0,0 +1,23 @@ +package bibtex + +import ( + "errors" + "fmt" +) + +var ( + // ErrUnexpectedAtsign is an error for unexpected @ in {}. + ErrUnexpectedAtsign = errors.New("unexpected @ sign") + // ErrUnknownStringVar is an error for looking up undefined string var. + ErrUnknownStringVar = errors.New("unknown string variable") +) + +// ErrParse is a parse error. +type ErrParse struct { + Pos tokenPos + Err string // Error string returned from parser. +} + +func (e *ErrParse) Error() string { + return fmt.Sprintf("parse failed at %s: %s", e.Pos, e.Err) +} diff --git a/src/vendor/github.com/nickng/bibtex/lexer.go b/src/vendor/github.com/nickng/bibtex/lexer.go new file mode 100644 index 0000000..4b9d1c6 --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/lexer.go @@ -0,0 +1,38 @@ +//go:generate goyacc -p bibtex -o bibtex.y.go bibtex.y + +package bibtex + +import ( + "fmt" + "io" +) + +// lexer for bibtex. +type lexer struct { + scanner *scanner + ParseErrors []error // Parse errors from yacc + Errors []error // Other errors +} + +// newLexer returns a new yacc-compatible lexer. +func newLexer(r io.Reader) *lexer { + return &lexer{ + scanner: newScanner(r), + } +} + +// Lex is provided for yacc-compatible parser. +func (l *lexer) Lex(yylval *bibtexSymType) int { + token, strval, err := l.scanner.Scan() + if err != nil { + l.Errors = append(l.Errors, fmt.Errorf("%w at %s", err, l.scanner.pos)) + return int(0) + } + yylval.strval = strval + return int(token) +} + +// Error handles error. +func (l *lexer) Error(err string) { + l.ParseErrors = append(l.ParseErrors, &ErrParse{Err: err, Pos: l.scanner.pos}) +} diff --git a/src/vendor/github.com/nickng/bibtex/scanner.go b/src/vendor/github.com/nickng/bibtex/scanner.go new file mode 100644 index 0000000..73d4f8d --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/scanner.go @@ -0,0 +1,237 @@ +package bibtex + +import ( + "bufio" + "bytes" + "io" + "strconv" + "strings" +) + +var parseField bool + +// scanner is a lexical scanner +type scanner struct { + commentMode bool + r *bufio.Reader + pos tokenPos +} + +// newScanner returns a new instance of scanner. +func newScanner(r io.Reader) *scanner { + return &scanner{r: bufio.NewReader(r), pos: tokenPos{Char: 0, Lines: []int{}}} +} + +// read reads the next rune from the buffered reader. +// Returns the rune(0) if an error occurs (or io.eof is returned). +func (s *scanner) read() rune { + ch, _, err := s.r.ReadRune() + if err != nil { + return eof + } + if ch == '\n' { + s.pos.Lines = append(s.pos.Lines, s.pos.Char) + s.pos.Char = 0 + } else { + s.pos.Char++ + } + return ch +} + +// unread places the previously read rune back on the reader. +func (s *scanner) unread() { + _ = s.r.UnreadRune() + if s.pos.Char == 0 { + s.pos.Char = s.pos.Lines[len(s.pos.Lines)-1] + s.pos.Lines = s.pos.Lines[:len(s.pos.Lines)-1] + } else { + s.pos.Char-- + } +} + +// Scan returns the next token and literal value. +func (s *scanner) Scan() (tok token, lit string, err error) { + ch := s.read() + if isWhitespace(ch) { + s.ignoreWhitespace() + ch = s.read() + } + if isAlphanum(ch) { + s.unread() + return s.scanIdent() + } + switch ch { + case eof: + return 0, "", nil + case '@': + return tATSIGN, string(ch), nil + case ':': + return tCOLON, string(ch), nil + case ',': + parseField = false // reset parseField if reached end of field. + return tCOMMA, string(ch), nil + case '=': + parseField = true // set parseField if = sign outside quoted or ident. + return tEQUAL, string(ch), nil + case '"': + tok, lit := s.scanQuoted() + return tok, lit, nil + case '{': + if parseField { + return s.scanBraced() + } + // If we're reading a comment, return everything after { + // to the next @-sign (exclusive) + if s.commentMode { + s.unread() + commentBodyTok, commentBody := s.scanCommentBody() + return commentBodyTok, commentBody, nil + } + return tLBRACE, string(ch), nil + case '}': + if parseField { // reset parseField if reached end of entry. + parseField = false + } + return tRBRACE, string(ch), nil + case '#': + return tPOUND, string(ch), nil + case ' ': + s.ignoreWhitespace() + } + return tILLEGAL, string(ch), nil +} + +// scanIdent categorises a string to one of three categories. +func (s *scanner) scanIdent() (tok token, lit string, err error) { + switch ch := s.read(); ch { + case '"': + tok, lit := s.scanQuoted() + return tok, lit, nil + case '{': + return s.scanBraced() + default: + s.unread() // Not open quote/brace. + tok, lit := s.scanBare() + return tok, lit, nil + } +} + +func (s *scanner) scanBare() (token, string) { + var buf bytes.Buffer + for { + if ch := s.read(); ch == eof { + break + } else if !isAlphanum(ch) && !isBareSymbol(ch) || isWhitespace(ch) { + s.unread() + break + } else { + _, _ = buf.WriteRune(ch) + } + } + str := buf.String() + if strings.ToLower(str) == "comment" { + s.commentMode = true + return tCOMMENT, str + } else if strings.ToLower(str) == "preamble" { + return tPREAMBLE, str + } else if strings.ToLower(str) == "string" { + return tSTRING, str + } else if _, err := strconv.Atoi(str); err == nil && parseField { // Special case for numeric + return tIDENT, str + } + return tBAREIDENT, str +} + +// scanBraced parses a braced string, like {this}. +func (s *scanner) scanBraced() (token, string, error) { + var buf bytes.Buffer + var macro bool + brace := 1 + for { + if ch := s.read(); ch == eof { + break + } else if ch == '\\' { + _, _ = buf.WriteRune(ch) + macro = true + } else if ch == '{' { + _, _ = buf.WriteRune(ch) + brace++ + } else if ch == '}' { + brace-- + macro = false + if brace == 0 { // Balances open brace. + return tIDENT, buf.String(), nil + } + _, _ = buf.WriteRune(ch) + } else if ch == '@' { + if macro { + _, _ = buf.WriteRune(ch) + } else { + return token(0), buf.String(), ErrUnexpectedAtsign + } + } else if isWhitespace(ch) { + _, _ = buf.WriteRune(ch) + macro = false + } else { + _, _ = buf.WriteRune(ch) + } + } + return tILLEGAL, buf.String(), nil +} + +// scanQuoted parses a quoted string, like "this". +func (s *scanner) scanQuoted() (token, string) { + var buf bytes.Buffer + brace := 0 + for { + if ch := s.read(); ch == eof { + break + } else if ch == '{' { + brace++ + } else if ch == '}' { + brace-- + } else if ch == '"' { + if brace == 0 { // Matches open quote, unescaped + return tIDENT, buf.String() + } + _, _ = buf.WriteRune(ch) + } else { + _, _ = buf.WriteRune(ch) + } + } + return tILLEGAL, buf.String() +} + +// skipCommentBody is a scan method used for reading bibtex +// comment item by reading all runes until the next @. +// +// e.g. +// @comment{...anything can go here even if braces are unbalanced@ +// comment body string will be "...anything can go here even if braces are unbalanced" +func (s *scanner) scanCommentBody() (token, string) { + var buf bytes.Buffer + for { + if ch := s.read(); ch == eof { + break + } else if ch == '@' { + s.unread() + break + } else { + _, _ = buf.WriteRune(ch) + } + } + s.commentMode = false + return tCOMMENTBODY, buf.String() +} + +// ignoreWhitespace consumes the current rune and all contiguous whitespace. +func (s *scanner) ignoreWhitespace() { + for { + if ch := s.read(); ch == eof { + break + } else if !isWhitespace(ch) { + s.unread() + break + } + } +} diff --git a/src/vendor/github.com/nickng/bibtex/token.go b/src/vendor/github.com/nickng/bibtex/token.go new file mode 100644 index 0000000..17c451e --- /dev/null +++ b/src/vendor/github.com/nickng/bibtex/token.go @@ -0,0 +1,55 @@ +package bibtex + +import ( + "fmt" + "strings" +) + +// Lexer token. +type token int + +const ( + // tILLEGAL stands for an invalid token. + tILLEGAL token = iota +) + +var eof = rune(0) + +// tokenPos is a pair of coordinate to identify start of token. +type tokenPos struct { + Char int + Lines []int +} + +func (p tokenPos) String() string { + return fmt.Sprintf("%d:%d", len(p.Lines)+1, p.Char) +} + +func isWhitespace(ch rune) bool { + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' +} + +func isAlpha(ch rune) bool { + return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') +} + +func isDigit(ch rune) bool { + return ('0' <= ch && ch <= '9') +} + +func isAlphanum(ch rune) bool { + return isAlpha(ch) || isDigit(ch) +} + +func isBareSymbol(ch rune) bool { + return strings.ContainsRune("-_:./+", ch) +} + +// isSymbol returns true if ch is a valid symbol +func isSymbol(ch rune) bool { + return strings.ContainsRune("!?&*+-./:;<>[]^_`|~@", ch) +} + +func isOpenQuote(ch rune) bool { + return ch == '{' || ch == '"' +} diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt new file mode 100644 index 0000000..f1b35c8 --- /dev/null +++ b/src/vendor/modules.txt @@ -0,0 +1,3 @@ +# github.com/nickng/bibtex v1.3.0 +## explicit; go 1.18 +github.com/nickng/bibtex From 1eeb1267b3e8e4b055a314d3990c8ef354e45815 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Sat, 9 Mar 2024 15:54:30 -0600 Subject: [PATCH 2/7] Add workflows to build and deploy. This commit adds two GitHub workflows that build and deploy CensorBib. The "build" workflow is run on every push and verifies the validity of the .bib file -- as seen by our build tool and *not* by LaTeX! The "deploy" workflow is run whenenver a branch is merged into master, at which point CensorBib is automatically built and deployed via GitHub pages. This means has two advantages: * First, I no longer need to manually build and deploy CensorBib, and can point the domain censorbib.nymity.ch to GitHub pages. * Second, CensorBib will also be available under https://nullhypothesis.github.io/censorbib/ which may be helpful to users for who my domain is blocked. --- .github/workflows/build.yaml | 14 +++++++++ .github/workflows/deploy-website.yaml | 28 ++++++++++++++++++ {img => assets}/author-icon.svg | 0 {img => assets}/author-reverse-icon.svg | 0 {img => assets}/bibtex-icon.svg | 0 {img => assets}/cache-icon.svg | 0 {img => assets}/code-icon.svg | 0 {img => assets}/donate-icon.svg | 0 {img => assets}/file-icon.svg | 0 {img => assets}/link-icon.svg | 0 {img => assets}/lock-icon.svg | 0 {img => assets}/pdf-icon.svg | 0 .../research-power-tools-cover.jpg | Bin {img => assets}/update-icon.svg | 0 {img => assets}/year-icon.svg | 0 {img => assets}/year-reverse-icon.svg | 0 docs/index.html | 1 + 17 files changed, 43 insertions(+) create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/deploy-website.yaml rename {img => assets}/author-icon.svg (100%) rename {img => assets}/author-reverse-icon.svg (100%) rename {img => assets}/bibtex-icon.svg (100%) rename {img => assets}/cache-icon.svg (100%) rename {img => assets}/code-icon.svg (100%) rename {img => assets}/donate-icon.svg (100%) rename {img => assets}/file-icon.svg (100%) rename {img => assets}/link-icon.svg (100%) rename {img => assets}/lock-icon.svg (100%) rename {img => assets}/pdf-icon.svg (100%) rename {img => assets}/research-power-tools-cover.jpg (100%) rename {img => assets}/update-icon.svg (100%) rename {img => assets}/year-icon.svg (100%) rename {img => assets}/year-reverse-icon.svg (100%) create mode 100644 docs/index.html diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..3682ba5 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,14 @@ +name: Build +on: push + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build + run: | + go build -C src -o ../compiler + ./compiler -path references.bib > /dev/null diff --git a/.github/workflows/deploy-website.yaml b/.github/workflows/deploy-website.yaml new file mode 100644 index 0000000..919411a --- /dev/null +++ b/.github/workflows/deploy-website.yaml @@ -0,0 +1,28 @@ +name: Deploy website +on: + push: + branches: + main + +permissions: + contents: write + +jobs: + deploy-website: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install and Build + run: | + go build -C src -o ../compiler + mkdir build + mv assets build + ./compiler -path references.bib > build/index.html + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + # Must be identical to where we wrote the HTML to. + folder: build diff --git a/img/author-icon.svg b/assets/author-icon.svg similarity index 100% rename from img/author-icon.svg rename to assets/author-icon.svg diff --git a/img/author-reverse-icon.svg b/assets/author-reverse-icon.svg similarity index 100% rename from img/author-reverse-icon.svg rename to assets/author-reverse-icon.svg diff --git a/img/bibtex-icon.svg b/assets/bibtex-icon.svg similarity index 100% rename from img/bibtex-icon.svg rename to assets/bibtex-icon.svg diff --git a/img/cache-icon.svg b/assets/cache-icon.svg similarity index 100% rename from img/cache-icon.svg rename to assets/cache-icon.svg diff --git a/img/code-icon.svg b/assets/code-icon.svg similarity index 100% rename from img/code-icon.svg rename to assets/code-icon.svg diff --git a/img/donate-icon.svg b/assets/donate-icon.svg similarity index 100% rename from img/donate-icon.svg rename to assets/donate-icon.svg diff --git a/img/file-icon.svg b/assets/file-icon.svg similarity index 100% rename from img/file-icon.svg rename to assets/file-icon.svg diff --git a/img/link-icon.svg b/assets/link-icon.svg similarity index 100% rename from img/link-icon.svg rename to assets/link-icon.svg diff --git a/img/lock-icon.svg b/assets/lock-icon.svg similarity index 100% rename from img/lock-icon.svg rename to assets/lock-icon.svg diff --git a/img/pdf-icon.svg b/assets/pdf-icon.svg similarity index 100% rename from img/pdf-icon.svg rename to assets/pdf-icon.svg diff --git a/img/research-power-tools-cover.jpg b/assets/research-power-tools-cover.jpg similarity index 100% rename from img/research-power-tools-cover.jpg rename to assets/research-power-tools-cover.jpg diff --git a/img/update-icon.svg b/assets/update-icon.svg similarity index 100% rename from img/update-icon.svg rename to assets/update-icon.svg diff --git a/img/year-icon.svg b/assets/year-icon.svg similarity index 100% rename from img/year-icon.svg rename to assets/year-icon.svg diff --git a/img/year-reverse-icon.svg b/assets/year-reverse-icon.svg similarity index 100% rename from img/year-reverse-icon.svg rename to assets/year-reverse-icon.svg diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/docs/index.html @@ -0,0 +1 @@ +hello world From e81081368173a251fefd0fc1203ae324780e64e1 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Sat, 9 Mar 2024 22:33:50 -0600 Subject: [PATCH 3/7] Update README. Remove now-obsolete text and add contribution guidelines. --- README.md | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 50c8ba7..9ff1fa7 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,19 @@ -Overview --------- +# CensorBib -This repository contains the BibTeX file and HTML templates that form the +This repository contains the +[BibTeX file](references.bib) +and +[corresponding tooling](src/) +that powers the [Internet Censorship Bibliography](https://censorbib.nymity.ch). +CensorBib is also available via +[GitHub pages](https://NullHypothesis.github.io/censorbib/) +in case the primary domain is inaccessible to you. -Build it --------- +## Contribute -You first need [`bibliogra.py`](https://github.com/NullHypothesis/bibliograpy) -to turn the BibTeX file into an HTML bibliography. +To contribute, please create a pull request that adds a new paper or +improves an existing one. -Then, run the following commands to write the bibliography to `OUTPUT_DIR`. - - $ ./fetch_pdfs.py references.bib OUTPUT_DIR - $ bibliogra.py -H header.tpl -F footer.tpl -f references.bib OUTPUT_DIR - -Acknowledgements ----------------- - -CensorBib uses [Font Awesome](https://fontawesome.com/license/free) icons -without modification. - -Feedback --------- - -Contact: Philipp Winter +> [!TIP] +> Try to mimic the style of existing BibTeX entries. The parser is strict! \ No newline at end of file From bd046c6a76988b2745fbb4044ee9287b3e188cb0 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Sat, 9 Mar 2024 22:38:01 -0600 Subject: [PATCH 4/7] Delete now-unused files. --- Makefile | 19 --- assets/author-icon.svg | 1 - assets/author-reverse-icon.svg | 1 - assets/donate-icon.svg | 1 - assets/file-icon.svg | 1 - assets/lock-icon.svg | 1 - assets/year-icon.svg | 1 - assets/year-reverse-icon.svg | 1 - docs/index.html | 1 - favicon.svg | 59 --------- fetch_pdfs.py | 100 --------------- footer.tpl | 7 -- header.tpl | 219 --------------------------------- open-access.svg | 99 --------------- 14 files changed, 511 deletions(-) delete mode 100644 Makefile delete mode 100644 assets/author-icon.svg delete mode 100644 assets/author-reverse-icon.svg delete mode 100644 assets/donate-icon.svg delete mode 100644 assets/file-icon.svg delete mode 100644 assets/lock-icon.svg delete mode 100644 assets/year-icon.svg delete mode 100644 assets/year-reverse-icon.svg delete mode 100644 docs/index.html delete mode 100644 favicon.svg delete mode 100755 fetch_pdfs.py delete mode 100644 footer.tpl delete mode 100644 header.tpl delete mode 100644 open-access.svg diff --git a/Makefile b/Makefile deleted file mode 100644 index 5166372..0000000 --- a/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -define LATEX_CODE -\\documentclass{article} -\\usepackage[top=2cm,bottom=2.5cm,left=2cm,right=2cm]{geometry} -\\usepackage[backend=biber]{biblatex} -\\addbibresource{references.bib} -\\begin{document} -\\nocite{*} -\\printbibliography -\\end{document} -endef - -export LATEX_CODE - -test: - TMP_FILE=$$(mktemp "censorbib-tmp-XXXXXXX.tex") ;\ - echo "$$LATEX_CODE" > "$$TMP_FILE" ;\ - pdflatex --interaction=batchmode "$${TMP_FILE%.tex}" ;\ - biber "$${TMP_FILE%.tex}" ;\ - rm "$${TMP_FILE%.tex}"* ; diff --git a/assets/author-icon.svg b/assets/author-icon.svg deleted file mode 100644 index d3b0a1f..0000000 --- a/assets/author-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/author-reverse-icon.svg b/assets/author-reverse-icon.svg deleted file mode 100644 index 8321cc9..0000000 --- a/assets/author-reverse-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/donate-icon.svg b/assets/donate-icon.svg deleted file mode 100644 index e13015b..0000000 --- a/assets/donate-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/file-icon.svg b/assets/file-icon.svg deleted file mode 100644 index c581e8f..0000000 --- a/assets/file-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/lock-icon.svg b/assets/lock-icon.svg deleted file mode 100644 index 19dfa22..0000000 --- a/assets/lock-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/year-icon.svg b/assets/year-icon.svg deleted file mode 100644 index 565af50..0000000 --- a/assets/year-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/year-reverse-icon.svg b/assets/year-reverse-icon.svg deleted file mode 100644 index 71dbcdd..0000000 --- a/assets/year-reverse-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 3b18e51..0000000 --- a/docs/index.html +++ /dev/null @@ -1 +0,0 @@ -hello world diff --git a/favicon.svg b/favicon.svg deleted file mode 100644 index 1e3ba38..0000000 --- a/favicon.svg +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - CB - - diff --git a/fetch_pdfs.py b/fetch_pdfs.py deleted file mode 100755 index 49967ae..0000000 --- a/fetch_pdfs.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2015 Philipp Winter -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -Fetch pdf and ps files in BibTeX file. -""" - -import os -import sys -import errno -import urllib.request - -import pybtex.database.input.bibtex as bibtex - - -def download_pdf(url, file_name): - """ - Download file and write it to given file name. - """ - - print("Now fetching %s" % url) - - try: - req = urllib.request.Request(url, headers={'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36"}) - fetched_file = urllib.request.urlopen(req) - except Exception as err: - print(url, err, file=sys.stderr) - return - - with open(file_name, "wb") as fd: - fd.write(fetched_file.read()) - - -def main(file_name, output_dir): - """ - Extract BibTeX key and URL, and then trigger file download. - """ - - parser = bibtex.Parser() - bibdata = parser.parse_file(file_name) - - # Create download directories. - - try: - os.makedirs(os.path.join(output_dir, "pdf")) - os.makedirs(os.path.join(output_dir, "ps")) - except OSError as exc: - if exc.errno == errno.EEXIST: - pass - else: - raise - - # Iterate over all BibTeX entries and trigger download if necessary. - - for bibkey in bibdata.entries: - - entry = bibdata.entries[bibkey] - url = entry.fields.get("url") - if url is None: - continue - - # Extract file name extension and see what we are dealing with. - - _, ext = os.path.splitext(url) - if ext: - ext = ext[1:] - - if ext not in ["pdf", "ps"]: - continue - - file_name = os.path.join(output_dir, ext, bibkey + ".%s" % ext) - if os.path.exists(file_name): - continue - - download_pdf(url, file_name) - - return 0 - - -if __name__ == "__main__": - - if len(sys.argv) != 3: - print("\nUsage: %s FILE_NAME OUTPUT_DIR\n" % sys.argv[0], - file=sys.stderr) - sys.exit(1) - - sys.exit(main(sys.argv[1], sys.argv[2])) diff --git a/footer.tpl b/footer.tpl deleted file mode 100644 index 10d642a..0000000 --- a/footer.tpl +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/header.tpl b/header.tpl deleted file mode 100644 index 3514449..0000000 --- a/header.tpl +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - The Internet censorship bibliography - - - - - - -
    - -
    - -
    -

    Selected Research Papers
    in Internet Censorship

    -
    - -
    - -
    - CensorBib is an online archive of selected research papers in the field - of Internet censorship. Most papers on CensorBib approach the topic - from a technical angle, by proposing designs that circumvent censorship - systems, or by measuring how censorship works. The icons next to each - paper make it easy to download, cite, and link to papers. If you think - I missed a paper, - let me know. - You can sort papers by - year, - reverse year (default), - author, and - reverse author. - Finally, the - net4people/bbs forum - has reading groups for many of the papers listed below. -
    - - - -
    - -
    - -
    - -
    -
    -
    - -
    - Are you a researcher? If so, you may like my book - Research Power Tools. -
    -
    - -
    - -
    - - diff --git a/open-access.svg b/open-access.svg deleted file mode 100644 index 209cac0..0000000 --- a/open-access.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - -image/svg+xml \ No newline at end of file From 4651ad9c6714aec13a8ab300dcb2e382ad96906d Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Sun, 10 Mar 2024 08:20:34 -0500 Subject: [PATCH 5/7] Ask contributors to make PRs. --- src/header.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/header.go b/src/header.go index 0376f86..cf8c156 100644 --- a/src/header.go +++ b/src/header.go @@ -171,12 +171,9 @@ const headerTemplate = ` systems, or by measuring how censorship works. The icons next to each paper make it easy to download, cite, and link to papers. If you think I missed a paper, - let me know. - You can sort papers by - year, - reverse year (default), - author, and - reverse author. + + make a pull request + . Finally, the net4people/bbs forum has reading groups for many of the papers listed below. From 14738b9abe2e22d2ef5d674244a05f1fef0a85fd Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Sun, 10 Mar 2024 09:14:21 -0500 Subject: [PATCH 6/7] Brighten text background. The background of the text boxes is slightly darker than it should be. --- src/header.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/header.go b/src/header.go index cf8c156..38a79f6 100644 --- a/src/header.go +++ b/src/header.go @@ -38,7 +38,7 @@ const headerTemplate = ` ul { border-radius: 10px; border:1px solid #c0c0c0; - background: #efefef; + background: #f5f5f5; box-shadow: 2px 2px 5px #bbb; } a:link { @@ -97,7 +97,7 @@ const headerTemplate = ` } #left-header { flex: 4; - background: #efefef; + background: #f5f5f5; margin-right: 0.5em; border-radius: 10px; border: 1px solid #c0c0c0; @@ -106,7 +106,7 @@ const headerTemplate = ` } #right-header { flex: 1; - background: #efefef; + background: #f5f5f5; margin-left: 0.5em; background: #333 url('assets/research-power-tools-cover.jpg') no-repeat; background-size: 100%; From 3bcd6f34700613cf24332ed5691f08c91d2d9059 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Sun, 10 Mar 2024 09:33:20 -0500 Subject: [PATCH 7/7] Add .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86a7c8e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +compiler