summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2025-06-06 01:32:41 +0200
committerThomas Voss <mail@thomasvoss.com> 2025-06-06 01:32:41 +0200
commit2c726e551d90d20bcd2d78545c369864cc9038e5 (patch)
tree520615a33c966c729e70e549e859a2c01dc4ba40
parent9379ea42a9b0afd1abf705bf3300f03d8bbe79f9 (diff)
Use CSV’s for mintages
-rw-r--r--.dir-locals.el9
-rw-r--r--.exrc9
-rw-r--r--.gitignore1
-rw-r--r--GNUmakefile9
-rw-r--r--cmd/mfmt/main.go189
-rw-r--r--cmd/mfmt/main_test.go140
-rw-r--r--cmd/mfmt/util_test.go35
-rw-r--r--contrib/emacs/mintage-mode.el26
-rw-r--r--contrib/vim/syntax/mintage.vim13
-rw-r--r--data/mintages/ad119
-rw-r--r--data/mintages/ad-c-circ.csv28
-rw-r--r--data/mintages/ad-c-nifc.csv24
-rw-r--r--data/mintages/ad-c-proof.csv24
-rw-r--r--data/mintages/ad-s-circ.csv13
-rw-r--r--data/mintages/ad-s-nifc.csv13
-rw-r--r--data/mintages/ad-s-proof.csv13
-rw-r--r--src/http.go13
-rw-r--r--src/mintage/mintage.go40
-rw-r--r--src/mintage/parser.go368
-rw-r--r--src/mintage/parser_test.go233
-rw-r--r--src/templates.go4
-rw-r--r--src/templates/coins-mintages.html.tmpl15
-rw-r--r--static/style.css4
23 files changed, 288 insertions, 1054 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
index d8584cc..8895f93 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -1,11 +1,4 @@
-((nil . ((eval . (progn
- (add-to-list
- 'load-path
- (expand-file-name
- "contrib/emacs"
- (locate-dominating-file default-directory "contrib/emacs")))
- (require 'mintage-mode)))))
- (go-ts-mode . ((comment-start . "/* ")
+((go-ts-mode . ((comment-start . "/* ")
(comment-end . " */")
(comment-continue . " ")
(require-final-newline . t)))
diff --git a/.exrc b/.exrc
index dca85d3..fdec142 100644
--- a/.exrc
+++ b/.exrc
@@ -1,23 +1,14 @@
-set runtimepath+=contrib/vim
-
function s:SaveExcursion(cmd)
let l:win = winsaveview()
silent execute 'keepjumps %!' .. a:cmd
call winrestview(l:win)
endfunction
-autocmd BufNewFile,BufRead */data/mintages/*
- \ setfiletype mintage
- \ | setlocal nowrap
autocmd BufNewFile,BufRead */cmd/exttmpl/*
\ setlocal makeprg=go\ build\ ./cmd/exttmpl
-autocmd BufNewFile,BufRead */cmd/mfmt/*
- \ setlocal makeprg=go\ build\ ./cmd/mfmt
autocmd FileType go autocmd BufWritePre <buffer>
\ call s:SaveExcursion('gofmt -s')
-autocmd FileType mintage autocmd BufWritePre <buffer>
- \ call s:SaveExcursion('./mfmt')
nnoremap <silent> gM :wall \| make all-i18n<CR>
nnoremap <silent> <LocalLeader>t :vimgrep /\CTODO/ **/*<CR>
diff --git a/.gitignore b/.gitignore
index 5f6c520..3a39bea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
# Binaries
euro-cash.eu
exttmpl
-mfmt
!cmd/*
diff --git a/GNUmakefile b/GNUmakefile
index 3c48127..62402b8 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -4,9 +4,8 @@ gofiles := $(shell find main.go src -name '*.go')
templates := $(shell find src/templates -name '*.tmpl')
exttmpl := $(wildcard cmd/exttmpl/*.go)
-mfmt := $(wildcard cmd/mfmt/*.go)
-all: euro-cash.eu exttmpl mfmt
+all: euro-cash.eu exttmpl
euro-cash.eu: $(cssfiles) $(templates) $(gofiles)
go build
@@ -19,9 +18,6 @@ all-i18n: exttmpl
exttmpl: $(exttmpl)
go build ./cmd/exttmpl
-mfmt: $(mfmt)
- go build ./cmd/mfmt
-
%.min.css: %.css
lightningcss -m $< -o $@
@@ -39,7 +35,6 @@ clean:
find . -type f \( \
-name euro-cash.eu \
-or -name exttmpl \
- -or -name mfmt \
-or -name '*.min.css' \
-or -name '*.tar.gz' \
- \) -delete
+ \) -delete \ No newline at end of file
diff --git a/cmd/mfmt/main.go b/cmd/mfmt/main.go
deleted file mode 100644
index c4e2277..0000000
--- a/cmd/mfmt/main.go
+++ /dev/null
@@ -1,189 +0,0 @@
-/* Simple formatter for mintage files. This is not perfect and doesn’t
- check for syntactic correctness, it’s just to get stuff aligned
- nicely. Maybe in the future I will construct a military-grade
- formatter. */
-
-package main
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "regexp"
- "strings"
-)
-
-const ws = " \t"
-
-var (
- rv int
-
- reMintageYear = regexp.MustCompile(`^\d{4}(-[^ \t]+)?`)
- reMintageRowC = regexp.MustCompile(`^[ \t]*(([0-9.]+|\?)[ \t]+){2}([0-9.]+|\?)$`)
- reMintageRowS = regexp.MustCompile(`^[ \t]*(([0-9.]+|\?)[ \t]+){7}([0-9.]+|\?)$`)
-)
-
-func main() {
- if len(os.Args) == 1 {
- mfmt("-", os.Stdin, os.Stdout)
- }
- for _, arg := range os.Args[1:] {
- f, err := os.OpenFile(arg, os.O_RDWR, 0)
- if err != nil {
- warn(err)
- continue
- }
- defer f.Close()
-
- buf := bytes.NewBuffer(make([]byte, 0, 8192))
- mfmt(arg, f, buf)
-
- if _, err = f.Seek(0, io.SeekStart); err != nil {
- warn(err)
- continue
- }
-
- if _, err = f.Write(buf.Bytes()); err != nil {
- warn(err)
- continue
- }
-
- if err = f.Truncate(int64(buf.Len())); err != nil {
- warn(err)
- }
- }
- os.Exit(rv)
-}
-
-func mfmt(file string, r io.Reader, w io.Writer) {
- var (
- buf [3]string
- bufsz int
- longestNums [8]int
- )
-
- scanner := bufio.NewScanner(r)
- for linenr := 1; scanner.Scan(); linenr++ {
- line := strings.Trim(scanner.Text(), ws)
-
- switch {
- case len(line) == 0, line[0] == '#':
- fmt.Fprintln(w, line)
- case reMintageRowS.MatchString(line):
- switch bufsz {
- case len(buf):
- bufsz = 0
- clear(longestNums[:])
- fallthrough
- default:
- setLongestNum(longestNums[:], line)
- buf[bufsz] = line
- if bufsz++; bufsz == len(buf) {
- fmtMintageRow(buf[0], longestNums[:], w)
- fmtMintageRow(buf[1], longestNums[:], w)
- fmtMintageRow(buf[2], longestNums[:], w)
- }
- }
- case reMintageRowC.MatchString(line):
- var ns [3]int
- setLongestNum(ns[:], line)
- fmtMintageRow(line, ns[:], w)
- case reMintageYear.MatchString(line):
- fmtMintageYear(line, w)
- default:
- warn(fmt.Sprintf("%s:%d: potential syntax error", file, linenr))
- fmt.Fprintln(w, line)
- }
- }
-}
-
-func setLongestNum(longest []int, line string) {
- for i, x := range strings.FieldsFunc(line, func(r rune) bool {
- return strings.ContainsRune(ws, r)
- }) {
- n := len(strings.ReplaceAll(x, ".", ""))
- n += (n - 1) / 3 // Thousands separators
- longest[i] = max(longest[i], n)
- }
-}
-
-func fmtMintageYear(line string, w io.Writer) {
- switch i := strings.IndexAny(line, ws); i {
- case -1:
- fmt.Fprintln(w, line)
- default:
- fmt.Fprintf(w, "%s %s\n", line[:i], strings.TrimLeft(line[i:], ws))
- }
-}
-
-func fmtMintageRow(line string, longest []int, w io.Writer) {
- tokens := strings.FieldsFunc(line, func(r rune) bool {
- return strings.ContainsRune(ws, r)
- })
-
- for i, tok := range tokens {
- s := formatMintage(tok)
-
- if i == 0 {
- fmt.Fprintf(w, "\t%*s", longest[i], s)
- } else {
- fmt.Fprintf(w, "%*s", longest[i]+1, s)
- }
- }
-
- fmt.Fprintln(w)
-}
-
-func formatMintage(s string) string {
- if s == "?" {
- return s
- }
-
- n := atoiWithDots(s)
- digits := intlen(n)
- dots := (digits - 1) / 3
- out := make([]byte, digits+dots)
-
- for i, j := len(out)-1, 0; ; i-- {
- out[i] = byte(n%10) + 48
- if n /= 10; n == 0 {
- return string(out)
- }
- if j++; j == 3 {
- i, j = i-1, 0
- out[i] = '.'
- }
- }
-}
-
-func intlen(v int) int {
- switch {
- case v == 0:
- return 1
- default:
- n := 0
- for x := v; x != 0; x /= 10 {
- n++
- }
- return n
- }
-}
-
-func atoiWithDots(s string) int {
- n := 0
- for _, ch := range s {
- if ch == '.' {
- continue
- }
- n = n*10 + int(ch) - '0'
- }
- return n
-}
-
-func warn(e any) {
- fmt.Fprintf(os.Stderr, "%s: %s\n", filepath.Base(os.Args[0]), e)
- rv = 1
-}
diff --git a/cmd/mfmt/main_test.go b/cmd/mfmt/main_test.go
deleted file mode 100644
index ef91473..0000000
--- a/cmd/mfmt/main_test.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package main
-
-import (
- "bytes"
- "testing"
-)
-
-func runTest(t *testing.T, in, out string, nilErr bool) {
-}
-
-func TestComplete(t *testing.T) {
- in := `
-2014
-60.000 60.000 860.000 860.000 860.000 340.000 511.843 360.000
-70.000 70.000 70.000 70.000 70.000 70.000 70.000 70.000
-3.000 3.000 3.000 3.000 3.000 3.000 3.000 3.000
-
-# Ref: https://example.com
-2015
- 0 0 0 0 0 0 0 1.072.400
-40.000 40.000 40.000 40.000 40.000 40.000 40.000 40.000
- ? ? ? ? ? ? ? ?
-
-2016
- 0 0 0 0 0 0 2.339.200 0
-35.000 35.000 35.000 35.000 35.000 35.000 35.000 35.000
- ? ? ? ? ? ? ? ?
-
-2017
-2.582.395 1.515.000 2.191.421 1.103.000 1.213.000 968.800 17.000 794.588
-22.000 22.000 22.000 22.000 22.000 22.000 22.000 22.000
- ? ? ? ? ? ? ? ?
-
-2018
-2.430.000 2.550.000 1.800.000 980.000 1.014.000 890.000 0 868.000
-20.000 20.000 20.000 20.000 20.000 20.000 20.000 20.000
- ? ? ? ? ? ? ? ?
-
-2019
-2.447.000 1.727.000 2.100.000 1.610.000 1.570.000 930.000 0 1.058.310
-15.000 15.000 15.000 15.000 15.000 15.000 15.000 15.000
- ? ? ? ? ? ? ? ?
-
-2020
- 0 0 0 860.000 175.000 740.000 0 1.500.000
-12.000 12.000 12.000 12.000 12.000 12.000 12.000 12.000
- ? ? ? ? ? ? ? ?
-
-2021
- 200.000 700.000 0 1.400.000 1.420.000 600.000 50.000 1.474.500
-10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
- ? ? ? ? ? ? ? ?
-
-2022
- 700.000 450.000 400.000 700.000 700.000 380.000 0 1.708.000
-10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
- ? ? ? ? ? ? ? ?
-
-2023
- 0 0 0 0 0 0 0 2.075.250
-10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
- ? ? ? ? ? ? ? ?
-
-2024
- ? ? ? ? ? ? ? ?
- ? ? ? ? ? ? ? ?
- ? ? ? ? ? ? ? ?
-
- 2014 "Council of Europe"
- ? ? ?
-`
- out := `
-2014
- 60.000 60.000 860.000 860.000 860.000 340.000 511.843 360.000
- 70.000 70.000 70.000 70.000 70.000 70.000 70.000 70.000
- 3.000 3.000 3.000 3.000 3.000 3.000 3.000 3.000
-
-# Ref: https://example.com
-2015
- 0 0 0 0 0 0 0 1.072.400
- 40.000 40.000 40.000 40.000 40.000 40.000 40.000 40.000
- ? ? ? ? ? ? ? ?
-
-2016
- 0 0 0 0 0 0 2.339.200 0
- 35.000 35.000 35.000 35.000 35.000 35.000 35.000 35.000
- ? ? ? ? ? ? ? ?
-
-2017
- 2.582.395 1.515.000 2.191.421 1.103.000 1.213.000 968.800 17.000 794.588
- 22.000 22.000 22.000 22.000 22.000 22.000 22.000 22.000
- ? ? ? ? ? ? ? ?
-
-2018
- 2.430.000 2.550.000 1.800.000 980.000 1.014.000 890.000 0 868.000
- 20.000 20.000 20.000 20.000 20.000 20.000 20.000 20.000
- ? ? ? ? ? ? ? ?
-
-2019
- 2.447.000 1.727.000 2.100.000 1.610.000 1.570.000 930.000 0 1.058.310
- 15.000 15.000 15.000 15.000 15.000 15.000 15.000 15.000
- ? ? ? ? ? ? ? ?
-
-2020
- 0 0 0 860.000 175.000 740.000 0 1.500.000
- 12.000 12.000 12.000 12.000 12.000 12.000 12.000 12.000
- ? ? ? ? ? ? ? ?
-
-2021
- 200.000 700.000 0 1.400.000 1.420.000 600.000 50.000 1.474.500
- 10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
- ? ? ? ? ? ? ? ?
-
-2022
- 700.000 450.000 400.000 700.000 700.000 380.000 0 1.708.000
- 10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
- ? ? ? ? ? ? ? ?
-
-2023
- 0 0 0 0 0 0 0 2.075.250
- 10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
- ? ? ? ? ? ? ? ?
-
-2024
- ? ? ? ? ? ? ? ?
- ? ? ? ? ? ? ? ?
- ? ? ? ? ? ? ? ?
-
-2014 "Council of Europe"
- ? ? ?
-`
-
- r := bytes.NewReader([]byte(in))
- w := new(bytes.Buffer)
-
- mfmt("-", r, w)
- if w.String() != out {
- t.Fatalf(`Expected w.String()="%s"; got "%s"`, out, w.String())
- }
-}
diff --git a/cmd/mfmt/util_test.go b/cmd/mfmt/util_test.go
deleted file mode 100644
index 8366a50..0000000
--- a/cmd/mfmt/util_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-/* This file contains tests for utility functions used in the mfmt
- binary. Tests of the actual application are in main_test.go. */
-
-package main
-
-import "testing"
-
-func TestFormatInt(t *testing.T) {
- for _, x := range [...]struct{ x, y string }{
- {"?", "?"},
- {"0", "0"},
- {"123", "123"},
- {"81758", "81.758"},
- {"752759237528", "752.759.237.528"},
- } {
- s := formatMintage(x.x)
- if s != x.y {
- t.Fatalf(`Expected s="%s"; got "%s"`, x.y, s)
- }
- }
-}
-
-func TestIntLen(t *testing.T) {
- for _, x := range [...]struct{ x, y int }{
- {0, len("0")},
- {123, len("123")},
- {81758, len("81758")},
- {752759237528, len("752759237528")},
- } {
- n := intlen(x.x)
- if n != x.y {
- t.Fatalf("Expected n=%d; got %d", x.y, n)
- }
- }
-}
diff --git a/contrib/emacs/mintage-mode.el b/contrib/emacs/mintage-mode.el
deleted file mode 100644
index 8cfe036..0000000
--- a/contrib/emacs/mintage-mode.el
+++ /dev/null
@@ -1,26 +0,0 @@
-;;; mintage-mode.el --- Major mode for mintage data files -*- lexical-binding: t; -*-
-
-(defconst mintage--number (rx (or digit ?.)))
-(defconst mintage--string (rx "\"" (*? (not "\"")) "\""))
-(defconst mintage--unknown (rx ??))
-(defconst mintage--comment (rx bol (* space) ?# (* any)))
-(defconst mintage--year (rx bol (= 4 digit) (? ?- (+ (not space)))))
-
-(defun mintage--font-lock-keywords ()
- `((,mintage--year . font-lock-constant-face)
- (,mintage--number . 'font-lock-number-face)
- (,mintage--string . font-lock-string-face)
- (,mintage--comment . font-lock-comment-face)
- (,mintage--unknown . font-lock-warning-face)))
-
-;;;###autoload
-(add-to-list 'auto-mode-alist (cons "mintages/[a-z]\\{2\\}\\'" 'mintage-mode))
-
-;;;###autoload
-(define-derived-mode mintage-mode conf-mode "Mintage"
- "Major mode for editing euro-cash.eu mintage files"
- (setq comment-start "#"
- comment-end "")
- (setq font-lock-defaults '(mintage--font-lock-keywords)))
-
-(provide 'mintage-mode)
diff --git a/contrib/vim/syntax/mintage.vim b/contrib/vim/syntax/mintage.vim
deleted file mode 100644
index 2b03b41..0000000
--- a/contrib/vim/syntax/mintage.vim
+++ /dev/null
@@ -1,13 +0,0 @@
-if exists('b:current_syntax')
- finish
-endif
-let b:current_syntax = 'mintage'
-
-syntax match Comment /^\s*#.*/
-syntax match Number /[0-9.]\+/
-syntax match Identifier /\v\d{4}(-\S+)?/
-syntax match String /"[^"]\{-}"/
-
-
-" ‘Todo’ is semantically a better syntax group, but it looks bad
-syntax match Error /?/
diff --git a/data/mintages/ad b/data/mintages/ad
deleted file mode 100644
index ee08991..0000000
--- a/data/mintages/ad
+++ /dev/null
@@ -1,119 +0,0 @@
-2014
- 60.000 60.000 860.000 860.000 860.000 340.000 511.843 360.000
- 70.000 70.000 70.000 70.000 70.000 70.000 70.000 70.000
- 3.000 3.000 3.000 3.000 3.000 3.000 3.000 3.000
-
-2015
- 0 0 0 0 0 0 0 1.072.400
- 40.000 40.000 40.000 40.000 40.000 40.000 40.000 40.000
- ? ? ? ? ? ? ? ?
-
-2016
- 0 0 0 0 0 0 2.339.200 0
- 35.000 35.000 35.000 35.000 35.000 35.000 35.000 35.000
- ? ? ? ? ? ? ? ?
-
-2017
- 2.582.395 1.515.000 2.191.421 1.103.000 1.213.000 968.800 17.000 794.588
- 22.000 22.000 22.000 22.000 22.000 22.000 22.000 22.000
- ? ? ? ? ? ? ? ?
-
-2018
- 2.430.000 2.550.000 1.800.000 980.000 1.014.000 890.000 0 868.000
- 20.000 20.000 20.000 20.000 20.000 20.000 20.000 20.000
- ? ? ? ? ? ? ? ?
-
-2019
- 2.447.000 1.727.000 2.100.000 1.610.000 1.570.000 930.000 0 1.058.310
- 15.000 15.000 15.000 15.000 15.000 15.000 15.000 15.000
- ? ? ? ? ? ? ? ?
-
-2020
- 0 0 0 860.000 175.000 740.000 0 1.500.000
- 12.000 12.000 12.000 12.000 12.000 12.000 12.000 12.000
- ? ? ? ? ? ? ? ?
-
-2021
- 200.000 700.000 0 1.400.000 1.420.000 600.000 50.000 1.474.500
- 10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
- ? ? ? ? ? ? ? ?
-
-2022
- 700.000 450.000 400.000 700.000 700.000 380.000 0 1.708.000
- 10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
- ? ? ? ? ? ? ? ?
-
-2023
- 0 0 0 0 0 0 0 2.075.250
- 10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
- ? ? ? ? ? ? ? ?
-
-2024
- ? ? ? ? ? ? ? ?
- ? ? ? ? ? ? ? ?
- ? ? ? ? ? ? ? ?
-
-2014 "20 Years in the Council of Europe"
- ? ? ?
-
-2015 "30 Years of Political Rights"
- ? ? ?
-
-2015 "25th Anniversary of the Customs Agreement"
- ? ? ?
-
-2016 "25th Anniversary of the Radio and Television of Andorra"
- ? ? ?
-
-2016 "150th Anniversary of the New Reform of 1866"
- ? ? ?
-
-2017 "100th Anniversary of the National Anthem of Andorra"
- ? ? ?
-
-2017 "The Pyrenean Country"
- ? ? ?
-
-2018 "70th Anniversary of the Universal Declaration of Human Rights"
- ? ? ?
-
-2018 "25th Anniversary of the Andorran Constitution"
- ? ? ?
-
-2019 "600th Anniversary of the Council of the Land"
- ? ? ?
-
-2019 "2019 Alpine Ski World Cup Finals"
- ? ? ?
-
-2020 "50 Years of Universal Female Suffrage"
- ? ? ?
-
-2020 "The 27th Ibero-American Summit"
- ? ? ?
-
-2021 "Taking Care of Our Seniors"
- ? ? ?
-
-2021 "Our Lady of Meritxell"
- ? ? ?
-
-2022 "The Legend of Charlemagne"
- ? ? ?
-
-2022 "10th Anniversary of the Monetary Agreement with the EU"
- ? ? ?
-
-2023 "Summer Solstice Fire Festivals in the Pyrenees"
- ? ? ?
-
-2023 "30 Years of United Nations Membership"
- ? ? ?
-
-# TODO: Find the official name of this coin
-2024 "100 Years of Skiing in Andorra"
- ? ? ?
-
-# TODO: Find the official name of this coin
-2024 "UCI Mountain Bike World Cup"
- ? ? ?
diff --git a/data/mintages/ad-c-circ.csv b/data/mintages/ad-c-circ.csv
new file mode 100644
index 0000000..3482b8e
--- /dev/null
+++ b/data/mintages/ad-c-circ.csv
@@ -0,0 +1,28 @@
+Year,Name,Mintmark,Mintage
+2014,20 Years in the Council of Europe,,
+2015,30 Years of Political Rights,,
+2015,25th Anniversary of the Customs Agreement,,
+2016,25th Anniversary of the Radio and Television of Andorra,,
+2016,150th Anniversary of the New Reform of 1866,,
+2017,100th Anniversary of the National Anthem of Andorra,,
+2017,The Pyrenean Country,,
+2018,70th Anniversary of the Universal Declaration of Human Rights,,
+2018,25th Anniversary of the Andorran Constitution,,
+2019,600th Anniversary of the Council of the Land,,
+2019,2019 Alpine Ski World Cup Finals,,
+2020,50 Years of Universal Female Suffrage,,
+2020,The 27th Ibero-American Summit,,
+2021,Taking Care of Our Seniors,,
+2021,Our Lady of Meritxell,,
+2022,The Legend of Charlemagne,,
+2022,10th Anniversary of the Monetary Agreement with the EU,,
+2023,Summer Solstice Fire Festivals in the Pyrenees,,
+2023,30 Years of United Nations Membership,,
+# TODO: Find the official name of this coin
+2024,100 Years of Skiing in Andorra,,
+# TODO: Find the official name of this coin
+2024,UCI Mountain Bike World Cup,,
+# TODO: Find the official name of this coin
+2025,Bearded Vulture,,
+# TODO: Find the official name of this coin
+2025,Games of the Small States of Europe,, \ No newline at end of file
diff --git a/data/mintages/ad-c-nifc.csv b/data/mintages/ad-c-nifc.csv
new file mode 100644
index 0000000..f230855
--- /dev/null
+++ b/data/mintages/ad-c-nifc.csv
@@ -0,0 +1,24 @@
+Year,Name,Mintmark,Mintage
+2014,20 Years in the Council of Europe,,
+2015,30 Years of Political Rights,,
+2015,25th Anniversary of the Customs Agreement,,
+2016,25th Anniversary of the Radio and Television of Andorra,,
+2016,150th Anniversary of the New Reform of 1866,,
+2017,100th Anniversary of the National Anthem of Andorra,,
+2017,The Pyrenean Country,,
+2018,70th Anniversary of the Universal Declaration of Human Rights,,
+2018,25th Anniversary of the Andorran Constitution,,
+2019,600th Anniversary of the Council of the Land,,
+2019,2019 Alpine Ski World Cup Finals,,
+2020,50 Years of Universal Female Suffrage,,
+2020,The 27th Ibero-American Summit,,
+2021,Taking Care of Our Seniors,,
+2021,Our Lady of Meritxell,,
+2022,The Legend of Charlemagne,,
+2022,10th Anniversary of the Monetary Agreement with the EU,,
+2023,Summer Solstice Fire Festivals in the Pyrenees,,
+2023,30 Years of United Nations Membership,,
+2024,100 Years of Skiing in Andorra,,
+2024,UCI Mountain Bike World Cup,,
+2025,Bearded Vulture,,
+2025,Games of the Small States of Europe,, \ No newline at end of file
diff --git a/data/mintages/ad-c-proof.csv b/data/mintages/ad-c-proof.csv
new file mode 100644
index 0000000..f230855
--- /dev/null
+++ b/data/mintages/ad-c-proof.csv
@@ -0,0 +1,24 @@
+Year,Name,Mintmark,Mintage
+2014,20 Years in the Council of Europe,,
+2015,30 Years of Political Rights,,
+2015,25th Anniversary of the Customs Agreement,,
+2016,25th Anniversary of the Radio and Television of Andorra,,
+2016,150th Anniversary of the New Reform of 1866,,
+2017,100th Anniversary of the National Anthem of Andorra,,
+2017,The Pyrenean Country,,
+2018,70th Anniversary of the Universal Declaration of Human Rights,,
+2018,25th Anniversary of the Andorran Constitution,,
+2019,600th Anniversary of the Council of the Land,,
+2019,2019 Alpine Ski World Cup Finals,,
+2020,50 Years of Universal Female Suffrage,,
+2020,The 27th Ibero-American Summit,,
+2021,Taking Care of Our Seniors,,
+2021,Our Lady of Meritxell,,
+2022,The Legend of Charlemagne,,
+2022,10th Anniversary of the Monetary Agreement with the EU,,
+2023,Summer Solstice Fire Festivals in the Pyrenees,,
+2023,30 Years of United Nations Membership,,
+2024,100 Years of Skiing in Andorra,,
+2024,UCI Mountain Bike World Cup,,
+2025,Bearded Vulture,,
+2025,Games of the Small States of Europe,, \ No newline at end of file
diff --git a/data/mintages/ad-s-circ.csv b/data/mintages/ad-s-circ.csv
new file mode 100644
index 0000000..fd05b7c
--- /dev/null
+++ b/data/mintages/ad-s-circ.csv
@@ -0,0 +1,13 @@
+Year,Mintmark,"€0,01","€0,02","€0,05","€0,10","€0,20","€0,50","€1,00","€2,00"
+2014,,60000,60000,860000,860000,860000,340000,511843,360000
+2015,,0,0,0,0,0,0,0,1072400
+2016,,0,0,0,0,0,0,2339200,0
+2017,,2582395,1515000,2191421,1103000,1213000,968800,17000,794588
+2018,,2430000,2550000,1800000,980000,1014000,890000,0,868000
+2019,,2447000,1727000,2100000,1610000,1570000,930000,0,1058310
+2020,,0,0,0,860000,175000,740000,0,1500000
+2021,,200000,700000,0,1400000,1420000,600000,50000,1474500
+2022,,700000,450000,400000,700000,700000,380000,0,1708000
+2023,,0,0,0,0,0,0,0,2075250
+2024,,0,900300,1950000,1000000,700000,500000,1050000,1601200
+2025,,,,,,,,, \ No newline at end of file
diff --git a/data/mintages/ad-s-nifc.csv b/data/mintages/ad-s-nifc.csv
new file mode 100644
index 0000000..dc8b452
--- /dev/null
+++ b/data/mintages/ad-s-nifc.csv
@@ -0,0 +1,13 @@
+Year,Mintmark,"€0,01","€0,02","€0,05","€0,10","€0,20","€0,50","€1,00","€2,00"
+2014,,70000,70000,70000,70000,70000,70000,70000,70000
+2015,,40000,40000,40000,40000,40000,40000,40000,40000
+2016,,35000,35000,35000,35000,35000,35000,35000,35000
+2017,,22000,22000,22000,22000,22000,22000,22000,22000
+2018,,20000,20000,20000,20000,20000,20000,20000,20000
+2019,,15000,15000,15000,15000,15000,15000,15000,15000
+2020,,12000,12000,12000,12000,12000,12000,12000,12000
+2021,,10500,10500,10500,10500,10500,10500,10500,10500
+2022,,10500,10500,10500,10500,10500,10500,10500,10500
+2023,,10500,10500,10500,10500,10500,10500,10500,10500
+2024,,,,,,,,,
+2025,,,,,,,,, \ No newline at end of file
diff --git a/data/mintages/ad-s-proof.csv b/data/mintages/ad-s-proof.csv
new file mode 100644
index 0000000..c8ed593
--- /dev/null
+++ b/data/mintages/ad-s-proof.csv
@@ -0,0 +1,13 @@
+Year,Mintmark,"€0,01","€0,02","€0,05","€0,10","€0,20","€0,50","€1,00","€2,00"
+2014,,,,,,,,,
+2015,,,,,,,,,
+2016,,,,,,,,,
+2017,,,,,,,,,
+2018,,,,,,,,,
+2019,,,,,,,,,
+2020,,,,,,,,,
+2021,,,,,,,,,
+2022,,,,,,,,,
+2023,,,,,,,,,
+2024,,,,,,,,,
+2025,,,,,,,,, \ No newline at end of file
diff --git a/src/http.go b/src/http.go
index 04c17de..eaaeb71 100644
--- a/src/http.go
+++ b/src/http.go
@@ -8,8 +8,6 @@ import (
"log"
"math"
"net/http"
- "os"
- "path/filepath"
"slices"
"strconv"
"strings"
@@ -139,15 +137,8 @@ func mintageHandler(next http.Handler) http.Handler {
td.Type = "circ"
}
- path := filepath.Join("data", "mintages", td.Code)
- f, err := os.Open(path)
- if err != nil {
- throwError(http.StatusInternalServerError, err, w, r)
- return
- }
- defer f.Close()
-
- td.Mintages, err = mintage.Parse(f, path)
+ var err error
+ td.Mintages, err = mintage.Parse(td.Code)
if err != nil {
throwError(http.StatusInternalServerError, err, w, r)
return
diff --git a/src/mintage/mintage.go b/src/mintage/mintage.go
new file mode 100644
index 0000000..cc94de7
--- /dev/null
+++ b/src/mintage/mintage.go
@@ -0,0 +1,40 @@
+package mintage
+
+type Data struct {
+ Standard []SRow
+ Commemorative []CRow
+}
+
+type SRow struct {
+ Year int
+ Mintmark string
+ Mintages [denoms]int
+}
+
+type CRow struct {
+ Year int
+ Name string
+ Mintmark string
+ Mintage int
+}
+
+const (
+ TypeCirc = iota
+ TypeNifc
+ TypeProof
+)
+
+const (
+ Unknown = -iota - 1
+ Invalid
+)
+
+const denoms = 8
+
+var cache map[string][3]Data = make(map[string][3]Data)
+
+func ClearCache(country string) {
+ if _, ok := cache[country]; ok {
+ delete(cache, country)
+ }
+}
diff --git a/src/mintage/parser.go b/src/mintage/parser.go
index 364b6e8..daeb05d 100644
--- a/src/mintage/parser.go
+++ b/src/mintage/parser.go
@@ -1,297 +1,153 @@
package mintage
import (
- "bufio"
- "fmt"
+ "encoding/csv"
"io"
+ "os"
+ "path/filepath"
"strconv"
- "strings"
- "time"
)
-type SyntaxError struct {
- expected, got string
- file string
- linenr int
-}
-
-func (e SyntaxError) Error() string {
- return fmt.Sprintf("%s:%d: syntax error: expected %s but got %s",
- e.file, e.linenr, e.expected, e.got)
-}
-
-type Data struct {
- Standard []SRow
- Commemorative []CRow
-}
+func Parse(country string) ([3]Data, error) {
+ if data, ok := cache[country]; ok {
+ return data, nil
+ }
-type SRow struct {
- Year int
- Mintmark string
- Mintages [typeCount][denoms]int
-}
+ var (
+ data [3]Data
+ err error
+ path = filepath.Join("data", "mintages", country)
+ )
-type CRow struct {
- Year int
- Mintmark string
- Name string
- Mintage [typeCount]int
+ data[TypeCirc].Standard, err = parseS(path + "-s-circ.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeNifc].Standard, err = parseS(path + "-s-nifc.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeProof].Standard, err = parseS(path + "-s-proof.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeCirc].Commemorative, err = parseC(path + "-c-circ.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeNifc].Commemorative, err = parseC(path + "-c-nifc.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeProof].Commemorative, err = parseC(path + "-c-proof.csv")
+ if err == nil {
+ cache[country] = data
+ }
+ return data, err
}
-const (
- TypeCirc = iota
- TypeNIFC
- TypeProof
- typeCount
-)
+func parseS(path string) ([]SRow, error) {
+ rows := make([]SRow, 0, 69) /* TODO: Compute number of rows */
-const (
- Unknown = -iota - 1
- Invalid
-)
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
-const (
- denoms = 8
- ws = " \t"
-)
+ r := csv.NewReader(f)
+ r.Comment = '#'
+ r.FieldsPerRecord = 10
+ r.ReuseRecord = true
-func Parse(r io.Reader, file string) (Data, error) {
- yearsSince := time.Now().Year() - 1999 + 1
- data := Data{
- Standard: make([]SRow, 0, yearsSince),
- Commemorative: make([]CRow, 0, yearsSince),
+ /* Skip header */
+ if _, err := r.Read(); err != nil {
+ return nil, err
}
- scanner := bufio.NewScanner(r)
- for linenr := 1; scanner.Scan(); linenr++ {
- line := strings.Trim(scanner.Text(), ws)
- if isBlankOrComment(line) {
- continue
+ for {
+ record, err := r.Read()
+ if err == io.EOF {
+ break
}
-
- if len(line) < 4 || !isNumeric(line[:4], false) {
- return Data{}, SyntaxError{
- expected: "4-digit year",
- got: line,
- linenr: linenr,
- file: file,
- }
+ if err != nil {
+ return nil, err
}
- var (
- commem bool
- mintmark string
- )
- year, _ := strconv.Atoi(line[:4])
- line = line[4:]
-
- if len(line) != 0 {
- if strings.ContainsRune(ws, rune(line[0])) {
- commem = true
- goto out
- }
- if line[0] != '-' {
- return Data{}, SyntaxError{
- expected: "end-of-line or mintmark",
- got: line,
- linenr: linenr,
- file: file,
- }
- }
+ data := SRow{Mintmark: record[1]}
- if line = line[1:]; len(line) == 0 {
- return Data{}, SyntaxError{
- expected: "mintmark",
- got: "end-of-line",
- linenr: linenr,
- file: file,
- }
- }
+ data.Year, err = strconv.Atoi(record[0])
+ if err != nil {
+ return nil, err
+ }
- switch i := strings.IndexAny(line, ws); i {
- case 0:
- return Data{}, SyntaxError{
- expected: "mintmark",
- got: "whitespace",
- linenr: linenr,
- file: file,
+ for i, s := range record[2:] {
+ if s == "" {
+ data.Mintages[i] = Unknown
+ } else {
+ data.Mintages[i], err = strconv.Atoi(s)
+ if err != nil {
+ data.Mintages[i] = Invalid
}
- case -1:
- mintmark = line
- default:
- mintmark, line = line[:i], line[i:]
- commem = true
}
}
- out:
-
- if !commem {
- row := SRow{
- Year: year,
- Mintmark: mintmark,
- }
- for i := range row.Mintages {
- line = ""
- for isBlankOrComment(line) {
- if !scanner.Scan() {
- return Data{}, SyntaxError{
- expected: "mintage row",
- got: "end-of-file",
- linenr: linenr,
- file: file,
- }
- }
- line = strings.Trim(scanner.Text(), ws)
- linenr++
- }
-
- tokens := strings.FieldsFunc(line, func(r rune) bool {
- return strings.ContainsRune(ws, r)
- })
- if tokcnt := len(tokens); tokcnt != denoms {
- word := "entries"
- if tokcnt == 1 {
- word = "entry"
- }
- return Data{}, SyntaxError{
- expected: fmt.Sprintf("%d mintage entries", denoms),
- got: fmt.Sprintf("%d %s", tokcnt, word),
- linenr: linenr,
- file: file,
- }
- }
-
- for j, tok := range tokens {
- if tok != "?" && !isNumeric(tok, true) {
- return Data{}, SyntaxError{
- expected: "numeric mintage figure or ‘?’",
- got: tok,
- linenr: linenr,
- file: file,
- }
- }
- if tok == "?" {
- row.Mintages[i][j] = Unknown
- } else {
- row.Mintages[i][j] = atoiWithDots(tok)
- }
- }
- }
+ rows = append(rows, data)
+ }
- data.Standard = append(data.Standard, row)
- } else {
- row := CRow{
- Year: year,
- Mintmark: mintmark,
- }
- line = strings.TrimLeft(line, ws)
- if line[0] != '"' {
- return Data{}, SyntaxError{
- expected: "string",
- got: line,
- linenr: linenr,
- file: file,
- }
- }
+ return rows, nil
+}
- line = line[1:]
- switch i := strings.IndexByte(line, '"'); i {
- case -1:
- return Data{}, SyntaxError{
- expected: "closing quote",
- got: "end-of-line",
- linenr: linenr,
- file: file,
- }
- case 0:
- return Data{}, SyntaxError{
- expected: "commemorated event",
- got: "empty string",
- linenr: linenr,
- file: file,
- }
- default:
- row.Name, line = line[:i], line[i+1:]
- }
+func parseC(path string) ([]CRow, error) {
+ rows := make([]CRow, 0, 69) /* TODO: Compute number of rows */
- if len(line) != 0 {
- return Data{}, SyntaxError{
- expected: "end-of-line",
- got: line,
- linenr: linenr,
- file: file,
- }
- }
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
- for isBlankOrComment(line) {
- if !scanner.Scan() {
- return Data{}, SyntaxError{
- expected: "mintage row",
- got: "end-of-file",
- linenr: linenr,
- file: file,
- }
- }
- line = strings.Trim(scanner.Text(), ws)
- linenr++
- }
+ r := csv.NewReader(f)
+ r.Comment = '#'
+ r.FieldsPerRecord = 4
+ r.ReuseRecord = true
- tokens := strings.FieldsFunc(line, func(r rune) bool {
- return strings.ContainsRune(ws, r)
- })
- if tokcnt := len(tokens); tokcnt != typeCount {
- word := "entries"
- if tokcnt == 1 {
- word = "entry"
- }
- return Data{}, SyntaxError{
- expected: fmt.Sprintf("%d mintage entries", typeCount),
- got: fmt.Sprintf("%d %s", tokcnt, word),
- linenr: linenr,
- file: file,
- }
- }
+ /* Skip header */
+ if _, err := r.Read(); err != nil {
+ return nil, err
+ }
- for i, tok := range tokens {
- if tok == "?" {
- row.Mintage[i] = Unknown
- } else {
- row.Mintage[i] = atoiWithDots(tok)
- }
- }
+ for {
+ record, err := r.Read()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
- data.Commemorative = append(data.Commemorative, row)
+ data := CRow{
+ Name: record[1],
+ Mintmark: record[2],
}
- }
- return data, nil
-}
+ data.Year, err = strconv.Atoi(record[0])
+ if err != nil {
+ return nil, err
+ }
-func isNumeric(s string, dot bool) bool {
- for _, ch := range s {
- switch ch {
- case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
- default:
- if ch != '.' || !dot {
- return false
+ s := record[3]
+ if s == "" {
+ data.Mintage = Unknown
+ } else {
+ data.Mintage, err = strconv.Atoi(s)
+ if err != nil {
+ data.Mintage = Invalid
}
}
- }
- return true
-}
-func atoiWithDots(s string) int {
- n := 0
- for _, ch := range s {
- if ch == '.' {
- continue
- }
- n = n*10 + int(ch) - '0'
+ rows = append(rows, data)
}
- return n
-}
-func isBlankOrComment(s string) bool {
- return len(s) == 0 || s[0] == '#'
+ return rows, nil
}
diff --git a/src/mintage/parser_test.go b/src/mintage/parser_test.go
deleted file mode 100644
index 76e0f01..0000000
--- a/src/mintage/parser_test.go
+++ /dev/null
@@ -1,233 +0,0 @@
-package mintage
-
-import (
- "bytes"
- "errors"
- "testing"
-)
-
-func TestParserComplete(t *testing.T) {
- data, err := Parse(bytes.NewBuffer([]byte(`
- 2020
- 1000 1001 1002 1003 1004 1005 1006 1007
- 1100 1101 1102 1103 1104 1105 1106 1107
- 1200 1201 1202 1203 1204 1205 1206 1207
- 2021-KNM
- 2.000 ? 2002 2003 2004 2005 2006 2007
- 2.100 ? 2102 2103 2104 2105 2106 2107
- 2.200 ? 2202 2203 2204 2205 2206 2207
- 2021-MdP
- 3000 3001 3002 3003 3004 3005 3006 3007
- 3100 3101 3102 3103 3104 3105 3106 3107
- 3200 3201 3202 3203 3204 3205 3206 3207
- 2022
- 4000 4001 4.002 4003 4004 4005 4006 4007
- 4100 4101 4.102 4103 4104 4105 4106 4107
- 4200 4201 4.202 4203 4204 4205 4206 4207
-
- 2009 "10th Anniversary of Economic and Monetary Union"
- 1000 2000 3000
- 2022-⋆ "35th Anniversary of the Erasmus Programme"
- 1001 ? 3001
- `)), "-")
-
- if err != nil {
- t.Fatalf(`Expected err=nil; got "%s"`, err)
- }
-
- for i, row := range data.Standard {
- for k := TypeCirc; k <= TypeProof; k++ {
- for j, col := range row.Mintages[k] {
- n := 1000*(i+1) + 100*k + j
- if i == 1 && j == 1 {
- n = Unknown
- }
- if col != n {
- t.Fatalf("Expected data.Standard[%d].Mintages[%d][%d]=%d; got %d",
- i, k, j, col, n)
- }
- }
- }
- }
-
- for i, row := range data.Commemorative {
- for k := TypeCirc; k <= TypeProof; k++ {
- n := 1000*(k+1) + i
- if i == 1 && k == 1 {
- n = Unknown
- }
- if row.Mintage[k] != n {
- t.Fatalf("Expected row.Mintage[%d]=%d; got %d",
- k, n, row.Mintage[k])
- }
- }
- }
-
- if len(data.Standard) != 4 {
- t.Fatalf("Expected len(data.Standard)=2; got %d", len(data.Standard))
- }
- if len(data.Commemorative) != 2 {
- t.Fatalf("Expected len(data.Commemorative)=2; got %d", len(data.Commemorative))
- }
-
- for i, x := range [...]struct {
- year int
- mintmark, name string
- }{
- {2009, "", "10th Anniversary of Economic and Monetary Union"},
- {2022, "⋆", "35th Anniversary of the Erasmus Programme"},
- } {
- if data.Commemorative[i].Year != x.year {
- t.Fatalf("Expected data.Commemorative[%d].Year=%d; got %d",
- i, x.year, data.Commemorative[i].Year)
- }
- if data.Commemorative[i].Mintmark != x.mintmark {
- t.Fatalf(`Expected data.Commemorative[%d].Mintmark="%s"; got "%s"`,
- i, x.mintmark, data.Commemorative[i].Mintmark)
- }
- if data.Commemorative[i].Name != x.name {
- t.Fatalf(`Expected data.Commemorative[%d].Name="%s"; got "%s"`,
- i, x.name, data.Commemorative[i].Name)
- }
- }
-}
-
-func TestParserMintmarks(t *testing.T) {
- data, err := Parse(bytes.NewBuffer([]byte(`
- 2020
- 1000 1001 1002 1003 1004 1005 1006 1007
- 1100 1101 1102 1103 1104 1105 1106 1107
- 1200 1201 1202 1203 1204 1205 1206 1207
- 2021-KNM
- 2.000 ? 2002 2003 2004 2005 2006 2007
- 2.100 ? 2102 2103 2104 2105 2106 2107
- 2.200 ? 2202 2203 2204 2205 2206 2207
- 2021-MdP
- 3000 3001 3002 3003 3004 3005 3006 3007
- 3100 3101 3102 3103 3104 3105 3106 3107
- 3200 3201 3202 3203 3204 3205 3206 3207
- 2022
- 4000 4001 4.002 4003 4004 4005 4006 4007
- 4100 4101 4.102 4103 4104 4105 4106 4107
- 4200 4201 4.202 4203 4204 4205 4206 4207
- `)), "-")
-
- if err != nil {
- t.Fatalf(`Expected err=nil; got "%s"`, err)
- }
-
- for i, row := range data.Standard {
- for j, col := range row.Mintages[TypeCirc] {
- n := 1000*(i+1) + j
- if i == 1 && j == 1 {
- n = Unknown
- }
- if col != n {
- t.Fatalf("Expected data.Standard[%d].Mintages[TypeCirc][%d]=%d; got %d",
- i, j, col, n)
- }
- }
- }
-
- for i, y := range [...]int{2020, 2021, 2021, 2022} {
- if data.Standard[i].Year != y {
- t.Fatalf("Expected data.Standard[%d].Year=%d; got %d",
- i, y, data.Standard[i].Year)
- }
- }
-
- for i, m := range [...]string{"", "KNM", "MdP", ""} {
- if data.Standard[i].Mintmark != m {
- t.Fatalf(`Expected data.Standard[%d].Mintmark="%s"; got "%s"`,
- i, m, data.Standard[i].Mintmark)
- }
- }
-}
-
-func TestParserNoYear(t *testing.T) {
- _, err := Parse(bytes.NewBuffer([]byte(`
- 1.000 1001 1002 1003 1004 1005 1006 1007
- 1.100 1101 1102 1103 1104 1105 1106 1107
- 1.200 1201 1202 1203 1204 1205 1206 1207
- 2021
- 2000 ? 2002 2003 2004 2005 2006 2007
- 2100 ? 2102 2103 2104 2105 2106 2107
- 2200 ? 2202 2203 2204 2205 2206 2207
- `)), "-")
-
- var sErr SyntaxError
- if !errors.As(err, &sErr) {
- t.Fatalf("Expected err=SyntaxError; got %s", err)
- }
-}
-
-func TestParserBadToken(t *testing.T) {
- _, err := Parse(bytes.NewBuffer([]byte(`
- 2020
- 1.000 1001 1002 1003 1004 1005 1006 1007
- 1.100 1101 1102 1103 1104 1105 1106 1107
- 1.200 1201 1202 1203 1204 1205 1206 1207
- 2021 Naughty!
- 2000 ? 2002 2003 2004 2005 2006 2007
- 2100 ? 2102 2103 2104 2105 2106 2107
- 2200 ? 2202 2203 2204 2205 2206 2207
- `)), "-")
-
- var sErr SyntaxError
- if !errors.As(err, &sErr) {
- t.Fatalf("Expected err=SyntaxError; got %s", err)
- }
-}
-
-func TestParserShortRow(t *testing.T) {
- _, err := Parse(bytes.NewBuffer([]byte(`
- 2020
- 1.000 1001 1002 1003 1004 1005 1006 1007
- 1.100 1101 1102 1103 1104 1105 1106 1107
- 1.200 1201 1202 1203 1204 1205 1206 1207
- 2021
- 2000 ? 2002 2003 2004 2005 2006 2007
- 2100 ? 2102 2103 2104 2105 2106
- 2200 ? 2202 2203 2204 2205 2206 2207
- `)), "-")
-
- var sErr SyntaxError
- if !errors.As(err, &sErr) {
- t.Fatalf("Expected err=SyntaxError; got %s", err)
- }
-}
-
-func TestParserLongRow(t *testing.T) {
- _, err := Parse(bytes.NewBuffer([]byte(`
- 2020
- 1.000 1001 1002 1003 1004 1005 1006 1007
- 1.100 1101 1102 1103 1104 1105 1106 1107
- 1.200 1201 1202 1203 1204 1205 1206 1207
- 2021
- 2000 ? 2002 2003 2004 2005 2006 2007
- 2100 ? 2102 2103 2104 2105 2106 2107 2108
- 2200 ? 2202 2203 2204 2205 2206 2207
- `)), "-")
-
- var sErr SyntaxError
- if !errors.As(err, &sErr) {
- t.Fatalf("Expected err=SyntaxError; got %s", err)
- }
-}
-
-func TestParserMissingRow(t *testing.T) {
- _, err := Parse(bytes.NewBuffer([]byte(`
- 2020
- 1.000 1001 1002 1003 1004 1005 1006 1007
- 1.100 1101 1102 1103 1104 1105 1106 1107
- 1.200 1201 1202 1203 1204 1205 1206 1207
- 2021
- 2000 ? 2002 2003 2004 2005 2006 2007
- 2200 ? 2202 2203 2204 2205 2206 2207
- `)), "-")
-
- var sErr SyntaxError
- if !errors.As(err, &sErr) {
- t.Fatalf("Expected err=SyntaxError; got %s", err)
- }
-}
diff --git a/src/templates.go b/src/templates.go
index 2cee22e..f6413d9 100644
--- a/src/templates.go
+++ b/src/templates.go
@@ -13,7 +13,7 @@ import (
type templateData struct {
Printer Printer
Code, Type string
- Mintages mintage.Data
+ Mintages [3]mintage.Data
Countries []country
}
@@ -86,7 +86,7 @@ func templateMakeTuple(args ...any) []any {
func strToCtype(s string) int {
switch s {
case "nifc":
- return mintage.TypeNIFC
+ return mintage.TypeNifc
case "proof":
return mintage.TypeProof
default:
diff --git a/src/templates/coins-mintages.html.tmpl b/src/templates/coins-mintages.html.tmpl
index 8c60248..8554c3f 100644
--- a/src/templates/coins-mintages.html.tmpl
+++ b/src/templates/coins-mintages.html.tmpl
@@ -66,6 +66,7 @@
</div>
<button type="submit">{{ .T "Filter" }}</button>
</form>
+ {{ $data := (index .Mintages (strToCtype .Type)) }}
<figure>
<figcaption>{{ .T "Standard Issue Coins" }}</figcaption>
<table class="mintage-table" role="grid">
@@ -80,7 +81,7 @@
<tbody>
{{ $p := .Printer }}
{{ $type := .Type }}
- {{ range .Mintages.Standard }}
+ {{ range $data.Standard }}
<tr>
<th scope="col">
{{- .Year -}}
@@ -88,9 +89,11 @@
&nbsp;<sub><small>{{ .Mintmark }}</small></sub>
{{- end -}}
</th>
- {{ range (index .Mintages (strToCtype $type)) }}
+ {{ range .Mintages }}
{{ if eq . -1 }}
<td>{{ $p.T "Unknown" }}</td>
+ {{ else if eq . -2 }}
+ <td class="error">{{ $p.T "Error" }}</td>
{{ else if eq . 0 }}
<td>—</td>
{{ else }}
@@ -103,7 +106,7 @@
</tbody>
</table>
</figure>
- {{ if ne (len .Mintages.Commemorative) 0 }}
+ {{ if ne (len $data.Commemorative) 0 }}
<figure>
<figcaption>{{ .T "Commemorative Coins" }}</figcaption>
<table class="mintage-table-cc" role="grid">
@@ -115,7 +118,7 @@
<tbody>
{{ $p := .Printer }}
{{ $type := .Type }}
- {{ range .Mintages.Commemorative }}
+ {{ range $data.Commemorative }}
<tr>
<th scope="col">
{{- .Year -}}
@@ -125,9 +128,11 @@
</th>
<!-- TODO: Translate commemorative names -->
<td>{{ .Name }}</td>
- {{ with (index .Mintage (strToCtype $type)) }}
+ {{ with .Mintage }}
{{ if eq . -1 }}
<td>{{ $p.T "Unknown" }}</td>
+ {{ else if eq . -2 }}
+ <td class="error">{{ $p.T "Error" }}</td>
{{ else if eq . 0 }}
<td>—</td>
{{ else }}
diff --git a/static/style.css b/static/style.css
index 9ce0d82..023317c 100644
--- a/static/style.css
+++ b/static/style.css
@@ -2377,3 +2377,7 @@ dl.unix-style dd {
dl dt:not(:first-child) {
margin-top: var(--block-spacing-vertical);
}
+
+.error {
+ color: var(--del-color);
+} \ No newline at end of file