calendarium-romanum 0.2.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/bin/calendariumrom +4 -1
  3. data/config/locales/cs.yml +54 -2
  4. data/config/locales/en.yml +64 -14
  5. data/config/locales/es.yml +90 -0
  6. data/config/locales/fr.yml +90 -0
  7. data/config/locales/it.yml +54 -4
  8. data/config/locales/la.yml +52 -2
  9. data/data/README.md +105 -5
  10. data/data/czech-brno-cs.txt +11 -5
  11. data/data/czech-budejovice-cs.txt +11 -5
  12. data/data/czech-cechy-cs.txt +11 -5
  13. data/data/czech-cs.txt +243 -234
  14. data/data/czech-hradec-cs.txt +10 -4
  15. data/data/czech-litomerice-cs.txt +12 -6
  16. data/data/czech-morava-cs.txt +11 -5
  17. data/data/czech-olomouc-cs.txt +9 -3
  18. data/data/czech-ostrava-cs.txt +10 -4
  19. data/data/czech-plzen-cs.txt +10 -4
  20. data/data/czech-praha-cs.txt +10 -3
  21. data/data/universal-en.txt +218 -212
  22. data/data/universal-es.txt +243 -0
  23. data/data/universal-fr.txt +243 -0
  24. data/data/universal-it.txt +218 -212
  25. data/data/universal-la.txt +218 -211
  26. data/lib/calendarium-romanum.rb +30 -18
  27. data/lib/calendarium-romanum/abstract_date.rb +12 -0
  28. data/lib/calendarium-romanum/calendar.rb +210 -48
  29. data/lib/calendarium-romanum/cli.rb +101 -52
  30. data/lib/calendarium-romanum/cr.rb +16 -0
  31. data/lib/calendarium-romanum/data.rb +46 -18
  32. data/lib/calendarium-romanum/day.rb +200 -21
  33. data/lib/calendarium-romanum/enum.rb +24 -5
  34. data/lib/calendarium-romanum/enums.rb +123 -37
  35. data/lib/calendarium-romanum/errors.rb +4 -0
  36. data/lib/calendarium-romanum/ordinalizer.rb +61 -0
  37. data/lib/calendarium-romanum/perpetual_calendar.rb +97 -0
  38. data/lib/calendarium-romanum/rank.rb +43 -6
  39. data/lib/calendarium-romanum/sanctorale.rb +142 -22
  40. data/lib/calendarium-romanum/sanctorale_factory.rb +74 -3
  41. data/lib/calendarium-romanum/sanctorale_loader.rb +176 -0
  42. data/lib/calendarium-romanum/temporale.rb +296 -251
  43. data/lib/calendarium-romanum/temporale/celebration_factory.rb +106 -0
  44. data/lib/calendarium-romanum/temporale/dates.rb +232 -0
  45. data/lib/calendarium-romanum/temporale/extensions/christ_eternal_priest.rb +37 -0
  46. data/lib/calendarium-romanum/transfers.rb +43 -6
  47. data/lib/calendarium-romanum/util.rb +36 -3
  48. data/lib/calendarium-romanum/version.rb +5 -1
  49. data/spec/abstract_date_spec.rb +11 -3
  50. data/spec/calendar_spec.rb +645 -188
  51. data/spec/celebration_factory_spec.rb +40 -0
  52. data/spec/celebration_spec.rb +67 -0
  53. data/spec/cli_spec.rb +154 -11
  54. data/spec/colour_spec.rb +22 -0
  55. data/spec/data_spec.rb +26 -3
  56. data/spec/date_parser_spec.rb +68 -0
  57. data/spec/date_spec.rb +8 -8
  58. data/spec/dates_spec.rb +73 -0
  59. data/spec/day_spec.rb +151 -0
  60. data/spec/i18n_spec.rb +11 -2
  61. data/spec/ordinalizer_spec.rb +44 -0
  62. data/spec/perpetual_calendar_spec.rb +125 -0
  63. data/spec/rank_spec.rb +42 -7
  64. data/spec/readme_spec.rb +18 -10
  65. data/spec/sanctorale_factory_spec.rb +113 -9
  66. data/spec/sanctorale_loader_spec.rb +229 -0
  67. data/spec/sanctorale_spec.rb +176 -62
  68. data/spec/season_spec.rb +22 -0
  69. data/spec/spec_helper.rb +27 -1
  70. data/spec/temporale_spec.rb +473 -154
  71. data/spec/year_spec.rb +25 -0
  72. metadata +42 -7
  73. data/lib/calendarium-romanum/sanctoraleloader.rb +0 -104
  74. data/spec/sanctoraleloader_spec.rb +0 -171
@@ -1,19 +1,31 @@
1
- %w{
2
- version
3
- i18n_setup
4
- rank
5
- enum
6
- enums
7
- data
8
- calendar
9
- temporale
10
- sanctorale
11
- sanctoraleloader
12
- sanctorale_factory
13
- transfers
14
- day
15
- abstract_date
16
- util
17
- }.each do |f|
18
- require_relative File.join('calendarium-romanum', f)
1
+ # Module wrapping the gem's classes
2
+ #
3
+ # If you hate typing the long module name, see {CR}
4
+ module CalendariumRomanum
5
+ end
6
+
7
+ %w(
8
+ version
9
+ i18n_setup
10
+ abstract_date
11
+ rank
12
+ enum
13
+ enums
14
+ errors
15
+ data
16
+ day
17
+ calendar
18
+ perpetual_calendar
19
+ temporale/dates
20
+ temporale/celebration_factory
21
+ temporale/extensions/christ_eternal_priest
22
+ temporale
23
+ sanctorale
24
+ sanctorale_loader
25
+ sanctorale_factory
26
+ transfers
27
+ util
28
+ ordinalizer
29
+ ).each do |f|
30
+ require_relative File.join('calendarium-romanum', f)
19
31
  end
@@ -4,12 +4,20 @@ module CalendariumRomanum
4
4
  class AbstractDate
5
5
  include Comparable
6
6
 
7
+ # @param month [Fixnum]
8
+ # @param day [Fixnum]
9
+ # @raise [RangeError] on invalid +month+/+day+ value
7
10
  def initialize(month, day)
8
11
  validate! month, day
9
12
  @month = month
10
13
  @day = day
11
14
  end
12
15
 
16
+ # Build a new instance from a +Date+ (or an object with
17
+ # similar public interface).
18
+ #
19
+ # @param date [Date]
20
+ # @return [AbstractDate]
13
21
  def self.from_date(date)
14
22
  new(date.month, date.day)
15
23
  end
@@ -32,6 +40,10 @@ module CalendariumRomanum
32
40
  month == other.month && day == other.day
33
41
  end
34
42
 
43
+ # Produce a +Date+ by providing a year to an +AbstractDate+
44
+ #
45
+ # @param year [Fixnum]
46
+ # @return [Date]
35
47
  def concretize(year)
36
48
  Date.new(year, month, day)
37
49
  end
@@ -5,37 +5,61 @@ module CalendariumRomanum
5
5
 
6
6
  # Provides complete information concerning a liturgical year,
7
7
  # it's days and celebrations occurring on them.
8
+ #
9
+ # {Calendar}'s business logic is mostly about correctly combining
10
+ # information from {Temporale} and {Sanctorale}.
8
11
  class Calendar
9
-
10
12
  extend Forwardable
11
13
 
12
- # year: Integer
13
- # returns a calendar for the liturgical year beginning with
14
+ # Day when the implemented calendar system became effective
15
+ EFFECTIVE_FROM = Date.new(1970, 1, 1).freeze
16
+
17
+ # Returns a calendar for the liturgical year beginning with
14
18
  # Advent of the specified civil year.
15
- def initialize(year, sanctorale=nil)
19
+ #
20
+ # @param year [Fixnum]
21
+ # Civil year when the liturgical year begins.
22
+ # @param sanctorale [Sanctorale, nil]
23
+ # If not provided, the +Calendar+ will only know celebrations
24
+ # of the temporale cycle, no feasts of the saints!
25
+ # @param temporale [Temporale, nil]
26
+ # If not provided, +Temporale+ for the given year with default
27
+ # configuration will built.
28
+ # @param vespers [Boolean] Set to true if you want the +Calendar+ to populate {Day#vespers}
29
+ # @raise [RangeError]
30
+ # if +year+ is specified for which the implemented calendar
31
+ # system wasn't in force
32
+ def initialize(year, sanctorale = nil, temporale = nil, vespers: false)
33
+ if year < (EFFECTIVE_FROM.year - 1)
34
+ raise system_not_effective
35
+ end
36
+
37
+ if temporale && temporale.year != year
38
+ raise ArgumentError.new('Temporale year must be the same as year.')
39
+ end
40
+
16
41
  @year = year
17
- @temporale = Temporale.new(year)
18
42
  @sanctorale = sanctorale || Sanctorale.new
43
+ @temporale = temporale || Temporale.new(year)
44
+ @populate_vespers = vespers
45
+
19
46
  @transferred = Transfers.new(@temporale, @sanctorale)
20
47
  end
21
48
 
22
49
  class << self
50
+ # @api private
23
51
  def mk_date(*args)
24
52
  ex = TypeError.new('Date, DateTime or three Integers expected')
25
53
 
26
- if args.size == 3 then
54
+ if args.size == 3
27
55
  args.each do |a|
28
- unless a.is_a? Integer
29
- raise ex
30
- end
56
+ raise ex unless a.is_a? Integer
31
57
  end
32
- return Date.new *args
58
+ return Date.new(*args)
33
59
 
34
- elsif args.size == 1 then
60
+ elsif args.size == 1
35
61
  a = args.first
36
- unless a.is_a? Date
37
- raise ex
38
- end
62
+ raise ex unless a.is_a? Date
39
63
  return a
40
64
 
41
65
  else
@@ -43,91 +67,229 @@ module CalendariumRomanum
43
67
  end
44
68
  end
45
69
 
46
- # creates a Calendar for the liturgical year including given
47
- # date
48
- def for_day(date, sanctorale=nil)
49
- return new(Temporale.liturgical_year(date), sanctorale)
70
+ # Creates a new instance for the liturgical year which includes
71
+ # given date
72
+ #
73
+ # @param date [Date]
74
+ # @param constructor_args
75
+ # arguments that will be passed to {initialize}
76
+ # @return [Calendar]
77
+ def for_day(date, *constructor_args)
78
+ new(Temporale.liturgical_year(date), *constructor_args)
50
79
  end
51
80
  end # class << self
52
81
 
82
+ # @!method range_check(date)
83
+ # @see Temporale#range_check
84
+ # @param date
85
+ # @return [void]
86
+ # @!method season(date)
87
+ # @see Temporale#season
88
+ # @param date
89
+ # @return [Season]
53
90
  def_delegators :@temporale, :range_check, :season
91
+
92
+ # @return [Fixnum]
54
93
  attr_reader :year
94
+
95
+ # @return [Temporale]
55
96
  attr_reader :temporale
97
+
98
+ # @return [Sanctorale]
56
99
  attr_reader :sanctorale
57
100
 
58
- # returns a Calendar for the subsequent year
59
- def succ
60
- c = Calendar.new @year + 1, @sanctorale
61
- return c
101
+ # Do {Day} instances returned by this +Calendar+
102
+ # have {Day#vespers} populated?
103
+ # @return [Boolean]
104
+ # @since 0.6.0
105
+ def populates_vespers?
106
+ @populate_vespers
62
107
  end
63
108
 
64
- # returns a Calendar for the previous year
65
- def pred
66
- c = Calendar.new @year - 1, @sanctorale
67
- return c
109
+ # Two +Calendar+s are equal if they have equal settings
110
+ # (which means that to equal input they return equal data)
111
+ def ==(b)
112
+ b.class == self.class &&
113
+ year == b.year &&
114
+ populates_vespers? == b.populates_vespers? &&
115
+ temporale == b.temporale &&
116
+ sanctorale == b.sanctorale
68
117
  end
69
118
 
70
- def ==(obj)
71
- unless obj.is_a? Calendar
72
- return false
119
+ # Retrieve liturgical calendar information for the specified day
120
+ # or range of days.
121
+ #
122
+ # @overload [](date)
123
+ # @param date [Date]
124
+ # @return [Day]
125
+ # @overload [](range)
126
+ # @param range [Range<Date>]
127
+ # @return [Array<Day>]
128
+ def [](args)
129
+ if args.is_a?(Range)
130
+ args.map {|date| day(date) }
131
+ else
132
+ day(args)
73
133
  end
74
-
75
- return year == obj.year
76
134
  end
77
135
 
78
- # accepts date information represented as
79
- # Date, DateTime, or two to three integers
80
- # (month - day or year - month - day);
81
- # returns filled Day for the specified day
82
- def day(*args)
136
+ # Retrieve liturgical calendar information for the specified day
137
+ #
138
+ # @overload day(date, vespers: false)
139
+ # @param date [Date]
140
+ # @overload day(year, month, day, vespers: false)
141
+ # @param year [Fixnum]
142
+ # @param month [Fixnum]
143
+ # @param day [Fixnum]
144
+ # @param vespers [Boolean]
145
+ # Set to +true+ in order to get {Day} with {Day#vespers}
146
+ # populated (overrides instance-wide setting {#populates_vespers?}).
147
+ # @return [Day]
148
+ # @raise [RangeError]
149
+ # If a date is specified on which the implemented calendar
150
+ # system was not yet in force (it became effective during
151
+ # the liturgical year 1969/1970)
152
+ def day(*args, vespers: false)
83
153
  if args.size == 2
84
154
  date = Date.new(@year, *args)
85
155
  unless @temporale.date_range.include? date
86
156
  date = Date.new(@year + 1, *args)
87
157
  end
88
158
  else
89
- date = self.class.mk_date *args
159
+ date = self.class.mk_date(*args)
90
160
  range_check date
91
161
  end
92
162
 
163
+ if date < EFFECTIVE_FROM
164
+ raise system_not_effective
165
+ end
166
+
167
+ celebrations = celebrations_for(date)
168
+ vespers_celebration = nil
169
+ if @populate_vespers || vespers
170
+ begin
171
+ vespers_celebration = first_vespers_on(date, celebrations)
172
+ rescue RangeError
173
+ # there is exactly one possible case when
174
+ # range_check(date) passes and range_check(date + 1) fails:
175
+ vespers_celebration = Temporale::CelebrationFactory.first_advent_sunday
176
+ end
177
+ end
178
+
93
179
  s = @temporale.season(date)
94
- return Day.new(
95
- date: date,
96
- season: s,
97
- season_week: @temporale.season_week(s, date),
98
- celebrations: celebrations_for(date)
99
- )
180
+ Day.new(
181
+ date: date,
182
+ season: s,
183
+ season_week: @temporale.season_week(s, date),
184
+ celebrations: celebrations,
185
+ vespers: vespers_celebration
186
+ )
187
+ end
188
+
189
+ # Iterate over the whole liturgical year, day by day,
190
+ # for each day yield calendar data.
191
+ # If called without a block, returns +Enumerator+.
192
+ #
193
+ # @yield [Day]
194
+ # @return [void, Enumerator]
195
+ # @since 0.6.0
196
+ def each
197
+ return to_enum(__method__) unless block_given?
198
+
199
+ temporale.date_range
200
+ .each {|date| yield(day(date)) }
100
201
  end
101
202
 
102
203
  # Sunday lectionary cycle
204
+ #
205
+ # @return [Symbol]
206
+ # For possible values see {LECTIONARY_CYCLES}
103
207
  def lectionary
104
208
  LECTIONARY_CYCLES[@year % 3]
105
209
  end
106
210
 
107
211
  # Ferial lectionary cycle
212
+ #
213
+ # @return [1, 2]
108
214
  def ferial_lectionary
109
215
  @year % 2 + 1
110
216
  end
111
217
 
218
+ # Freezes the instance.
219
+ #
220
+ # *WARNING*: {Temporale} and {Sanctorale} instances passed
221
+ # to the +Calendar+ on initialization will be frozen, too!
222
+ # This is necessary, because a +Calendar+ would not really be
223
+ # frozen were it possible to mutate it's key components.
224
+ def freeze
225
+ @temporale.freeze
226
+ @sanctorale.freeze
227
+ super
228
+ end
229
+
230
+ private
231
+
112
232
  def celebrations_for(date)
113
233
  tr = @transferred.get(date)
114
234
  return [tr] if tr
115
235
 
116
- t = @temporale.get date
117
- st = @sanctorale.get date
236
+ t = @temporale[date]
237
+ st = @sanctorale[date]
238
+
239
+ if date.saturday? &&
240
+ @temporale.season(date) == Seasons::ORDINARY &&
241
+ (st.empty? || st.first.rank == Ranks::MEMORIAL_OPTIONAL) &&
242
+ t.rank <= Ranks::MEMORIAL_OPTIONAL
243
+ st = st.dup << Temporale::CelebrationFactory.saturday_memorial_bvm
244
+ end
118
245
 
119
246
  unless st.empty?
120
247
  if st.first.rank > t.rank
121
248
  if st.first.rank == Ranks::MEMORIAL_OPTIONAL
122
- st.unshift t
123
- return st
249
+ return st.dup.unshift t
124
250
  else
125
251
  return st
126
252
  end
253
+ elsif t.rank == Ranks::FERIAL_PRIVILEGED && st.first.rank.memorial?
254
+ commemorations = st.collect do |c|
255
+ c.change(rank: Ranks::COMMEMORATION, colour: t.colour)
256
+ end
257
+ return commemorations.unshift t
258
+ elsif t.symbol == :immaculate_heart &&
259
+ [Ranks::MEMORIAL_GENERAL, Ranks::MEMORIAL_PROPER].include?(st.first.rank)
260
+ optional_memorials = ([t] + st).collect do |celebration|
261
+ celebration.change rank: Ranks::MEMORIAL_OPTIONAL
262
+ end
263
+ ferial = temporale.send :ferial, date # ugly and evil
264
+ return [ferial] + optional_memorials
127
265
  end
128
266
  end
129
267
 
130
- return [t]
268
+ [t]
269
+ end
270
+
271
+ def first_vespers_on(date, celebrations)
272
+ tomorrow = date + 1
273
+ tomorrow_celebrations = celebrations_for(tomorrow)
274
+
275
+ c = tomorrow_celebrations.first
276
+ if c.rank >= Ranks::SOLEMNITY_PROPER ||
277
+ c.rank == Ranks::SUNDAY_UNPRIVILEGED ||
278
+ (c.rank == Ranks::FEAST_LORD_GENERAL && tomorrow.sunday?)
279
+ if c.symbol == :ash_wednesday || c.symbol == :good_friday
280
+ return nil
281
+ end
282
+
283
+ if c.rank > celebrations.first.rank || c.symbol == :easter_sunday
284
+ return c
285
+ end
286
+ end
287
+
288
+ nil
289
+ end
290
+
291
+ def system_not_effective
292
+ RangeError.new('Year out of range. Implemented calendar system has been in use only since 1st January 1970.')
131
293
  end
132
294
  end # class Calendar
133
295
  end
@@ -2,45 +2,47 @@ require 'thor'
2
2
 
3
3
  module CalendariumRomanum
4
4
 
5
+ # Implementation of the +calendariumrom+ executable.
6
+ # _Not_ loaded by default when you +require+ the gem.
7
+ #
8
+ # @api private
5
9
  class CLI < Thor
6
10
  include CalendariumRomanum::Util
7
11
 
8
12
  desc 'query 2007-06-05', 'show calendar information for a specified date'
9
13
  option :calendar, default: 'universal-en', aliases: :c
10
14
  option :locale, default: 'en', aliases: :l
11
- def query(date_str=nil)
15
+ def query(date_str = nil)
12
16
  I18n.locale = options[:locale]
17
+ calendar = options[:calendar]
18
+ if File.exist?(calendar)
19
+ begin
20
+ sanctorale = SanctoraleLoader.new.load_from_file(calendar)
21
+ rescue CalendariumRomanum::InvalidDataError
22
+ die! 'Invalid file format.'
23
+ end
24
+ else
25
+ data_file = Data[calendar]
13
26
 
14
- data_file = Data[options[:calendar]]
15
- if data_file.nil?
16
- STDERR.puts 'Invalid calendar. See subcommand `calendars` for valid options.'
17
- exit 1
18
- end
19
- sanctorale = data_file.load
20
-
21
- date =
22
- if date_str
23
- begin
24
- Date.parse(date_str)
25
- rescue ArgumentError
26
- STDERR.puts 'Invalid date.'
27
- exit 1
28
- end
29
- else
30
- Date.today
27
+ if data_file.nil?
28
+ die! "Invalid calendar. Either loading a calendar from filesystem did not succeed, \n or a preinstalled calendar was specified which doesn't exist. See subcommand `calendars` for valid options."
31
29
  end
32
- calendar = Calendar.for_day(date, sanctorale)
33
- day = calendar.day date
30
+ sanctorale = data_file.load
31
+ end
34
32
 
35
- puts date
36
- puts "season: #{day.season}"
37
- puts
33
+ pcal = PerpetualCalendar.new sanctorale: sanctorale
38
34
 
39
- rank_length = day.celebrations.collect {|c| c.rank.short_desc.size }.max
40
- day.celebrations.each do |c|
41
- print c.rank.short_desc.rjust(rank_length)
42
- print ' : '
43
- puts c.title
35
+ if date_str
36
+ begin
37
+ parsed_date = DateParser.new(date_str)
38
+ parsed_date.date_range.each do |day|
39
+ print_single_date(pcal, day)
40
+ end
41
+ rescue ArgumentError
42
+ die! 'Invalid date.'
43
+ end
44
+ else
45
+ print_single_date(pcal, Date.today)
44
46
  end
45
47
  end
46
48
 
@@ -54,47 +56,94 @@ module CalendariumRomanum
54
56
  loader = SanctoraleLoader.new
55
57
  files.each do |path|
56
58
  s = Sanctorale.new
57
- loader.load_from_file path, s
59
+ begin
60
+ loader.load_from_file path, s
61
+ rescue Errno::ENOENT, InvalidDataError => err
62
+ die! err.message
63
+ end
58
64
  end
59
65
  end
60
66
 
61
67
  desc 'cmp FILE1, FILE2', 'detect differences in rank and colour of corresponding celebrations'
62
68
  def cmp(a, b)
63
69
  loader = SanctoraleLoader.new
64
- sanctorales = []
65
-
66
- [a, b].each do |source|
67
- s = Sanctorale.new
68
- loader.load_from_file source, s
69
- sanctorales << s
70
- end
70
+ paths = [a, b]
71
+ sanctoralia = paths.collect {|source| loader.load_from_file source }
72
+ names = paths.collect {|source| File.basename source }
71
73
 
72
74
  # a leap year must be chosen in order to iterate over
73
75
  # all possible days of a Sanctorale
74
76
  Year.new(1990).each_day do |d|
75
- celebs = sanctorales.collect {|s| s.get d.month, d.day }
76
- if celebs.find {|cc| cc.nil? }
77
- next
78
- end
77
+ a, b = sanctoralia.collect {|s| s.get(d) }
79
78
 
80
- celebs[0].each_index do |i|
81
- if i >= celebs[1].size
82
- break
83
- end
84
-
85
- ca = celebs[0][i]
86
- cb = celebs[1][i]
79
+ 0.upto([a.size, b.size].max - 1) do |i|
80
+ ca = a[i]
81
+ cb = b[i]
82
+ compared = [ca, cb]
87
83
 
88
- _print_cel = Proc.new {|c| puts "#{c.rank.priority} #{c.colour} | #{c.title}" }
84
+ if compared.index(&:nil?)
85
+ notnili = compared.index {|c| !c.nil? }
89
86
 
90
- if ca.rank != cb.rank || ca.colour != cb.colour
91
- puts "#{d.month}/#{d.day}"
92
- _print_cel.call ca
93
- _print_cel.call cb
87
+ print date(d)
88
+ puts " only in #{names[notnili]}:"
89
+ puts celebration(compared[notnili])
94
90
  puts
91
+ next
95
92
  end
93
+
94
+ differences = %i(rank colour symbol).select do |property|
95
+ ca.public_send(property) != cb.public_send(property)
96
+ end
97
+
98
+ next if differences.empty?
99
+ print date(d)
100
+ puts " differs in #{differences.join(', ')}"
101
+ puts celebration(ca)
102
+ puts celebration(cb)
103
+ puts
96
104
  end
97
105
  end
98
106
  end
107
+
108
+ desc 'version', 'print version information'
109
+ def version
110
+ puts 'calendarium-romanum CLI'
111
+ puts "calendarium-romanum: version #{CalendariumRomanum::VERSION}, released #{CalendariumRomanum::RELEASE_DATE}"
112
+ end
113
+
114
+ private
115
+
116
+ def date(d)
117
+ "#{d.month}/#{d.day}"
118
+ end
119
+
120
+ def celebration(c)
121
+ "#{c.rank.priority} #{c.colour.symbol} | #{c.title}"
122
+ end
123
+
124
+ def die!(message, code = 1)
125
+ STDERR.puts message
126
+ exit code
127
+ end
128
+
129
+ def print_single_date(calendar, date)
130
+ day = calendar.day date
131
+
132
+ puts date
133
+ puts "season: #{day.season.name}"
134
+ puts
135
+
136
+ rank_length = day.celebrations.collect {|c| c.rank.short_desc.nil? ? 0 : c.rank.short_desc.size }.max
137
+ day.celebrations.each do |c|
138
+ if [Ranks::PRIMARY, Ranks::TRIDUUM].include? c.rank
139
+ puts c.title
140
+ elsif !c.rank.short_desc.nil?
141
+ print c.rank.short_desc.rjust(rank_length)
142
+ print ' : '
143
+ puts c.title
144
+ end
145
+ end
146
+ end
147
+
99
148
  end
100
149
  end