calendarium-romanum 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/bin/calendariumrom +3 -0
  3. data/config/locales/es.yml +90 -0
  4. data/data/README.md +43 -1
  5. data/data/czech-brno-cs.txt +4 -6
  6. data/data/czech-budejovice-cs.txt +4 -6
  7. data/data/czech-cechy-cs.txt +4 -5
  8. data/data/czech-cs.txt +237 -234
  9. data/data/czech-hradec-cs.txt +3 -5
  10. data/data/czech-litomerice-cs.txt +5 -7
  11. data/data/czech-morava-cs.txt +4 -5
  12. data/data/czech-olomouc-cs.txt +2 -4
  13. data/data/czech-ostrava-cs.txt +3 -5
  14. data/data/czech-plzen-cs.txt +3 -5
  15. data/data/czech-praha-cs.txt +3 -4
  16. data/data/universal-en.txt +214 -211
  17. data/data/universal-es.txt +243 -0
  18. data/data/universal-fr.txt +214 -210
  19. data/data/universal-it.txt +214 -211
  20. data/data/universal-la.txt +214 -212
  21. data/lib/calendarium-romanum.rb +6 -0
  22. data/lib/calendarium-romanum/abstract_date.rb +12 -0
  23. data/lib/calendarium-romanum/calendar.rb +93 -13
  24. data/lib/calendarium-romanum/cli.rb +11 -4
  25. data/lib/calendarium-romanum/cr.rb +16 -0
  26. data/lib/calendarium-romanum/data.rb +34 -7
  27. data/lib/calendarium-romanum/day.rb +132 -14
  28. data/lib/calendarium-romanum/enum.rb +22 -1
  29. data/lib/calendarium-romanum/enums.rb +80 -28
  30. data/lib/calendarium-romanum/errors.rb +1 -1
  31. data/lib/calendarium-romanum/ordinalizer.rb +10 -1
  32. data/lib/calendarium-romanum/perpetual_calendar.rb +43 -5
  33. data/lib/calendarium-romanum/rank.rb +23 -0
  34. data/lib/calendarium-romanum/sanctorale.rb +119 -20
  35. data/lib/calendarium-romanum/sanctorale_factory.rb +74 -3
  36. data/lib/calendarium-romanum/sanctorale_loader.rb +60 -19
  37. data/lib/calendarium-romanum/temporale.rb +110 -9
  38. data/lib/calendarium-romanum/temporale/celebration_factory.rb +45 -2
  39. data/lib/calendarium-romanum/temporale/dates.rb +65 -1
  40. data/lib/calendarium-romanum/temporale/extensions/christ_eternal_priest.rb +14 -2
  41. data/lib/calendarium-romanum/transfers.rb +20 -1
  42. data/lib/calendarium-romanum/util.rb +15 -3
  43. data/lib/calendarium-romanum/version.rb +3 -2
  44. data/spec/calendar_spec.rb +48 -4
  45. data/spec/celebration_factory_spec.rb +4 -0
  46. data/spec/celebration_spec.rb +9 -0
  47. data/spec/cli_spec.rb +7 -0
  48. data/spec/data_spec.rb +23 -0
  49. data/spec/day_spec.rb +26 -0
  50. data/spec/i18n_spec.rb +10 -0
  51. data/spec/sanctorale_factory_spec.rb +113 -9
  52. data/spec/sanctorale_loader_spec.rb +49 -24
  53. data/spec/sanctorale_spec.rb +72 -9
  54. data/spec/temporale_spec.rb +52 -53
  55. data/spec/year_spec.rb +25 -0
  56. metadata +7 -3
@@ -1,15 +1,50 @@
1
1
  module CalendariumRomanum
2
- # conveniently creates sanctorale from several data files
2
+ # Utility loading {Sanctorale} from several sources
3
+ # and building a single {Sanctorale} by layering them
4
+ # over each other.
3
5
  class SanctoraleFactory
4
6
  class << self
5
- # layers several sanctorale instances.
7
+ # Takes several {Sanctorale} instances, returns a new one,
8
+ # resulting by merging them all together
9
+ # (using {Sanctorale#update})
10
+ #
11
+ # @return [Sanctorale]
12
+ #
13
+ # @example
14
+ # include CalendariumRomanum
15
+ #
16
+ # prague_sanctorale = SanctoraleFactory.create_layered(
17
+ # Data['czech-cs'].load, # Czech Republic
18
+ # Data['czech-cechy-cs'].load, # Province of Bohemia
19
+ # Data['czech-praha-cs'].load, # Archdiocese of Prague
20
+ # )
6
21
  def create_layered(*instances)
7
22
  r = Sanctorale.new
8
23
  instances.each {|i| r.update i }
24
+
25
+ metadata = instances
26
+ .collect(&:metadata)
27
+ .select {|i| i.is_a? Hash }
28
+ r.metadata = metadata.inject((metadata.first || {}).dup) {|merged,i| merged.update i }
29
+ r.metadata.delete 'extends'
30
+ r.metadata['components'] = instances.collect(&:metadata)
31
+
9
32
  r
10
33
  end
11
34
 
12
- # loads and layers several sanctorale instances.
35
+ # Takes several filesystem paths, loads a {Sanctorale}
36
+ # from each of them (using {SanctoraleLoader})
37
+ # and then merges them (using {.create_layered})
38
+ #
39
+ # @return [Sanctorale]
40
+ #
41
+ # @example
42
+ # include CalendariumRomanum
43
+ #
44
+ # my_sanctorale = SanctoraleFactory.load_layered_from_files(
45
+ # 'my_data/general_calendar.txt',
46
+ # 'my_data/particular_calendar.txt'
47
+ # )
13
48
  def load_layered_from_files(*paths)
14
49
  loader = SanctoraleLoader.new
15
50
  instances = paths.collect do |p|
@@ -17,6 +52,42 @@ module CalendariumRomanum
17
52
  end
18
53
  create_layered(*instances)
19
54
  end
55
+
56
+ # Takes a single filesystem path. If the file's YAML front
57
+ # matter references any parent data files using the
58
+ # 'extends' key, it loads all the parents and assembles
59
+ # the resulting {Sanctorale}.
60
+ # If the data file doesn't reference any parents,
61
+ # result is the same as {SanctoraleLoader#load_from_file}.
62
+ #
63
+ # @return [Sanctorale]
64
+ # @since 0.7.0
65
+ def load_with_parents(path)
66
+ loader = SanctoraleLoader.new
67
+
68
+ hierarchy = load_parent_hierarchy(path, loader)
69
+ return hierarchy.first if hierarchy.size == 1
70
+
71
+ create_layered *hierarchy
72
+ end
73
+
74
+ private
75
+
76
+ def load_parent_hierarchy(path, loader)
77
+ main = loader.load_from_file path
78
+ return [main] unless main.metadata.has_key? 'extends'
79
+
80
+ to_merge = [main]
81
+ parents = main.metadata['extends']
82
+ parents = [parents] unless parents.is_a? Array
83
+ parents.reverse.each do |parent_path|
84
+ expanded_path = File.expand_path parent_path, File.dirname(path)
85
+ subtree = load_parent_hierarchy(expanded_path, loader)
86
+ to_merge = subtree + to_merge
87
+ end
88
+
89
+ to_merge
90
+ end
20
91
  end
21
92
  end
22
93
  end
@@ -1,47 +1,62 @@
1
+ require 'yaml'
2
+
1
3
  module CalendariumRomanum
2
4
 
3
- # understands a plaintext calendar format
4
- # and knows how to transform it to Celebrations
5
- # and fill them in a Sanctorale
6
- #
7
- # Format of the file:
8
- # 1/31 m : S. Ioannis Bosco, presbyteri
5
+ # Understands a custom plaintext calendar format
6
+ # and knows how to transform it to {Celebration}s
7
+ # and fill them in a {Sanctorale}.
9
8
  #
10
- # <month>/<day> <rank shortcut> : <title>
11
- # rank shortcut is optional, default value is optional memorial
9
+ # For specification of the data format see {file:data/README.md},
10
+ # for a complete example see the file describing General Roman Calendar:
11
+ # {file:data/universal-en.txt}
12
12
  class SanctoraleLoader
13
13
 
14
+ # @api private
14
15
  RANK_CODES = {
15
- nil => Ranks::MEMORIAL_OPTIONAL,
16
+ nil => Ranks::MEMORIAL_OPTIONAL, # default
16
17
  'm' => Ranks::MEMORIAL_GENERAL,
17
18
  'f' => Ranks::FEAST_GENERAL,
18
19
  's' => Ranks::SOLEMNITY_GENERAL
19
20
  }.freeze
21
+
22
+ # @api private
20
23
  COLOUR_CODES = {
21
- nil => Colours::WHITE,
24
+ nil => Colours::WHITE, # default
22
25
  'w' => Colours::WHITE,
23
26
  'v' => Colours::VIOLET,
24
27
  'g' => Colours::GREEN,
25
28
  'r' => Colours::RED
26
29
  }.freeze
27
30
 
28
- # dest should be a Sanctorale,
29
- # src anything with #each_line
31
+ # Load from an object which understands +#each_line+
32
+ #
33
+ # @param src [String, File, #each_line]
34
+ # source of the loaded data
35
+ # @param dest [Sanctorale, nil]
36
+ # objects to populate. If not provided, a new {Sanctorale}
37
+ # instance will be created
38
+ # @return [Sanctorale]
39
+ # @raise [InvalidDataError]
30
40
  def load(src, dest = nil)
31
41
  dest ||= Sanctorale.new
32
42
 
33
43
  in_front_matter = false
44
+ front_matter = ''
34
45
  month_section = nil
35
46
  src.each_line.with_index(1) do |l, line_num|
36
47
  # skip YAML front matter
37
48
  if line_num == 1 && l.start_with?('---')
38
49
  in_front_matter = true
50
+ front_matter += l
39
51
  next
40
52
  elsif in_front_matter
41
53
  if l.start_with?('---')
42
54
  in_front_matter = false
55
+ dest.metadata = YAML.load(front_matter).freeze
43
56
  end
44
57
 
58
+ front_matter += l
59
+
45
60
  next
46
61
  end
47
62
 
@@ -78,26 +93,52 @@ module CalendariumRomanum
78
93
 
79
94
  alias load_from_string load
80
95
 
96
+ # Load from a filesystem path
97
+ #
98
+ # @param filename [String]
99
+ # @param dest [Sanctorale, nil]
100
+ # @param encoding [String]
101
+ # @return (see #load)
102
+ # @raise (see #load)
81
103
  def load_from_file(filename, dest = nil, encoding = 'utf-8')
82
104
  load File.open(filename, 'r', encoding: encoding), dest
83
105
  end
84
106
 
85
107
  private
86
108
 
109
+ def line_regexp
110
+ @line_regexp ||=
111
+ begin
112
+ rank_letters = RANK_CODES.keys.compact.join('')
113
+ colour_letters = COLOUR_CODES.keys.compact.join('')
114
+
115
+ Regexp.new(
116
+ '^((?<month>\d+)\/)?(?<day>\d+)' + # date
117
+ '(\s+(?<rank_char>[' + rank_letters + '])?(?<rank_num>\d\.\d{1,2})?)?' + # rank (optional)
118
+ '(\s+(?<colour>[' + colour_letters + ']))?' + # colour (optional)
119
+ '(\s+(?<symbol>[\w]{2,}))?' + # symbol (optional)
120
+ '\s*:(?<title>.*)$', # title
121
+ Regexp::IGNORECASE
122
+ )
123
+ end
124
+ end
125
+
87
126
  # parses a line containing celebration record,
88
127
  # returns a single Celebration
89
128
  def load_line(line, month_section = nil)
90
129
  # celebration record
91
- rank_letters = RANK_CODES.keys.compact.join('')
92
- m = line.match(/^((\d+)\/)?(\d+)\s*(([#{rank_letters}])?(\d\.\d{1,2})?)?\s*([WVRG])?\s*(:[\w\d_]+)?\s*:(.*)$/i)
130
+ m = line.match(line_regexp)
93
131
  if m.nil?
94
132
  raise RuntimeError.new("Syntax error, line skipped '#{line}'")
95
133
  end
96
134
 
97
- month, day, rank_char, rank_num, colour, symbol_str, title = m.values_at(2, 3, 5, 6, 7, 8, 9)
98
- month ||= month_section
99
- day = day.to_i
100
- month = month.to_i
135
+ month = (m[:month] || month_section).to_i
136
+ day = m[:day].to_i
137
+ rank_char = m[:rank_char]
138
+ rank_num = m[:rank_num]
139
+ colour = m[:colour]
140
+ symbol_str = m[:symbol]
141
+ title = m[:title]
101
142
 
102
143
  rank = RANK_CODES[rank_char && rank_char.downcase]
103
144
 
@@ -116,7 +157,7 @@ module CalendariumRomanum
116
157
 
117
158
  symbol = nil
118
159
  if symbol_str
119
- symbol = symbol_str[1 .. -1].to_sym
160
+ symbol = symbol_str.to_sym
120
161
  end
121
162
 
122
163
  Celebration.new(
@@ -2,15 +2,26 @@ require 'date'
2
2
 
3
3
  module CalendariumRomanum
4
4
 
5
- # determine seasons and dates of Temporale feasts of the given year
5
+ # One of the two main {Calendar} components.
6
+ # Handles seasons and celebrations of the temporale cycle
7
+ # for a given liturgical year.
6
8
  class Temporale
7
9
 
10
+ # How many days in a week
8
11
  WEEK = 7
9
12
 
13
+ # Which solemnities can be transferred to Sunday
10
14
  SUNDAY_TRANSFERABLE_SOLEMNITIES =
11
15
  %i(epiphany ascension corpus_christi).freeze
12
16
 
13
- # year is Integer - the civil year when the liturgical year begins
17
+ # @param year [Fixnum]
18
+ # the civil year when the liturgical year _begins_
19
+ # @param extensions [Array<#each_celebration>]
20
+ # extensions implementing custom temporale celebrations
21
+ # @param transfer_to_sunday [Array<Symbol>]
22
+ # which solemnities should be transferred to a nearby
23
+ # Sunday - see {SUNDAY_TRANSFERABLE_SOLEMNITIES}
24
+ # for possible values
14
25
  def initialize(year, extensions: [], transfer_to_sunday: [])
15
26
  @year = year
16
27
 
@@ -21,10 +32,14 @@ module CalendariumRomanum
21
32
  prepare_solemnities
22
33
  end
23
34
 
35
+ # @return [Fixnum]
24
36
  attr_reader :year
25
37
 
26
38
  class << self
27
39
  # Determines liturgical year for the given date
40
+ #
41
+ # @param date [Date]
42
+ # @return [Fixnum]
28
43
  def liturgical_year(date)
29
44
  year = date.year
30
45
  temporale = Temporale.new year
@@ -36,14 +51,19 @@ module CalendariumRomanum
36
51
  year
37
52
  end
38
53
 
39
- # creates a Calendar for the liturgical year including given
54
+ # Creates an instance for the liturgical year including given
40
55
  # date
56
+ #
57
+ # @param date [Date]
58
+ # @return [Temporale]
41
59
  def for_day(date)
42
60
  new(liturgical_year(date))
43
61
  end
44
62
 
45
- # factory method creating temporale celebrations
63
+ # Factory method creating temporale {Celebration}s
46
64
  # with sensible defaults
65
+ #
66
+ # See {Celebration#initialize} for argument description.
47
67
  def create_celebration(title, rank, colour, symbol: nil, date: nil)
48
68
  Celebration.new(title, rank, colour, symbol, date, :temporale)
49
69
  end
@@ -51,7 +71,7 @@ module CalendariumRomanum
51
71
  C = Struct.new(:date_method, :celebration)
52
72
  private_constant :C
53
73
 
54
- # implementation detail, not to be touched by client code
74
+ # @api private
55
75
  def celebrations
56
76
  @celebrations ||=
57
77
  begin
@@ -90,22 +110,41 @@ module CalendariumRomanum
90
110
  end
91
111
  end
92
112
 
113
+ # Does this instance transfer the specified solemnity to Sunday?
114
+ #
115
+ # @param solemnity [Symbol]
116
+ # @return [Boolean]
93
117
  def transferred_to_sunday?(solemnity)
94
118
  @transfer_to_sunday.include?(solemnity)
95
119
  end
96
120
 
121
+ # First day of the liturgical year
122
+ #
123
+ # @return [Date]
97
124
  def start_date
98
125
  first_advent_sunday
99
126
  end
100
127
 
128
+ # Last day of the liturgical year
129
+ #
130
+ # @return [Date]
101
131
  def end_date
102
132
  Dates.first_advent_sunday(year + 1) - 1
103
133
  end
104
134
 
135
+ # Date range of the liturgical year
136
+ #
137
+ # @return [Range<Date>]
105
138
  def date_range
106
139
  start_date .. end_date
107
140
  end
108
141
 
142
+ # Check that the date belongs to the liturgical year.
143
+ # If it does not, throw exception.
144
+ #
145
+ # @param date [Date]
146
+ # @return [void]
147
+ # @raise [RangeError]
109
148
  def range_check(date)
110
149
  # necessary in order to handle Date correctly
111
150
  date = date.to_date if date.class != Date
@@ -115,6 +154,44 @@ module CalendariumRomanum
115
154
  end
116
155
  end
117
156
 
157
+ # @!method nativity
158
+ # @return [Date]
159
+ # @!method holy_family
160
+ # @return [Date]
161
+ # @!method mother_of_god
162
+ # @return [Date]
163
+ # @!method epiphany
164
+ # @return [Date]
165
+ # @!method baptism_of_lord
166
+ # @return [Date]
167
+ # @!method ash_wednesday
168
+ # @return [Date]
169
+ # @!method good_friday
170
+ # @return [Date]
171
+ # @!method holy_saturday
172
+ # @return [Date]
173
+ # @!method palm_sunday
174
+ # @return [Date]
175
+ # @!method easter_sunday
176
+ # @return [Date]
177
+ # @!method ascension
178
+ # @return [Date]
179
+ # @!method pentecost
180
+ # @return [Date]
181
+ # @!method holy_trinity
182
+ # @return [Date]
183
+ # @!method corpus_christi
184
+ # @return [Date]
185
+ # @!method mother_of_church
186
+ # @return [Date]
187
+ # @!method sacred_heart
188
+ # @return [Date]
189
+ # @!method christ_king
190
+ # @return [Date]
191
+ # @!method immaculate_heart
192
+ # @return [Date]
193
+ # @!method first_advent_sunday
194
+ # @return [Date]
118
195
  (celebrations.collect(&:date_method) + [:first_advent_sunday])
119
196
  .each do |feast|
120
197
  if SUNDAY_TRANSFERABLE_SOLEMNITIES.include? feast
@@ -132,7 +209,12 @@ module CalendariumRomanum
132
209
  end
133
210
  end
134
211
 
135
- # which liturgical season is it?
212
+ # Determine liturgical season for a given date
213
+ #
214
+ # @param date [Date]
215
+ # @return [Season]
216
+ # @raise [RangeError]
217
+ # if the given date doesn't belong to the liturgical year
136
218
  def season(date)
137
219
  range_check date
138
220
 
@@ -157,6 +239,10 @@ module CalendariumRomanum
157
239
  end
158
240
  end
159
241
 
242
+ # When the specified liturgical season begins
243
+ #
244
+ # @param s [Season]
245
+ # @return [Date]
160
246
  def season_beginning(s)
161
247
  case s
162
248
  when Seasons::ADVENT
@@ -174,6 +260,10 @@ module CalendariumRomanum
174
260
  end
175
261
  end
176
262
 
263
+ # Determine week of a season for a given date
264
+ #
265
+ # @param seasonn [Season]
266
+ # @param date [Date]
177
267
  def season_week(seasonn, date)
178
268
  week1_beginning = season_beginning = season_beginning(seasonn)
179
269
  unless season_beginning.sunday?
@@ -197,14 +287,23 @@ module CalendariumRomanum
197
287
  week
198
288
  end
199
289
 
290
+ # Retrieve temporale celebration for the given day
291
+ #
292
+ # @param date [Date]
293
+ # @return [Celebration]
294
+ # @since 0.6.0
200
295
  def [](date)
201
296
  @solemnities[date] || @feasts[date] || sunday(date) || @memorials[date] || ferial(date)
202
297
  end
203
298
 
204
- # returns a Celebration
205
- # scheduled for the given day
299
+ # Retrieve temporale celebration for the given day
206
300
  #
207
- # expected arguments: Date or two Integers (month, day)
301
+ # @overload get(date)
302
+ # @param date [Date]
303
+ # @overload get(month, day)
304
+ # @param month [Fixnum]
305
+ # @param day [Fixnum]
306
+ # @return (see #[])
208
307
  def get(*args)
209
308
  if args.size == 1 && args[0].is_a?(Date)
210
309
  date = args[0]
@@ -219,6 +318,8 @@ module CalendariumRomanum
219
318
  self[date]
220
319
  end
221
320
 
321
+ # @return [Boolean]
322
+ # @since 0.6.0
222
323
  def ==(b)
223
324
  self.class == b.class &&
224
325
  year == b.year &&