calendarium-romanum 0.5.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +50 -0
  5. data/.travis.yml +23 -0
  6. data/.yardopts +3 -0
  7. data/Appraisals +67 -0
  8. data/CHANGELOG.md +488 -0
  9. data/Gemfile +26 -0
  10. data/Gemfile.lock +95 -0
  11. data/README.md +601 -0
  12. data/Rakefile +27 -0
  13. data/bin/calendariumrom +3 -0
  14. data/calendarium-romanum.gemspec +31 -0
  15. data/config/locales/cs.yml +4 -0
  16. data/config/locales/en.yml +20 -14
  17. data/config/locales/es.yml +94 -0
  18. data/config/locales/fr.yml +6 -0
  19. data/config/locales/it.yml +6 -0
  20. data/config/locales/la.yml +6 -0
  21. data/config/locales/pt.yml +94 -0
  22. data/data/README.md +70 -24
  23. data/data/czech-brno-cs.txt +4 -6
  24. data/data/czech-budejovice-cs.txt +4 -6
  25. data/data/czech-cechy-cs.txt +4 -5
  26. data/data/czech-cs.txt +239 -235
  27. data/data/czech-hradec-cs.txt +3 -5
  28. data/data/czech-litomerice-cs.txt +5 -7
  29. data/data/czech-morava-cs.txt +4 -5
  30. data/data/czech-olomouc-cs.txt +2 -4
  31. data/data/czech-ostrava-cs.txt +3 -5
  32. data/data/czech-plzen-cs.txt +3 -5
  33. data/data/czech-praha-cs.txt +3 -4
  34. data/data/easter_dates.txt +67 -0
  35. data/data/universal-1969-la.txt +234 -0
  36. data/data/universal-en.txt +217 -211
  37. data/data/universal-es.txt +246 -0
  38. data/data/universal-fr.txt +217 -210
  39. data/data/universal-it.txt +217 -211
  40. data/data/universal-la.txt +217 -212
  41. data/data/universal-pt.txt +248 -0
  42. data/doc/data_readme.md +2 -0
  43. data/doc/images/class_diagram.png +0 -0
  44. data/doc/images/class_diagram.puml +44 -0
  45. data/doc/yard_readme.rdoc +76 -0
  46. data/lib/calendarium-romanum.rb +16 -2
  47. data/lib/calendarium-romanum/abstract_date.rb +15 -0
  48. data/lib/calendarium-romanum/calendar.rb +150 -33
  49. data/lib/calendarium-romanum/cli.rb +80 -100
  50. data/lib/calendarium-romanum/cli/comparator.rb +83 -0
  51. data/lib/calendarium-romanum/cli/date_parser.rb +30 -0
  52. data/lib/calendarium-romanum/cli/dumper.rb +68 -0
  53. data/lib/calendarium-romanum/cli/helper.rb +23 -0
  54. data/lib/calendarium-romanum/cli/querier.rb +73 -0
  55. data/lib/calendarium-romanum/cr.rb +16 -0
  56. data/lib/calendarium-romanum/data.rb +40 -8
  57. data/lib/calendarium-romanum/day.rb +187 -32
  58. data/lib/calendarium-romanum/enum.rb +41 -24
  59. data/lib/calendarium-romanum/enums.rb +127 -43
  60. data/lib/calendarium-romanum/errors.rb +1 -1
  61. data/lib/calendarium-romanum/ordinalizer.rb +10 -1
  62. data/lib/calendarium-romanum/perpetual_calendar.rb +58 -7
  63. data/lib/calendarium-romanum/rank.rb +39 -8
  64. data/lib/calendarium-romanum/rank_predicates.rb +43 -0
  65. data/lib/calendarium-romanum/sanctorale.rb +213 -23
  66. data/lib/calendarium-romanum/sanctorale_factory.rb +74 -3
  67. data/lib/calendarium-romanum/sanctorale_loader.rb +180 -0
  68. data/lib/calendarium-romanum/sanctorale_writer.rb +124 -0
  69. data/lib/calendarium-romanum/temporale.rb +222 -42
  70. data/lib/calendarium-romanum/temporale/celebration_factory.rb +68 -9
  71. data/lib/calendarium-romanum/temporale/date_helper.rb +85 -0
  72. data/lib/calendarium-romanum/temporale/dates.rb +52 -59
  73. data/lib/calendarium-romanum/temporale/easter_table.rb +27 -0
  74. data/lib/calendarium-romanum/temporale/extensions.rb +15 -0
  75. data/lib/calendarium-romanum/temporale/extensions/christ_eternal_priest.rb +16 -3
  76. data/lib/calendarium-romanum/temporale/extensions/dedication_before_all_saints.rb +73 -0
  77. data/lib/calendarium-romanum/transfers.rb +84 -24
  78. data/lib/calendarium-romanum/util.rb +21 -23
  79. data/lib/calendarium-romanum/version.rb +3 -2
  80. data/liturgical_law/1969_normae_universales.md +568 -0
  81. data/liturgical_law/1977_decretum_de_celebratione_baptismatis_domini.md +58 -0
  82. data/liturgical_law/1990_decretum_de_variatione_inducenda.md +67 -0
  83. data/liturgical_law/1998_notificatio_de_occurrentia.md +57 -0
  84. data/liturgical_law/2002_normae_universales.md +946 -0
  85. data/liturgical_law/2006_notification.md +37 -0
  86. data/liturgical_law/2012_declarationes.md +38 -0
  87. data/liturgical_law/2020_dubia_de_calendario_2022.md +100 -0
  88. data/liturgical_law/README.md +74 -0
  89. metadata +61 -38
  90. data/lib/calendarium-romanum/sanctoraleloader.rb +0 -122
  91. data/spec/abstract_date_spec.rb +0 -62
  92. data/spec/calendar_spec.rb +0 -559
  93. data/spec/celebration_factory_spec.rb +0 -16
  94. data/spec/celebration_spec.rb +0 -43
  95. data/spec/cli_spec.rb +0 -155
  96. data/spec/colour_spec.rb +0 -17
  97. data/spec/data_spec.rb +0 -23
  98. data/spec/date_parser_spec.rb +0 -68
  99. data/spec/date_spec.rb +0 -61
  100. data/spec/dates_spec.rb +0 -45
  101. data/spec/day_spec.rb +0 -108
  102. data/spec/enum_spec.rb +0 -51
  103. data/spec/i18n_spec.rb +0 -58
  104. data/spec/ordinalizer_spec.rb +0 -36
  105. data/spec/perpetual_calendar_spec.rb +0 -91
  106. data/spec/rank_spec.rb +0 -57
  107. data/spec/readme_spec.rb +0 -56
  108. data/spec/sanctorale_factory_spec.rb +0 -42
  109. data/spec/sanctorale_spec.rb +0 -191
  110. data/spec/sanctoraleloader_spec.rb +0 -176
  111. data/spec/season_spec.rb +0 -17
  112. data/spec/spec_helper.rb +0 -46
  113. data/spec/temporale_spec.rb +0 -572
@@ -0,0 +1,83 @@
1
+ require 'set'
2
+
3
+ module CalendariumRomanum
4
+ class CLI
5
+ # Compares two sanctorale data files, reports differences.
6
+ #
7
+ # @api private
8
+ class Comparator
9
+ include Helper
10
+
11
+ SUPPORTED_PROPERTIES = %i(rank colour symbol title)
12
+ DEFAULT_PROPERTIES = SUPPORTED_PROPERTIES - %i(title)
13
+
14
+ def initialize(properties = DEFAULT_PROPERTIES)
15
+ unless Set.new(properties) <= Set.new(SUPPORTED_PROPERTIES)
16
+ raise ArgumentError.new("Unsupported properties specified: #{properties} Only #{SUPPORTED_PROPERTIES} are supported")
17
+ end
18
+
19
+ @properties = properties
20
+ end
21
+
22
+ def call(path_a, path_b)
23
+ paths = [path_a, path_b]
24
+ sanctoralia = paths.collect {|source| sanctorale_from_path source }
25
+ names = paths.collect {|source| File.basename source }
26
+
27
+ differences_found = false
28
+ all_possible_dates.each do |d|
29
+ a, b = sanctoralia.collect {|sanctorale| sanctorale[d] }
30
+
31
+ 0.upto([a.size, b.size].max - 1) do |i|
32
+ ca = a[i]
33
+ cb = b[i]
34
+ compared = [ca, cb]
35
+
36
+ if compared.index(&:nil?)
37
+ differences_found = true
38
+ notnili = compared.index {|c| !c.nil? }
39
+
40
+ print date(d)
41
+ puts " only in #{names[notnili]}:"
42
+ puts celebration(compared[notnili])
43
+ puts
44
+ next
45
+ end
46
+
47
+ differences = @properties.select do |property|
48
+ ca.public_send(property) != cb.public_send(property)
49
+ end
50
+
51
+ next if differences.empty?
52
+ differences_found = true
53
+ print date(d)
54
+ puts " differs in #{differences.join(', ')}"
55
+ puts celebration(ca)
56
+ puts celebration(cb)
57
+ puts
58
+ end
59
+ end
60
+
61
+ !differences_found
62
+ end
63
+
64
+ private
65
+
66
+ def all_possible_dates
67
+ # a leap year must be chosen in order to iterate over
68
+ # all possible days of a Sanctorale
69
+ Util::Year.new(1990).each_day
70
+ end
71
+
72
+ def date(d)
73
+ "#{d.month}/#{d.day}"
74
+ end
75
+
76
+ def celebration(c)
77
+ symbol_chunk = c.symbol && "#{c.symbol} | "
78
+
79
+ "#{c.rank.priority} #{c.colour.symbol} | #{symbol_chunk}#{c.title}"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,30 @@
1
+ module CalendariumRomanum
2
+ class CLI
3
+ # @api private
4
+ class DateParser
5
+ def self.parse(date_str)
6
+ /(?<year>\d{4})([\/-](?<month>\d{1,2})([\/-](?<day>\d{1,2}))?)?/.match(date_str) do |m|
7
+ date_segments =
8
+ %i(year month day)
9
+ .collect {|name| m[name] }
10
+ .compact
11
+ .collect(&:to_i)
12
+
13
+ build_range(*date_segments)
14
+ end || raise(ArgumentError.new('Unparseable date'))
15
+ end
16
+
17
+ def self.build_range(*args)
18
+ case args.size
19
+ when 1
20
+ Util::Year.new(*args)
21
+ when 2
22
+ Util::Month.new(*args)
23
+ else
24
+ date = Date.new(*args)
25
+ date..date
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,68 @@
1
+ module CalendariumRomanum
2
+ class CLI
3
+ # Produces a condensed text representation of a Calendar, used in regression tests.
4
+ # Not loaded by default by +require 'calendarium-romanum'+
5
+ #
6
+ # @api private
7
+ class Dumper
8
+ def initialize(io=STDOUT)
9
+ @io = io
10
+ end
11
+
12
+ # Dumps +calendar+. If +other_calendars+ are specified, dumps an alternative entry
13
+ # for any date for which any of +other_calendars+ differs from +calendar+.
14
+ def call(calendar, *other_calendars)
15
+ @io.puts "Calendar for liturgical year #{calendar.year}"
16
+ calendar.each do |day|
17
+ dump_day(day)
18
+
19
+ other_calendars.each do |cal|
20
+ day2 = cal[day.date]
21
+ if day2 != day
22
+ @io.print 'or '
23
+ dump_day(day2)
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ # Produces the dump used for regression tests for the specified +year+.
30
+ def regression_tests_dump(year)
31
+ sanctorale = Data::GENERAL_ROMAN_LATIN.load
32
+ calendar = Calendar.new(
33
+ year,
34
+ sanctorale,
35
+ vespers: true
36
+ )
37
+ calendar_with_transfers = Calendar.new(
38
+ Temporale.new(year, transfer_to_sunday: Temporale::SUNDAY_TRANSFERABLE_SOLEMNITIES),
39
+ sanctorale,
40
+ vespers: true
41
+ )
42
+ calendar_with_extensions = Calendar.new(
43
+ Temporale.new(year, extensions: Temporale::Extensions.all),
44
+ sanctorale,
45
+ vespers: true
46
+ )
47
+
48
+ I18n.with_locale(:la) do
49
+ call(
50
+ calendar,
51
+ calendar_with_transfers,
52
+ calendar_with_extensions,
53
+ )
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def dump_day(day)
60
+ @io.puts [day.date, day.season.symbol, day.season_week, !day.vespers.nil?].join(' ')
61
+
62
+ day.celebrations.each do |c|
63
+ @io.puts ['-', c.title.inspect, c.rank.priority, c.colour.symbol, c.symbol, (c.date && "#{c.date.month}/#{c.date.day}"), c.cycle].join(' ')
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,23 @@
1
+ module CalendariumRomanum
2
+ class CLI
3
+ # Mixin providing helper methods used by multiple CLI-related classes.
4
+ #
5
+ # @api private
6
+ module Helper
7
+ def sanctorale_from_path(path)
8
+ loader = SanctoraleLoader.new
9
+
10
+ if path == '-'
11
+ loader.load(STDIN)
12
+ else
13
+ loader.load_from_file(path)
14
+ end
15
+ end
16
+
17
+ def die!(message, code = 1)
18
+ STDERR.puts message
19
+ exit code
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,73 @@
1
+ module CalendariumRomanum
2
+ class CLI
3
+ # "Queries" a Calendar.
4
+ # Builds liturgical calendar according to the specified options and
5
+ # prints calendar entries for the specified period.
6
+ #
7
+ # @api private
8
+ class Querier
9
+ include Helper
10
+
11
+ def initialize(locale: :en, calendar: 'universal-en')
12
+ @locale = locale
13
+ @calendar = calendar
14
+ end
15
+
16
+ def call(date_str = nil)
17
+ I18n.locale = @locale
18
+
19
+ pcal = PerpetualCalendar.new sanctorale: sanctorale
20
+
21
+ today = Date.today
22
+ date_range = today..today
23
+
24
+ if date_str
25
+ begin
26
+ date_range = DateParser.parse(date_str)
27
+ rescue ArgumentError
28
+ die! 'Invalid date.'
29
+ end
30
+ end
31
+
32
+ date_range.each do |day|
33
+ print_single_date(pcal, day)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def print_single_date(calendar, date)
40
+ day = calendar.day date
41
+
42
+ puts date
43
+ puts "season: #{day.season.name}"
44
+ puts
45
+
46
+ rank_length = day.celebrations.collect {|c| c.rank.short_desc.nil? ? 0 : c.rank.short_desc.size }.max
47
+ day.celebrations.each do |c|
48
+ if [Ranks::PRIMARY, Ranks::TRIDUUM].include? c.rank
49
+ puts c.title
50
+ elsif !c.rank.short_desc.nil?
51
+ print c.rank.short_desc.rjust(rank_length)
52
+ print ' : '
53
+ puts c.title
54
+ end
55
+ end
56
+ end
57
+
58
+ def sanctorale
59
+ if File.exist?(@calendar)
60
+ begin
61
+ sanctorale_from_path(@calendar)
62
+ rescue CalendariumRomanum::InvalidDataError
63
+ die! 'Invalid file format.'
64
+ end
65
+ elsif data_file = Data[@calendar]
66
+ data_file.load
67
+ else
68
+ 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."
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,16 @@
1
+ require 'calendarium-romanum'
2
+
3
+ # The module name {CalendariumRomanum} is quite long,
4
+ # hence constant +CR+ is provided as a convenient shortcut.
5
+ # It is _not_ loaded by +require 'calendarium-romanum'+,
6
+ # must be required explicitly +require 'calendarium-romanum/cr'+ -
7
+ # because there's a good chance
8
+ # that the short constant name clashes with a constant
9
+ # defined by some other code.
10
+ #
11
+ # @example
12
+ # require 'calendarium-romanum/cr'
13
+ #
14
+ # calendar = CR::Calendar.new 2000
15
+ # @since 0.7.0
16
+ CR = CalendariumRomanum
@@ -1,8 +1,17 @@
1
1
  module CalendariumRomanum
2
- # allows easy access to bundled data files
3
- class Data < Enum
2
+ # Allows easy access to bundled data files
3
+ #
4
+ # @example
5
+ # sanctorale = CalendariumRomanum::Data::GENERAL_ROMAN_LATIN.load
6
+ module Data
7
+ extend Enum
4
8
 
5
9
  class SanctoraleFile
10
+ # This class is not intended to be initialized by client code -
11
+ # it's sole purpose is to provide functionality for easy
12
+ # loading of the bundled sanctorale data files.
13
+ #
14
+ # @api private
6
15
  def initialize(base_name)
7
16
  @siglum = base_name.sub(/\.txt$/, '')
8
17
  @path = File.expand_path('../../data/' + base_name, File.dirname(__FILE__))
@@ -10,19 +19,42 @@ module CalendariumRomanum
10
19
 
11
20
  attr_reader :siglum, :path
12
21
 
22
+ # Load the data file
23
+ #
24
+ # @return [Sanctorale]
13
25
  def load
14
- SanctoraleLoader.new.load_from_file(@path)
26
+ SanctoraleLoader.new.load_from_file(path)
27
+ end
28
+
29
+ # Load the data file and all it's parents
30
+ #
31
+ # @return [Sanctorale]
32
+ # @since 0.7.0
33
+ def load_with_parents
34
+ SanctoraleFactory.load_with_parents(path)
15
35
  end
16
36
  end
17
37
 
38
+ GENERAL_ROMAN_LATIN = SanctoraleFile.new('universal-la.txt')
39
+ GENERAL_ROMAN_LATIN_1969 = SanctoraleFile.new('universal-1969-la.txt')
40
+ GENERAL_ROMAN_ENGLISH = SanctoraleFile.new('universal-en.txt')
41
+ GENERAL_ROMAN_FRENCH = SanctoraleFile.new('universal-fr.txt')
42
+ GENERAL_ROMAN_ITALIAN = SanctoraleFile.new('universal-it.txt')
43
+ GENERAL_ROMAN_PORTUGUESE = SanctoraleFile.new('universal-pt.txt')
44
+ GENERAL_ROMAN_SPANISH = SanctoraleFile.new('universal-es.txt')
45
+ CZECH = SanctoraleFile.new('czech-cs.txt')
46
+
18
47
  values(index_by: :siglum) do
19
48
  # only calendars of broader interest have constants defined
20
49
  [
21
- GENERAL_ROMAN_LATIN = SanctoraleFile.new('universal-la.txt'),
22
- GENERAL_ROMAN_ENGLISH = SanctoraleFile.new('universal-en.txt'),
23
- GENERAL_ROMAN_FRENCH = SanctoraleFile.new('universal-fr.txt'),
24
- GENERAL_ROMAN_ITALIAN = SanctoraleFile.new('universal-it.txt'),
25
- CZECH = SanctoraleFile.new('czech-cs.txt')
50
+ GENERAL_ROMAN_LATIN,
51
+ GENERAL_ROMAN_LATIN_1969,
52
+ GENERAL_ROMAN_ENGLISH,
53
+ GENERAL_ROMAN_FRENCH,
54
+ GENERAL_ROMAN_ITALIAN,
55
+ GENERAL_ROMAN_PORTUGUESE,
56
+ GENERAL_ROMAN_SPANISH,
57
+ CZECH,
26
58
  ] \
27
59
  +
28
60
  %w(
@@ -1,9 +1,16 @@
1
- require 'forwardable'
2
-
3
1
  module CalendariumRomanum
4
2
 
5
- # information on one particular day of the liturgical year
3
+ # Information on one particular day of the liturgical year
6
4
  class Day
5
+ # Note: despite of all constructor arguments being nullable,
6
+ # instances returned by {Calendar} always have all of them set,
7
+ # the only exception being +vespers+.
8
+ #
9
+ # @param date [Date, nil]
10
+ # @param season [Season, nil]
11
+ # @param season_week [Integer, nil]
12
+ # @param celebrations [Array<Celebration>, nil]
13
+ # @param vespers [Celebration, nil]
7
14
  def initialize(date: nil, season: nil, season_week: nil, celebrations: nil, vespers: nil)
8
15
  @date = date
9
16
  @season = season
@@ -12,25 +19,50 @@ module CalendariumRomanum
12
19
  @vespers = vespers
13
20
  end
14
21
 
22
+ # @return [Date]
15
23
  attr_reader :date
16
24
 
25
+ # Weekday as integer (Sunday is 0)
26
+ #
27
+ # @return [Integer]
17
28
  def weekday
18
29
  date.wday
19
30
  end
20
31
 
21
- # one of the Seasons
32
+ # Weekday as internationalized string
33
+ #
34
+ # @return [String]
35
+ # @since 0.7.0
36
+ def weekday_name
37
+ I18n.t(date.wday, scope: 'weekday')
38
+ end
39
+
40
+ # @return [Season]
22
41
  attr_reader :season
23
42
 
24
- # week of the season (Integer)
43
+ # Week of the season
44
+ #
45
+ # @return [Integer]
25
46
  attr_reader :season_week
26
47
 
27
- # an Array of Celebrations, possibly empty
48
+ # List of celebrations for the given day.
49
+ #
50
+ # In tests and other "less-standard" situations the array
51
+ # may be empty, but it's never empty for instances
52
+ # returned by {Calendar}.
53
+ #
54
+ # @return [Array<Celebration>]
28
55
  attr_reader :celebrations
29
56
 
30
- # nil or Celebration from which first Vespers are celebrated
31
- # instead of Vespers of the day's other Celebrations.
32
- # Please note that Calendar by default *doesn't* populate
33
- # Vespers, - it's an opt-in feature.
57
+ # {Celebration} whose first Vespers are celebrated
58
+ # in place of Vespers of the day's {Celebration}(s).
59
+ # Please note that {Calendar} by default _doesn't_ populate
60
+ # Vespers, - it's an opt-in feature
61
+ # (see {Calendar#initialize}, {Calendar#populates_vespers?},
62
+ # {Calendar#day}).
63
+ #
64
+ # @return [Celebration, nil]
65
+ # @since 0.5.0
34
66
  attr_reader :vespers
35
67
 
36
68
  def ==(other)
@@ -44,28 +76,97 @@ module CalendariumRomanum
44
76
 
45
77
  # Are the day's Vespers suppressed in favour of first Vespers
46
78
  # of a Sunday or solemnity?
79
+ #
80
+ # @return [Boolean]
47
81
  def vespers_from_following?
48
82
  !vespers.nil?
49
83
  end
84
+
85
+ # String representation of the instance listing it's contents.
86
+ # Intended mostly for debugging purposes.
87
+ #
88
+ # @return [String]
89
+ # @since 0.7.0
90
+ def to_s
91
+ celebrations_string = '['
92
+ celebrations.each do |c|
93
+ celebrations_string << c.to_s + ', '
94
+ end
95
+ celebrations_string = celebrations_string.chomp(', ') << ']'
96
+ "#<#{self.class.name} @date=#{date} @season=#{season} @season_week=#{season_week} celebrations=#{celebrations_string} vespers=#{vespers.inspect}>"
97
+ end
50
98
  end
51
99
 
52
- # information on one particular celebration of the liturgical year
100
+ # One particular celebration of the liturgical year
53
101
  # (like a Sunday, feast or memorial);
54
- # some days have no (ferial office is used), some have one,
55
- # some have more among which one may and may not be chosen
102
+ # some days have one,
103
+ # some have more among which one is to be chosen
56
104
  class Celebration
57
- extend Forwardable
105
+ include RankPredicates
106
+
107
+ # All arguments can be passed either as positional or keyword arguments.
108
+ # In case of conflict keyword arguments win.
109
+ # @example
110
+ # Celebration.new('Lost title', title: 'Winning title') # will have title 'Winning title'
111
+ #
112
+ # @param title [String|Proc]
113
+ # Celebration title/name.
114
+ # If a +Proc+ is passed, it is expected not to receive
115
+ # arguments and to return a +String+.
116
+ # (Used for celebration titles which have to be
117
+ # internationalizable - the +Proc+ is called whenever
118
+ # {#title} is invoked, which allows the value to vary
119
+ # depending e.g. on state of the +Proc+ or some
120
+ # global setting - like +I18n.locale+ - it may access.)
121
+ # @param rank [Rank] Celebration rank
122
+ # @param colour [Colour] Liturgical colour
123
+ # @param symbol [Symbol, nil]
124
+ # Unique machine-readable identifier of the celebration
125
+ # @param date [AbstractDate, nil]
126
+ # Normal fixed date of the celebration
127
+ # @param cycle [:sanctorale, :temporale]
128
+ # Cycle the celebration belongs to
129
+ def initialize(title = '', rank = Ranks::FERIAL, colour = Colours::GREEN, symbol = nil, date = nil, cycle = :sanctorale, sunday = false, **kwargs)
130
+ @title = kwargs.delete(:title) || title
131
+ @rank = kwargs.delete(:rank) || rank
132
+ @colour = kwargs.delete(:colour) || kwargs.delete(:color) || colour
133
+ @symbol = kwargs.delete(:symbol) || symbol
134
+ @date = kwargs.delete(:date) || date
135
+ @cycle = kwargs.delete(:cycle) || cycle
136
+ @sunday = kwargs.delete(:sunday) || sunday
137
+
138
+ unless kwargs.empty?
139
+ raise ArgumentError.new('Unexpected keyword arguments: ' + kwargs.keys.inspect)
140
+ end
141
+
142
+ if @sunday && ![Ranks::SUNDAY_UNPRIVILEGED, Ranks::PRIMARY].include?(@rank)
143
+ raise ArgumentError.new("Rank #{@rank} cannot be Sunday")
144
+ end
145
+ end
58
146
 
59
- def initialize(title = '', rank = Ranks::FERIAL, colour = Colours::GREEN, symbol = nil)
60
- @title = title
61
- @rank = rank
62
- @colour = colour
63
- @symbol = symbol
147
+ # Build a new instance using the receiver's attributes
148
+ # for all properties for which (a non-nil) value was not passed.
149
+ #
150
+ # @return [Celebration]
151
+ # @since 0.5.0
152
+ def change(title: nil, rank: nil, colour: nil, color: nil, symbol: nil, date: nil, cycle: nil, sunday: nil)
153
+ self.class.new(
154
+ title: title || self.title,
155
+ rank: rank || self.rank,
156
+ colour: colour || color || self.colour,
157
+ symbol: symbol || self.symbol,
158
+ date: date || self.date,
159
+ cycle: cycle || self.cycle,
160
+ sunday: sunday || @sunday
161
+ )
64
162
  end
65
163
 
164
+ # @return [Rank]
66
165
  attr_reader :rank
67
- def_delegators :@rank, :solemnity?, :feast?, :memorial?
68
166
 
167
+ # Feast title/name
168
+ #
169
+ # @return [String]
69
170
  def title
70
171
  if @title.respond_to? :call
71
172
  @title.call
@@ -74,25 +175,79 @@ module CalendariumRomanum
74
175
  end
75
176
  end
76
177
 
178
+ # Liturgical colour
179
+ #
180
+ # @return [Colour]
77
181
  attr_reader :colour
78
182
  alias color colour
79
183
 
184
+ # Symbol uniquely identifying the celebration
185
+ #
186
+ # @return [Symbol, nil]
187
+ # @since 0.5.0
80
188
  attr_reader :symbol
81
189
 
82
- def ==(other)
83
- self.class == other.class &&
84
- title == other.title &&
85
- rank == other.rank &&
86
- colour == other.colour
190
+ # Usual date of the celebration.
191
+ #
192
+ # Only set for celebrations with fixed date.
193
+ # (Only) In case of solemnities it may happen that
194
+ # {Celebration#date} differs from {Day#date} due to
195
+ # transfer of an impeded solemnity.
196
+ #
197
+ # @return [AbstractDate, nil]
198
+ # @since 0.6.0
199
+ attr_reader :date
200
+
201
+ # Describes the celebration as belonging either to the
202
+ # temporale or sanctorale cycle
203
+ #
204
+ # @return [:sanctorale, :temporale]
205
+ # @since 0.6.0
206
+ attr_reader :cycle
207
+
208
+ def ==(b)
209
+ self.class == b.class &&
210
+ title == b.title &&
211
+ rank == b.rank &&
212
+ colour == b.colour &&
213
+ symbol == b.symbol &&
214
+ date == b.date &&
215
+ cycle == b.cycle
87
216
  end
88
217
 
89
- def change(title: nil, rank: nil, colour: nil, color: nil, symbol: nil)
90
- self.class.new(
91
- title || self.title,
92
- rank || self.rank,
93
- colour || color || self.colour,
94
- symbol || self.symbol
95
- )
218
+ # Does the celebration belong to the temporale cycle?
219
+ #
220
+ # @return [Boolean]
221
+ # @since 0.6.0
222
+ def temporale?
223
+ cycle == :temporale
224
+ end
225
+
226
+ # Does the celebration belong to the sanctorale cycle?
227
+ #
228
+ # @return [Boolean]
229
+ # @since 0.6.0
230
+ def sanctorale?
231
+ cycle == :sanctorale
232
+ end
233
+
234
+ # Is the celebration a Sunday?
235
+ #
236
+ # Please note that for "privileged Sundays" true is returned, while {Rank#sunday?}
237
+ # returns false (because not all celebrations of that rank are Sundays).
238
+ #
239
+ # @return [Boolean]
240
+ def sunday?
241
+ rank.sunday? || @sunday
242
+ end
243
+
244
+ # String representation of the object's contents
245
+ # (not very pretty, intended mostly for development inspections).
246
+ #
247
+ # @return [String]
248
+ # @since 0.7.0
249
+ def to_s
250
+ "#<#{self.class.name} @title=\"#{title}\" @rank=#{rank} @colour=#{colour} symbol=#{symbol.inspect} date=#{date.inspect} cycle=#{cycle.inspect}>"
96
251
  end
97
252
  end
98
253
  end