calendarium-romanum 0.4.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) 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 +22 -0
  6. data/.yardopts +3 -0
  7. data/CHANGELOG.md +431 -0
  8. data/Gemfile +25 -0
  9. data/Gemfile.lock +86 -0
  10. data/README.md +598 -0
  11. data/Rakefile +16 -0
  12. data/bin/calendariumrom +4 -1
  13. data/calendarium-romanum.gemspec +31 -0
  14. data/config/locales/cs.yml +5 -0
  15. data/config/locales/en.yml +21 -14
  16. data/config/locales/es.yml +94 -0
  17. data/config/locales/fr.yml +7 -0
  18. data/config/locales/it.yml +7 -0
  19. data/config/locales/la.yml +7 -0
  20. data/data/README.md +70 -24
  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 +236 -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/easter_dates.txt +67 -0
  33. data/data/universal-1969-la.txt +234 -0
  34. data/data/universal-en.txt +214 -211
  35. data/data/universal-es.txt +243 -0
  36. data/data/universal-fr.txt +214 -210
  37. data/data/universal-it.txt +214 -211
  38. data/data/universal-la.txt +214 -210
  39. data/doc/data_readme.md +2 -0
  40. data/doc/images/class_diagram.png +0 -0
  41. data/doc/images/class_diagram.puml +44 -0
  42. data/doc/yard_readme.rdoc +76 -0
  43. data/lib/calendarium-romanum.rb +35 -22
  44. data/lib/calendarium-romanum/abstract_date.rb +15 -0
  45. data/lib/calendarium-romanum/calendar.rb +207 -42
  46. data/lib/calendarium-romanum/cli.rb +63 -80
  47. data/lib/calendarium-romanum/cli/comparator.rb +63 -0
  48. data/lib/calendarium-romanum/cli/date_parser.rb +30 -0
  49. data/lib/calendarium-romanum/cli/dumper.rb +68 -0
  50. data/lib/calendarium-romanum/cli/helper.rb +23 -0
  51. data/lib/calendarium-romanum/cli/querier.rb +73 -0
  52. data/lib/calendarium-romanum/cr.rb +16 -0
  53. data/lib/calendarium-romanum/data.rb +50 -20
  54. data/lib/calendarium-romanum/day.rb +208 -32
  55. data/lib/calendarium-romanum/enum.rb +42 -25
  56. data/lib/calendarium-romanum/enums.rb +124 -44
  57. data/lib/calendarium-romanum/errors.rb +4 -0
  58. data/lib/calendarium-romanum/ordinalizer.rb +23 -2
  59. data/lib/calendarium-romanum/perpetual_calendar.rb +58 -7
  60. data/lib/calendarium-romanum/rank.rb +43 -12
  61. data/lib/calendarium-romanum/rank_predicates.rb +43 -0
  62. data/lib/calendarium-romanum/sanctorale.rb +164 -24
  63. data/lib/calendarium-romanum/sanctorale_factory.rb +74 -3
  64. data/lib/calendarium-romanum/sanctorale_loader.rb +180 -0
  65. data/lib/calendarium-romanum/sanctorale_writer.rb +119 -0
  66. data/lib/calendarium-romanum/temporale.rb +226 -94
  67. data/lib/calendarium-romanum/temporale/celebration_factory.rb +107 -0
  68. data/lib/calendarium-romanum/temporale/dates.rb +84 -16
  69. data/lib/calendarium-romanum/temporale/easter_table.rb +27 -0
  70. data/lib/calendarium-romanum/temporale/extensions.rb +15 -0
  71. data/lib/calendarium-romanum/temporale/extensions/christ_eternal_priest.rb +16 -3
  72. data/lib/calendarium-romanum/temporale/extensions/dedication_before_all_saints.rb +73 -0
  73. data/lib/calendarium-romanum/transfers.rb +60 -15
  74. data/lib/calendarium-romanum/util.rb +22 -3
  75. data/lib/calendarium-romanum/version.rb +5 -1
  76. data/liturgical_law/1969_normae_universales.md +568 -0
  77. data/liturgical_law/1977_decretum_de_celebratione_baptismatis_domini.md +58 -0
  78. data/liturgical_law/1990_decretum_de_variatione_inducenda.md +67 -0
  79. data/liturgical_law/1998_notificatio_de_occurrentia.md +57 -0
  80. data/liturgical_law/2002_normae_universales.md +946 -0
  81. data/liturgical_law/2006_notification.md +37 -0
  82. data/liturgical_law/2012_declarationes.md +38 -0
  83. data/liturgical_law/README.md +74 -0
  84. metadata +50 -28
  85. data/lib/calendarium-romanum/sanctoraleloader.rb +0 -115
  86. data/spec/abstract_date_spec.rb +0 -62
  87. data/spec/calendar_spec.rb +0 -330
  88. data/spec/celebration_spec.rb +0 -23
  89. data/spec/cli_spec.rb +0 -26
  90. data/spec/colour_spec.rb +0 -17
  91. data/spec/data_spec.rb +0 -23
  92. data/spec/date_spec.rb +0 -61
  93. data/spec/dates_spec.rb +0 -45
  94. data/spec/day_spec.rb +0 -59
  95. data/spec/enum_spec.rb +0 -51
  96. data/spec/i18n_spec.rb +0 -59
  97. data/spec/ordinalizer_spec.rb +0 -22
  98. data/spec/perpetual_calendar_spec.rb +0 -91
  99. data/spec/rank_spec.rb +0 -57
  100. data/spec/readme_spec.rb +0 -52
  101. data/spec/sanctorale_factory_spec.rb +0 -42
  102. data/spec/sanctorale_spec.rb +0 -191
  103. data/spec/sanctoraleloader_spec.rb +0 -171
  104. data/spec/season_spec.rb +0 -17
  105. data/spec/spec_helper.rb +0 -35
  106. data/spec/temporale_spec.rb +0 -519
@@ -1,104 +1,87 @@
1
1
  require 'thor'
2
2
 
3
+ # monkey patch preventing Thor from screwing formatting in our commands' long_desc
4
+ # credits: https://github.com/erikhuda/thor/issues/398#issuecomment-237400762
5
+ class Thor
6
+ module Shell
7
+ class Basic
8
+ def print_wrapped(message, options = {})
9
+ stdout.puts message
10
+ end
11
+ end
12
+ end
13
+ end
14
+
3
15
  module CalendariumRomanum
4
16
 
17
+ # Implementation of the +calendariumrom+ executable.
18
+ # _Not_ loaded by default when you +require+ the gem.
19
+ #
20
+ # @api private
5
21
  class CLI < Thor
6
- include CalendariumRomanum::Util
7
-
8
- desc 'query 2007-06-05', 'show calendar information for a specified date'
9
- option :calendar, default: 'universal-en', aliases: :c
10
- option :locale, default: 'en', aliases: :l
11
- def query(date_str=nil)
12
- I18n.locale = options[:locale]
13
-
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
31
- end
32
- calendar = Calendar.for_day(date, sanctorale)
33
- day = calendar.day date
34
-
35
- puts date
36
- puts "season: #{day.season}"
37
- puts
38
-
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
44
- end
22
+ desc 'query [DATE]', 'show calendar information for a specified date/month/year'
23
+ long_desc <<-EOS
24
+ show calendar information for a specified date/month/year
25
+
26
+ DATE formats:
27
+ not specified - today
28
+ 2000-01-02 - single date
29
+ 2000-01 - month
30
+ 2000 - year
31
+ EOS
32
+ option :calendar, default: Data::GENERAL_ROMAN_ENGLISH.siglum, aliases: :c, desc: 'sanctorale data file to use (filesystem path or identifier of a bundled one)'
33
+ option :locale, default: 'en', aliases: :l, desc: 'locale to use for localized strings'
34
+ def query(date_str = nil)
35
+ Querier
36
+ .new(locale: options[:locale], calendar: options[:calendar])
37
+ .call(date_str)
45
38
  end
46
39
 
47
- desc 'calendars', 'lists calendars available for querying'
40
+ desc 'calendars', 'list calendars available for querying'
48
41
  def calendars
49
42
  Data.each {|c| puts c.siglum }
50
43
  end
51
44
 
52
- desc 'errors FILE1, ...', 'finds errors in sanctorale data files'
45
+ desc 'errors FILE1, ...', 'find errors in sanctorale data files'
53
46
  def errors(*files)
54
- loader = SanctoraleLoader.new
55
47
  files.each do |path|
56
- s = Sanctorale.new
57
- loader.load_from_file path, s
48
+ begin
49
+ sanctorale_from_path path
50
+ rescue Errno::ENOENT, InvalidDataError => err
51
+ die! err.message
52
+ end
58
53
  end
59
54
  end
60
55
 
61
- desc 'cmp FILE1, FILE2', 'detect differences in rank and colour of corresponding celebrations'
56
+ desc 'cmp FILE1, FILE2', 'detect differences between two sanctorale data files'
62
57
  def cmp(a, b)
63
- 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
71
-
72
- # a leap year must be chosen in order to iterate over
73
- # all possible days of a Sanctorale
74
- 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
79
-
80
- celebs[0].each_index do |i|
81
- if i >= celebs[1].size
82
- break
83
- end
58
+ Comparator.new.call(a, b)
59
+ end
84
60
 
85
- ca = celebs[0][i]
86
- cb = celebs[1][i]
61
+ desc 'dump YEAR', 'print calendar of the specified year for use in regression tests'
62
+ def dump(year)
63
+ Dumper.new.regression_tests_dump year.to_i
64
+ end
87
65
 
88
- if ca.rank != cb.rank || ca.colour != cb.colour
89
- puts "#{d.month}/#{d.day}"
90
- print_celebration ca
91
- print_celebration cb
92
- puts
93
- end
94
- end
95
- end
66
+ desc 'version', 'print version information'
67
+ def version
68
+ puts 'calendarium-romanum CLI'
69
+ puts "calendarium-romanum: version #{CalendariumRomanum::VERSION}, released #{CalendariumRomanum::RELEASE_DATE}"
96
70
  end
71
+ end
72
+ end
97
73
 
98
- private
74
+ # required files reopen the CLI class - require after the class'es main definition
75
+ # in order to prevent superclass mismatch errors
76
+ require_relative 'cli/helper'
99
77
 
100
- def print_celebration(c)
101
- puts "#{c.rank.priority} #{c.colour.symbol} | #{c.title}"
102
- end
78
+ module CalendariumRomanum
79
+ class CLI
80
+ include Helper
103
81
  end
104
82
  end
83
+
84
+ require_relative 'cli/comparator'
85
+ require_relative 'cli/date_parser'
86
+ require_relative 'cli/dumper'
87
+ require_relative 'cli/querier'
@@ -0,0 +1,63 @@
1
+ module CalendariumRomanum
2
+ class CLI
3
+ # Compares two sanctorale data files, reports differences.
4
+ #
5
+ # @api private
6
+ class Comparator
7
+ include Helper
8
+
9
+ def call(path_a, path_b)
10
+ paths = [path_a, path_b]
11
+ sanctoralia = paths.collect {|source| sanctorale_from_path source }
12
+ names = paths.collect {|source| File.basename source }
13
+
14
+ all_possible_dates.each do |d|
15
+ a, b = sanctoralia.collect {|sanctorale| sanctorale[d] }
16
+
17
+ 0.upto([a.size, b.size].max - 1) do |i|
18
+ ca = a[i]
19
+ cb = b[i]
20
+ compared = [ca, cb]
21
+
22
+ if compared.index(&:nil?)
23
+ notnili = compared.index {|c| !c.nil? }
24
+
25
+ print date(d)
26
+ puts " only in #{names[notnili]}:"
27
+ puts celebration(compared[notnili])
28
+ puts
29
+ next
30
+ end
31
+
32
+ differences = %i(rank colour symbol).select do |property|
33
+ ca.public_send(property) != cb.public_send(property)
34
+ end
35
+
36
+ next if differences.empty?
37
+ print date(d)
38
+ puts " differs in #{differences.join(', ')}"
39
+ puts celebration(ca)
40
+ puts celebration(cb)
41
+ puts
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def all_possible_dates
49
+ # a leap year must be chosen in order to iterate over
50
+ # all possible days of a Sanctorale
51
+ Util::Year.new(1990).each_day
52
+ end
53
+
54
+ def date(d)
55
+ "#{d.month}/#{d.day}"
56
+ end
57
+
58
+ def celebration(c)
59
+ "#{c.rank.priority} #{c.colour.symbol} | #{c.title}"
60
+ end
61
+ end
62
+ end
63
+ 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