summaryrefslogtreecommitdiff
path: root/cmd/lectio-ef-dump/main.go
blob: 73c3ea9c881c3c21987f8638e19c3eb779bc45fb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Command lectio-ef-dump prints lectio's Extraordinary Form (1962) calendar,
// one line per day, for use as colitur's differential oracle.
//
// Output format is colitur's `colitur day <year>` line, PLUS four columns
// colitur's own line does not carry (so a straight `diff` against colitur's
// output must ignore trailing fields, not compare them line for line):
//
//	YYYY-MM-DD weekday season week slug rank colour name_en name_pl first gospel [+other-slug]...
//
// name_en and name_pl are the observed celebration's own display name in
// English and Polish (Celebration.Name["en"]/["pl"]), spaces replaced with
// "_" so the line stays whitespace-delimited; "-" where the name is empty
// (a bare feria with no proper name). Added after a regeneration of
// internal/caldata/tridentine-calendar.ini once deleted all 322 Polish
// names (name.pl 322 -> 0) with nothing in this repository's test suite OR
// this dumper noticing: the season/rank/colour columns this tool already
// printed were all still correct, since name is a wholly separate field
// naming.CelebrationName reads independently. This dumper is what the
// task's own before/after diff verification is run against, so it needed
// to be structurally capable of seeing a name regression, not just told to
// look harder next time.
//
// first and gospel are the Epistle/Lesson and Gospel citations RESOLVED for
// the day (Reading.Part "first"/"gospel"), not a raw lectionary-table lookup
// keyed by slug: they come from caldata.Readings, the same function the CLI
// and calfeed use, which applies the day's own inline propers first and
// falls back to the 1962 rule that a green-season weekday with no proper
// Mass repeats the PRECEDING SUNDAY's Mass (via calendar.TemporalSlug, which
// still finds that Sunday's temporal identity even when a feast displaced it
// as the observed celebration). That fallback is exactly why this column is
// worth comparing: colitur implements the same rubric independently, so this
// checks two implementations against each other rather than one against
// silence. Spaces become "_" (dumpField, shared with name_en/name_pl); "-"
// where the day has no such reading (e.g. no epistle/gospel citation at
// all, or the Mass proper does not carry that part).
//
// Every column carries lectio's OWN vocabulary (season names, slugs, rank and
// colour spellings) -- this dumper does not translate lectio's values into
// colitur's. The mapping between the two vocabularies is the differential
// comparator's job, not this dumper's; translating here would hide real
// divergences behind an already-reconciled view.
//
// The trailing "+other-slug" tokens are lectio's Others: the sanctoral
// candidates that lost the day's precedence contest. lectio has no RG 111
// commemoration-admission logic, so this is the set of losing candidates, not
// the set of admitted commemorations -- the two are different things. See the
// task report for detail; the comparator does not diff this column.
//
// Usage: lectio-ef-dump <from-year> <to-year>
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/lukaszkasprzak/lectio/internal/caldata"
	"github.com/lukaszkasprzak/lectio/internal/calendar"
)

func main() {
	os.Exit(run(os.Args[1:], os.Stdout, os.Stderr))
}

// run is lectio-ef-dump's testable entry point: parse args, compute, and
// write. Returns a process exit code (0 ok, 1 runtime error, 2 usage error).
func run(args []string, stdout, stderr io.Writer) int {
	if len(args) != 2 {
		fmt.Fprintln(stderr, "usage: lectio-ef-dump <from-year> <to-year>")
		return 2
	}
	from, errFrom := strconv.Atoi(args[0])
	to, errTo := strconv.Atoi(args[1])
	if errFrom != nil || errTo != nil {
		fmt.Fprintln(stderr, "lectio-ef-dump: from-year and to-year must be integers")
		return 2
	}
	if from > to {
		fmt.Fprintf(stderr, "lectio-ef-dump: from-year %d is after to-year %d\n", from, to)
		return 2
	}

	sel := calendar.DefaultSelection()
	sel.Form = "old"
	layers := []calendar.Layer{caldata.Tridentine()}

	w := bufio.NewWriter(stdout)
	for y := from; y <= to; y++ {
		start := time.Date(y, time.January, 1, 0, 0, 0, 0, time.UTC)
		end := time.Date(y, time.December, 31, 0, 0, 0, 0, time.UTC)
		for d := start; !d.After(end); d = d.AddDate(0, 0, 1) {
			day := calendar.Compute(d, sel, layers)
			rs := caldata.Readings(sel, layers, d, day)
			if _, err := w.WriteString(dumpLine(day, rs)); err != nil {
				fmt.Fprintf(stderr, "lectio-ef-dump: %v\n", err)
				return 1
			}
		}
	}
	if err := w.Flush(); err != nil {
		fmt.Fprintf(stderr, "lectio-ef-dump: %v\n", err)
		return 1
	}
	return 0
}

// dumpField renders a whitespace-delimited field: spaces become "_" (values
// routinely contain spaces, e.g. the name "St. Thomas Becket" or the citation
// "Rom 13:11-14"); "-" for an empty value, matching the week column's own
// convention for "absent". Shared by the name_en/name_pl columns and the
// first/gospel reading-citation columns.
func dumpField(s string) string {
	if s == "" {
		return "-"
	}
	return strings.ReplaceAll(s, " ", "_")
}

// citationFor returns the citation of the first reading of the given Part
// ("first" or "gospel") among rs, or "" if rs carries none.
func citationFor(rs []calendar.Reading, part string) string {
	for _, r := range rs {
		if r.Part == part {
			return r.Citation
		}
	}
	return ""
}

// dumpLine renders one LiturgicalDay as a colitur-format line, plus the
// name_en/name_pl/first/gospel columns colitur's own line does not carry
// (see this package's doc comment). rs is the day's RESOLVED readings
// (caldata.Readings' result), not day.Observed.Masses directly: for a
// green-season weekday that repeats the preceding Sunday's Mass,
// day.Observed carries the temporal celebration's own (proper-less) Masses,
// and only caldata.Readings applies that fallback. Week is lectio's own int
// (0 where no season week applies, e.g. named I class feasts and per annum
// green-season ferias); printed as "-" there so the field count stays fixed,
// matching colitur's own convention for an absent week.
func dumpLine(day calendar.LiturgicalDay, rs []calendar.Reading) string {
	week := "-"
	if day.Week != 0 {
		week = strconv.Itoa(day.Week)
	}
	var b strings.Builder
	fmt.Fprintf(&b, "%s %s %s %s %s %s %s %s %s %s %s",
		day.Date.Format("2006-01-02"),
		strings.ToLower(day.Weekday.String()),
		string(day.Season),
		week,
		day.Observed.Slug,
		string(day.Observed.Rank),
		string(day.Colour),
		dumpField(day.Observed.Name["en"]),
		dumpField(day.Observed.Name["pl"]),
		dumpField(citationFor(rs, "first")),
		dumpField(citationFor(rs, "gospel")),
	)
	for _, o := range day.Others {
		fmt.Fprintf(&b, " +%s", o.Slug)
	}
	b.WriteByte('\n')
	return b.String()
}