calendarium-romanum 0.3.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +47 -0
  5. data/.travis.yml +20 -0
  6. data/.yardopts +3 -0
  7. data/CHANGELOG.md +340 -0
  8. data/Gemfile +25 -0
  9. data/Gemfile.lock +86 -0
  10. data/README.md +515 -0
  11. data/Rakefile +9 -0
  12. data/bin/calendariumrom +4 -1
  13. data/calendarium-romanum.gemspec +26 -0
  14. data/config/locales/cs.yml +17 -1
  15. data/config/locales/en.yml +28 -14
  16. data/config/locales/es.yml +90 -0
  17. data/config/locales/fr.yml +90 -0
  18. data/config/locales/it.yml +18 -2
  19. data/config/locales/la.yml +17 -1
  20. data/data/README.md +43 -1
  21. data/data/czech-brno-cs.txt +4 -6
  22. data/data/czech-budejovice-cs.txt +4 -6
  23. data/data/czech-cechy-cs.txt +4 -5
  24. data/data/czech-cs.txt +237 -234
  25. data/data/czech-hradec-cs.txt +3 -5
  26. data/data/czech-litomerice-cs.txt +5 -7
  27. data/data/czech-morava-cs.txt +4 -5
  28. data/data/czech-olomouc-cs.txt +2 -4
  29. data/data/czech-ostrava-cs.txt +3 -5
  30. data/data/czech-plzen-cs.txt +3 -5
  31. data/data/czech-praha-cs.txt +3 -4
  32. data/data/universal-en.txt +214 -211
  33. data/data/universal-es.txt +243 -0
  34. data/data/universal-fr.txt +243 -0
  35. data/data/universal-it.txt +214 -211
  36. data/data/universal-la.txt +214 -210
  37. data/doc/data_readme.md +2 -0
  38. data/doc/images/class_diagram.png +0 -0
  39. data/doc/images/class_diagram.puml +44 -0
  40. data/doc/yard_readme.rdoc +76 -0
  41. data/lib/calendarium-romanum.rb +30 -21
  42. data/lib/calendarium-romanum/abstract_date.rb +12 -0
  43. data/lib/calendarium-romanum/calendar.rb +207 -52
  44. data/lib/calendarium-romanum/cli.rb +101 -52
  45. data/lib/calendarium-romanum/cr.rb +16 -0
  46. data/lib/calendarium-romanum/data.rb +46 -18
  47. data/lib/calendarium-romanum/day.rb +202 -20
  48. data/lib/calendarium-romanum/enum.rb +24 -5
  49. data/lib/calendarium-romanum/enums.rb +102 -36
  50. data/lib/calendarium-romanum/errors.rb +4 -0
  51. data/lib/calendarium-romanum/ordinalizer.rb +30 -6
  52. data/lib/calendarium-romanum/perpetual_calendar.rb +97 -0
  53. data/lib/calendarium-romanum/rank.rb +43 -6
  54. data/lib/calendarium-romanum/sanctorale.rb +170 -24
  55. data/lib/calendarium-romanum/sanctorale_factory.rb +74 -3
  56. data/lib/calendarium-romanum/sanctorale_loader.rb +176 -0
  57. data/lib/calendarium-romanum/temporale.rb +251 -119
  58. data/lib/calendarium-romanum/temporale/celebration_factory.rb +106 -0
  59. data/lib/calendarium-romanum/temporale/dates.rb +117 -36
  60. data/lib/calendarium-romanum/temporale/extensions/christ_eternal_priest.rb +18 -6
  61. data/lib/calendarium-romanum/transfers.rb +20 -1
  62. data/lib/calendarium-romanum/util.rb +36 -3
  63. data/lib/calendarium-romanum/version.rb +5 -1
  64. metadata +29 -21
  65. data/lib/calendarium-romanum/sanctoraleloader.rb +0 -115
  66. data/spec/abstract_date_spec.rb +0 -62
  67. data/spec/calendar_spec.rb +0 -352
  68. data/spec/cli_spec.rb +0 -26
  69. data/spec/data_spec.rb +0 -23
  70. data/spec/date_spec.rb +0 -61
  71. data/spec/dates_spec.rb +0 -45
  72. data/spec/enum_spec.rb +0 -51
  73. data/spec/i18n_spec.rb +0 -59
  74. data/spec/rank_spec.rb +0 -42
  75. data/spec/readme_spec.rb +0 -52
  76. data/spec/sanctorale_factory_spec.rb +0 -42
  77. data/spec/sanctorale_spec.rb +0 -167
  78. data/spec/sanctoraleloader_spec.rb +0 -171
  79. data/spec/spec_helper.rb +0 -35
  80. data/spec/temporale_spec.rb +0 -500
@@ -1,12 +1,18 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module CalendariumRomanum
4
- # Utility class for definition of enumerated "types"
4
+ # Each subclass encapsulates a finite set of value objects.
5
+ #
6
+ # @abstract
5
7
  class Enum
6
8
  class << self
7
9
  extend Forwardable
8
10
 
9
- def values(index_by: nil, &blk)
11
+ # @api private
12
+ # @param index_by
13
+ # specifies which value objects' property contains
14
+ # unique internal identifier for use with {.[]}
15
+ def values(index_by: nil)
10
16
  defined?(@indexed) && raise(RuntimeError.new('initialized repeatedly'))
11
17
 
12
18
  @indexed = {}
@@ -22,11 +28,24 @@ module CalendariumRomanum
22
28
  @indexed.freeze
23
29
  end
24
30
 
25
- def all
26
- @all
27
- end
31
+ # Returns all contained value objects
32
+ #
33
+ # @return [Array]
34
+ attr_reader :all
28
35
 
36
+ # Enumerates contained value objects
37
+ #
38
+ # @!method each
39
+ # @yield value object
40
+ # @return [void]
29
41
  def_delegators :@all, :each
42
+
43
+ # Allows accessing contained value objects by their
44
+ # internal unique identifiers
45
+ #
46
+ # @!method [](identifier)
47
+ # @param identifier
48
+ # @return value object or nil
30
49
  def_delegators :@indexed, :[]
31
50
  end
32
51
  end
@@ -1,76 +1,142 @@
1
1
  module CalendariumRomanum
2
+ # Methods shared by most value objects defined by the gem
3
+ module ValueObjectInterface
4
+ # Machine-readable internal representation of the value
5
+ #
6
+ # @return [Symbol]
7
+ attr_reader :symbol
8
+ alias to_sym symbol
9
+
10
+ # Internationalized, human-readable name
11
+ #
12
+ # @return [String]
13
+ def name
14
+ I18n.t @i18n_key
15
+ end
2
16
 
17
+ # String representation of the contents for debugging purposes
18
+ #
19
+ # @return [String]
20
+ def to_s
21
+ "#<#{self.class.name} #{symbol}>"
22
+ end
23
+ end
24
+
25
+ # Represents a liturgical colour
3
26
  class Colour
27
+ include ValueObjectInterface
28
+
4
29
  def initialize(symbol)
5
30
  @symbol = symbol
31
+ @i18n_key = "colour.#{@symbol}"
6
32
  end
7
-
8
- attr_reader :symbol
9
- alias to_sym symbol
10
33
  end
11
34
 
35
+ # Standard set of liturgical colours
12
36
  class Colours < Enum
13
- values do
37
+ GREEN = Colour.new(:green)
38
+ VIOLET = Colour.new(:violet)
39
+ WHITE = Colour.new(:white)
40
+ RED = Colour.new(:red)
41
+
42
+ values(index_by: :symbol) do
14
43
  [
15
- GREEN = Colour.new(:green),
16
- VIOLET = Colour.new(:violet),
17
- WHITE = Colour.new(:white),
18
- RED = Colour.new(:red)
44
+ GREEN,
45
+ VIOLET,
46
+ WHITE,
47
+ RED
19
48
  ]
20
49
  end
21
50
  end
22
51
 
52
+ # Convenience alias (American English spelling)
23
53
  Colors = Colours
24
54
 
55
+ # Liturgical season
25
56
  class Season
57
+ include ValueObjectInterface
58
+
59
+ # @param symbol [Symbol] internal identifier
60
+ # @param colour [Colour]
61
+ # liturgical colour of the season's Sundays and ferials
26
62
  def initialize(symbol, colour)
27
63
  @symbol = symbol
28
64
  @colour = colour
65
+ @i18n_key = "temporale.season.#{@symbol}"
29
66
  end
30
67
 
31
- attr_reader :symbol, :colour
32
- alias to_sym symbol
68
+ # Liturgical colour of the season's Sundays and ferials
69
+ #
70
+ # @return [Colour]
71
+ attr_reader :colour
33
72
  end
34
73
 
74
+ # Standard set of liturgical seasons
35
75
  class Seasons < Enum
36
- values do
76
+ ADVENT = Season.new(:advent, Colours::VIOLET)
77
+ CHRISTMAS = Season.new(:christmas, Colours::WHITE)
78
+ LENT = Season.new(:lent, Colours::VIOLET)
79
+ EASTER = Season.new(:easter, Colours::WHITE)
80
+ ORDINARY = Season.new(:ordinary, Colours::GREEN)
81
+
82
+ values(index_by: :symbol) do
37
83
  [
38
- ADVENT = Season.new(:advent, Colours::VIOLET),
39
- CHRISTMAS = Season.new(:christmas, Colours::WHITE),
40
- LENT = Season.new(:lent, Colours::VIOLET),
41
- EASTER = Season.new(:easter, Colours::WHITE),
42
- ORDINARY = Season.new(:ordinary, Colours::GREEN)
84
+ ADVENT,
85
+ CHRISTMAS,
86
+ LENT,
87
+ EASTER,
88
+ ORDINARY,
43
89
  ]
44
90
  end
45
91
  end
46
92
 
47
- LECTIONARY_CYCLES = [:A, :B, :C]
93
+ # Sunday lectionary cycles.
94
+ # Values returned by {Calendar#lectionary}
95
+ LECTIONARY_CYCLES = [:A, :B, :C].freeze
48
96
 
49
- # ranks of celebrations
97
+ # Celebration ranks as specified in the Table of Liturgical Days
50
98
  class Ranks < Enum
99
+ TRIDUUM = Rank.new(1.1, 'rank.1_1')
100
+ PRIMARY = Rank.new(1.2, 'rank.1_2') # description may not be exact
101
+ SOLEMNITY_GENERAL = Rank.new(1.3, 'rank.1_3', 'rank.short.solemnity') # description may not be exact
102
+ SOLEMNITY_PROPER = Rank.new(1.4, 'rank.1_4', 'rank.short.solemnity')
103
+
104
+ FEAST_LORD_GENERAL = Rank.new(2.5, 'rank.2_5', 'rank.short.feast')
105
+ SUNDAY_UNPRIVILEGED = Rank.new(2.6, 'rank.2_6', 'rank.short.sunday')
106
+ FEAST_GENERAL = Rank.new(2.7, 'rank.2_7', 'rank.short.feast')
107
+ FEAST_PROPER = Rank.new(2.8, 'rank.2_8', 'rank.short.feast')
108
+ FERIAL_PRIVILEGED = Rank.new(2.9, 'rank.2_9', 'rank.short.ferial')
109
+
110
+ MEMORIAL_GENERAL = Rank.new(3.10, 'rank.3_10', 'rank.short.memorial')
111
+ MEMORIAL_PROPER = Rank.new(3.11, 'rank.3_11', 'rank.short.memorial')
112
+ MEMORIAL_OPTIONAL = Rank.new(3.12, 'rank.3_12', 'rank.short.memorial_opt')
113
+ FERIAL = Rank.new(3.13, 'rank.3_13', 'rank.short.ferial')
114
+ # Not included as a celebration rank on it's own
115
+ # in the Table of Liturgical Days
116
+ COMMEMORATION = Rank.new(4.0, 'rank.4_0', 'rank.short.commemoration')
117
+
51
118
  values(index_by: :priority) do
52
119
  # Values are at the same time references to sections
53
120
  # of the Table of Liturgical Days.
54
121
  # The lower value, the higher rank.
55
122
  [
56
- TRIDUUM = Rank.new(1.1, 'rank.1_1'),
57
- PRIMARY = Rank.new(1.2, 'rank.1_2'), # description may not be exact
58
- SOLEMNITY_GENERAL = Rank.new(1.3, 'rank.1_3', 'rank.short.solemnity'), # description may not be exact
59
- SOLEMNITY_PROPER = Rank.new(1.4, 'rank.1_4', 'rank.short.solemnity'),
60
-
61
- FEAST_LORD_GENERAL = Rank.new(2.5, 'rank.2_5', 'rank.short.feast'),
62
- SUNDAY_UNPRIVILEGED = Rank.new(2.6, 'rank.2_6', 'rank.short.sunday'),
63
- FEAST_GENERAL = Rank.new(2.7, 'rank.2_7', 'rank.short.feast'),
64
- FEAST_PROPER = Rank.new(2.8, 'rank.2_8', 'rank.short.feast'),
65
- FERIAL_PRIVILEGED = Rank.new(2.9, 'rank.2_9', 'rank.short.ferial'),
66
-
67
- MEMORIAL_GENERAL = Rank.new(3.10, 'rank.3_10', 'rank.short.memorial'),
68
- MEMORIAL_PROPER = Rank.new(3.11, 'rank.3_11', 'rank.short.memorial'),
69
- MEMORIAL_OPTIONAL = Rank.new(3.12, 'rank.3_12', 'rank.short.memorial_opt'),
70
- FERIAL = Rank.new(3.13, 'rank.3_13', 'rank.short.ferial'),
71
- # not included as a celebration rank on it's own
72
- # in the Table of Liturgical Days
73
- COMMEMORATION = Rank.new(4.0, 'rank.4_0', 'rank.short.commemoration')
123
+ TRIDUUM,
124
+ PRIMARY,
125
+ SOLEMNITY_GENERAL,
126
+ SOLEMNITY_PROPER,
127
+
128
+ FEAST_LORD_GENERAL,
129
+ SUNDAY_UNPRIVILEGED,
130
+ FEAST_GENERAL,
131
+ FEAST_PROPER,
132
+ FERIAL_PRIVILEGED,
133
+
134
+ MEMORIAL_GENERAL,
135
+ MEMORIAL_PROPER,
136
+ MEMORIAL_OPTIONAL,
137
+ FERIAL,
138
+
139
+ COMMEMORATION,
74
140
  ]
75
141
  end
76
142
  end
@@ -0,0 +1,4 @@
1
+ module CalendariumRomanum
2
+ # Thrown by {SanctoraleLoader} on attempt to load invalid data
3
+ class InvalidDataError < RuntimeError; end
4
+ end
@@ -1,9 +1,18 @@
1
1
  require 'roman-numerals'
2
2
 
3
3
  module CalendariumRomanum
4
- # Knows how to produce localized ordinals
4
+ # Knows how to produce localized ordinals.
5
+ #
6
+ # Used by {Temporale} for building names of Sundays and ferials.
5
7
  class Ordinalizer
6
8
  class << self
9
+ # @param number [Fixnum] number to build ordinal for
10
+ # @param locale [Symbol,nil]
11
+ # locale; +I18n.locale+ (i.e. the `i18n` gem's current locale)
12
+ # is used if not provided
13
+ # @return [String, Fixnum]
14
+ # ordinal, or unchanged +number+ if +Ordinalizer+ cannot
15
+ # build ordinals for the given locale
7
16
  def ordinal(number, locale: nil)
8
17
  locale ||= I18n.locale
9
18
 
@@ -12,7 +21,8 @@ module CalendariumRomanum
12
21
  "#{number}."
13
22
  when :en
14
23
  english_ordinal(number)
15
- # when :it # TODO
24
+ when :fr
25
+ french_ordinal(number)
16
26
  when :la, :it
17
27
  RomanNumerals.to_roman number
18
28
  else
@@ -20,18 +30,32 @@ module CalendariumRomanum
20
30
  end
21
31
  end
22
32
 
33
+ private
34
+
23
35
  def english_ordinal(number)
24
- case number
36
+ modulo = number % 10
37
+ modulo = 9 if number / 10 == 1
38
+
39
+ case modulo
25
40
  when 1
26
- '1st'
41
+ "#{number}st"
27
42
  when 2
28
- '2nd'
43
+ "#{number}nd"
29
44
  when 3
30
- '3rd'
45
+ "#{number}rd"
31
46
  else
32
47
  "#{number}th"
33
48
  end
34
49
  end
50
+
51
+ def french_ordinal(number)
52
+ case number
53
+ when 1
54
+ '1er'
55
+ else
56
+ "#{number}ème"
57
+ end
58
+ end
35
59
  end
36
60
  end
37
61
  end
@@ -0,0 +1,97 @@
1
+ module CalendariumRomanum
2
+ # Has mostly the same public interface as {Calendar},
3
+ # but represents a "perpetual" calendar, not a calendar
4
+ # for a single year, thus allowing the client code
5
+ # to query for liturgical data of any day, without bothering
6
+ # about boundaries of liturgical years.
7
+ #
8
+ # Internally builds {Calendar} instances as needed
9
+ # and delegates method calls to them.
10
+ #
11
+ # @since 0.4.0
12
+ class PerpetualCalendar
13
+ # @param sanctorale [Sanctorale, nil]
14
+ # @param temporale_factory [Proc, nil]
15
+ # +Proc+ receiving a single parameter - year - and returning
16
+ # a {Temporale} instance.
17
+ # @param temporale_options [Hash, nil]
18
+ # +Hash+ of arguments for {Temporale#initialize}.
19
+ # +temporale_factory+ and +temporale_options+ are mutually
20
+ # exclusive - pass either (or none) of them, never both.
21
+ # @param cache [Hash]
22
+ # object to be used as internal cache of {Calendar} instances -
23
+ # anything exposing +#[]=+ and +#[]+ and "behaving mostly like
24
+ # a +Hash+" will work.
25
+ # There's no need to pass it unless you want to have control
26
+ # over the cache. That may be sometimes useful
27
+ # in order to prevent a long-lived
28
+ # +PerpetualCalendar+ instance flooding the memory
29
+ # by huge amount of cached {Calendar} instances.
30
+ # (By default, once a {Calendar} for a certain year is built,
31
+ # it is cached for the +PerpetualCalendar+ instances' lifetime.)
32
+ def initialize(sanctorale: nil, temporale_factory: nil, temporale_options: nil, cache: {})
33
+ if temporale_factory && temporale_options
34
+ raise ArgumentError.new('Specify either temporale_factory or temporale_options, not both')
35
+ end
36
+
37
+ @sanctorale = sanctorale
38
+ @temporale_factory = temporale_factory || build_temporale_factory(temporale_options)
39
+
40
+ @cache = cache
41
+ end
42
+
43
+ # @return [Day]
44
+ # @see Calendar#day
45
+ def day(*args)
46
+ calendar_for(*args).day(*args)
47
+ end
48
+
49
+ # @return [Day, Array<Day>]
50
+ # @see Calendar#[]
51
+ # @since 0.6.0
52
+ def [](arg)
53
+ if arg.is_a? Range
54
+ return arg.collect do |date|
55
+ calendar_for(date).day(date)
56
+ end
57
+ end
58
+
59
+ day(arg)
60
+ end
61
+
62
+ # Returns a {Calendar} instance for the liturgical year containing
63
+ # the specified day
64
+ #
65
+ # Parameters like {Calendar#day}
66
+ #
67
+ # @return [Calendar]
68
+ def calendar_for(*args)
69
+ date = Calendar.mk_date(*args)
70
+ year = Temporale.liturgical_year date
71
+ calendar_instance year
72
+ end
73
+
74
+ # Returns a Calendar instance for the specified liturgical year
75
+ #
76
+ # @param year [Fixnum]
77
+ # @return [Calendar]
78
+ def calendar_for_year(year)
79
+ calendar_instance year
80
+ end
81
+
82
+ private
83
+
84
+ def build_temporale_factory(temporale_options)
85
+ temporale_options ||= {}
86
+ lambda {|year| Temporale.new(year, **temporale_options) }
87
+ end
88
+
89
+ def calendar_instance(year)
90
+ if @cache.has_key? year
91
+ @cache[year]
92
+ else
93
+ @cache[year] = Calendar.new(year, @sanctorale, @temporale_factory.call(year))
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,40 +1,77 @@
1
1
  module CalendariumRomanum
2
+ # Celebration rank
2
3
  class Rank
3
4
  include Comparable
4
5
 
5
- def initialize(priority=nil, desc=nil, short_desc=nil)
6
+ # @param priority [Float, nil] number in the Table of Liturgical Days
7
+ # @param desc [String, nil]
8
+ # full description (translation string identifier)
9
+ # @param short_desc [String, nil]
10
+ # short rank name (translation string identifier)
11
+ def initialize(priority = nil, desc = nil, short_desc = nil)
6
12
  @priority = priority
7
13
  @desc = desc
8
14
  @short_desc = short_desc
9
15
  end
10
16
 
17
+ # @return [Float, nil]
11
18
  attr_reader :priority
12
- alias_method :to_f, :priority
19
+ alias to_f priority
13
20
 
21
+ # Full description - internationalized human-readable string.
22
+ #
23
+ # @return [String, nil]
14
24
  def desc
15
25
  @desc && I18n.t(@desc)
16
26
  end
17
27
 
18
- alias_method :to_s, :desc
28
+ # String representation mostly for debugging purposes.
29
+ #
30
+ # @return [String]
31
+ def to_s
32
+ # 'desc' instead of '@desc' is intentional -
33
+ # for a good reason we don't present contents of an instance
34
+ # variable but result of an instance method
35
+ "#<#{self.class.name} @priority=#{priority} desc=#{desc.inspect}>"
36
+ end
19
37
 
38
+ # Short name - internationalized human-readable string.
39
+ #
40
+ # @return [String, nil]
20
41
  def short_desc
21
42
  @short_desc && I18n.t(@short_desc)
22
43
  end
23
44
 
24
- def <=>(b)
25
- b.priority <=> self.priority
45
+ def <=>(other)
46
+ other.priority <=> priority
26
47
  end
27
48
 
49
+ # @return [Boolean]
28
50
  def solemnity?
29
51
  priority.to_i == 1
30
52
  end
31
53
 
54
+ # @return [Boolean]
55
+ # @since 0.6.0
56
+ def sunday?
57
+ self == Ranks::SUNDAY_UNPRIVILEGED
58
+ end
59
+
60
+ # @return [Boolean]
32
61
  def feast?
33
62
  priority.to_i == 2
34
63
  end
35
64
 
65
+ # @return [Boolean]
36
66
  def memorial?
37
- priority.to_i == 3
67
+ priority.to_i == 3 && priority <= 3.12
68
+ end
69
+
70
+ # @return [Boolean]
71
+ # @since 0.6.0
72
+ def ferial?
73
+ self == Ranks::FERIAL ||
74
+ self == Ranks::FERIAL_PRIVILEGED
38
75
  end
39
76
  end
40
77
  end