aenea 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +39 -0
  3. data/Rakefile +24 -0
  4. data/app/assets/config/aenea_manifest.js +0 -0
  5. data/app/helpers/inline_boolean_input.rb +9 -0
  6. data/app/helpers/markdown_input.rb +21 -0
  7. data/app/helpers/non_input_input.rb +7 -0
  8. data/app/models/concerns/acts_as_only_flagged.rb +65 -0
  9. data/app/models/concerns/can_be_inactive.rb +8 -0
  10. data/app/models/concerns/has_external_id.rb +9 -0
  11. data/app/models/concerns/has_obfuscated_id.rb +51 -0
  12. data/app/models/concerns/model.rb +51 -0
  13. data/app/models/concerns/model/belongs_to.rb +38 -0
  14. data/app/models/concerns/model/enum.rb +24 -0
  15. data/app/models/concerns/model/has_many.rb +127 -0
  16. data/app/models/concerns/model/has_one.rb +77 -0
  17. data/app/models/concerns/sluggable.rb +7 -0
  18. data/app/models/countries.rb +278 -0
  19. data/app/models/country_validator.rb +10 -0
  20. data/app/models/database_function_helper.rb +88 -0
  21. data/app/models/google_maps_api.rb +19 -0
  22. data/app/models/google_maps_api/geocode.rb +29 -0
  23. data/app/models/google_maps_api/time_zone.rb +29 -0
  24. data/app/models/json_api.rb +16 -0
  25. data/app/models/phone_number_validator.rb +10 -0
  26. data/app/models/us_state_validator.rb +10 -0
  27. data/app/models/us_states.rb +75 -0
  28. data/app/models/zip_code_validator.rb +12 -0
  29. data/lib/aenea.rb +12 -0
  30. data/lib/aenea/active_admin.rb +23 -0
  31. data/lib/aenea/active_admin/index_as_table.rb +9 -0
  32. data/lib/aenea/active_admin/index_as_table/sortable_columns.rb +29 -0
  33. data/lib/aenea/active_admin/resource_controller.rb +9 -0
  34. data/lib/aenea/active_admin/resource_controller/find_sluggable.rb +21 -0
  35. data/lib/aenea/active_admin/resource_dsl.rb +11 -0
  36. data/lib/aenea/active_admin/resource_dsl/sortable_actions.rb +53 -0
  37. data/lib/aenea/active_admin/resource_dsl/use_parent_actions.rb +79 -0
  38. data/lib/aenea/date.rb +14 -0
  39. data/lib/aenea/engine.rb +20 -0
  40. data/lib/aenea/enum.rb +13 -0
  41. data/lib/aenea/enum/changes.rb +61 -0
  42. data/lib/aenea/enum/ext.rb +68 -0
  43. data/lib/aenea/enum/groups.rb +53 -0
  44. data/lib/aenea/markdown_attr.rb +43 -0
  45. data/lib/aenea/time.rb +9 -0
  46. data/lib/aenea/time_of_day.rb +41 -0
  47. data/lib/aenea/time_window.rb +86 -0
  48. data/lib/aenea/version.rb +3 -0
  49. data/lib/tasks/aenea_tasks.rake +4 -0
  50. metadata +203 -0
@@ -0,0 +1,14 @@
1
+ class Date
2
+ def beginning_of_month
3
+ Date.new(self.year, self.month, 1)
4
+ end
5
+
6
+ def end_of_month
7
+ next_month = self + 1.month
8
+ Date.new(next_month.year, next_month.month, 1) - 1.day
9
+ end
10
+
11
+ def self.today_in_pacific_tz
12
+ Time.now.in_pacific_tz.to_date
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ module Aenea
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Aenea
4
+
5
+ config.to_prepare do
6
+ ActiveSupport.on_load(:active_record) do
7
+ include Aenea::Enum
8
+ include Aenea::MarkdownAttr
9
+
10
+ ActiveRecord::Type.register :time_of_day, Tod::TimeOfDay::Type
11
+ ActiveModel::Type.register :time_of_day, Tod::TimeOfDay::Type
12
+
13
+ ActiveRecord::Type.register :time_window, Aenea::TimeWindow::Type
14
+ ActiveModel::Type.register :time_window, Aenea::TimeWindow::Type
15
+
16
+ Aenea::ActiveAdmin.install
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ require 'aenea/enum/changes'
2
+ require 'aenea/enum/ext'
3
+ require 'aenea/enum/groups'
4
+
5
+ module Aenea
6
+ module Enum
7
+ extend ActiveSupport::Concern
8
+
9
+ include Changes
10
+ include Ext
11
+ include Groups
12
+ end
13
+ end
@@ -0,0 +1,61 @@
1
+ module Aenea
2
+ module Enum
3
+ module Changes
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def record_enum_changed_at(enum, name)
8
+ # validates activated_at, presence: true, if: :active?
9
+ column = "#{name}_at".to_sym
10
+ validates column, presence: true, if: "#{name}?".to_sym
11
+
12
+ # before_validation populate_status_activated_at
13
+ populate_method = "populate_#{enum}_#{column}".to_sym
14
+ before_validation populate_method, if: "will_save_#{enum}_as_#{name}?".to_sym
15
+
16
+ # def populate_status_activated_at
17
+ define_method populate_method do
18
+ send("#{column}=", Time.now) unless send(column).present?
19
+ end
20
+
21
+ # def formatted_activated_at
22
+ define_method "formatted_#{column}" do
23
+ Formatter.timestamp send(column)
24
+ end
25
+
26
+ # def activated_on
27
+ date_method = "#{name}_on"
28
+ define_method date_method do
29
+ send(column).try(:to_date)
30
+ end
31
+
32
+ # def formatted_activated_on
33
+ define_method "formatted_#{date_method}" do
34
+ Formatter.date send(date_method)
35
+ end
36
+
37
+ # def short_formatted_activated_on
38
+ define_method "short_formatted_#{date_method}" do
39
+ Formatter.short_date send(date_method)
40
+ end
41
+ end
42
+
43
+ def validate_enum_change(enum, name, valid_previous_names)
44
+ method = "validate_#{enum}_change_to_#{name}".to_sym
45
+
46
+ # validate :validate_status_change_to_active
47
+ validate method, if: "will_save_#{enum}_as_#{name}?".to_sym
48
+
49
+ # def validate_status_change_to_active
50
+ define_method method do
51
+ name_in_database = send("#{enum}_in_database")
52
+
53
+ unless valid_previous_names.include?(name_in_database)
54
+ errors.add(enum, "cannot change from #{name_in_database.to_s.titleize} to #{send(enum).to_s.titleize}")
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,68 @@
1
+ module Aenea
2
+ module Enum
3
+ module Ext
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def enum(*args)
8
+ super
9
+
10
+ defined_enums.keys.each do |name|
11
+ will_save_change_method = "will_save_change_to_#{name}?"
12
+ saved_change_method = "saved_change_to_#{name}?"
13
+
14
+ # def will_save_status_as_nil?
15
+ define_method "will_save_#{name}_as_nil?" do
16
+ send(will_save_change_method) && send(name).nil?
17
+ end
18
+
19
+ # def will_save_status_as_non_nil?
20
+ define_method "will_save_#{name}_as_non_nil?" do
21
+ send(will_save_change_method) && !send(name).nil?
22
+ end
23
+
24
+ # def saved_status_as_nil?
25
+ define_method "saved_#{name}_as_nil?" do
26
+ send(saved_change_method) && send(name).nil?
27
+ end
28
+
29
+ # def saved_status_as_non_nil?
30
+ define_method "saved_#{name}_as_non_nil?" do
31
+ send(saved_change_method) && !send(name).nil?
32
+ end
33
+
34
+ enum_method = name.to_s.pluralize
35
+ send(enum_method).keys.each do |key|
36
+ # def save_as_active
37
+ define_method "save_as_#{key}" do
38
+ update(name => key)
39
+ end
40
+
41
+ # def will_save_status_as_active?
42
+ define_method "will_save_#{name}_as_#{key}?" do
43
+ send(will_save_change_method) && send("#{key}?")
44
+ end
45
+
46
+ # def saved_status_as_active?
47
+ define_method "saved_#{name}_as_#{key}?" do
48
+ send(saved_change_method) && send("#{key}?")
49
+ end
50
+ end
51
+
52
+ define_method "formatted_#{name}" do
53
+ send(name).try(:titleize)
54
+ end
55
+
56
+ define_singleton_method("#{name}_select_values") do
57
+ send(enum_method).keys.map {|k| [k.titleize, k] }
58
+ end
59
+
60
+ define_singleton_method("#{name}_filter_values") do
61
+ send(enum_method).map {|k, v| [k.titleize, v] }
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,53 @@
1
+ module Aenea
2
+ module Enum
3
+ module Groups
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def enum_group(modifier, enum, *keys)
8
+ enum_plural = enum.to_s.pluralize # statuses
9
+ group = [ modifier, enum_plural ].join('_') # inactive_statuses
10
+ singular_group = [ modifier, enum ].join('_') # inactive_status
11
+ keys = keys.flatten.freeze
12
+
13
+ keys.each do |key|
14
+ raise "invalid #{enum}: #{key}" unless send(enum_plural).keys.include?(key)
15
+ end
16
+
17
+ # def self.inactive_statuses
18
+ define_singleton_method group do
19
+ keys.dup
20
+ end
21
+
22
+ # scope :inactive, -> { where(status: inactive_statuses) }
23
+ scope modifier, -> { where(enum => send(group)) }
24
+
25
+ # def inactive?
26
+ define_method "#{modifier}?" do
27
+ self.class.send(group).include?(send(enum)) # self.class.inactive_statuses.include?(status)
28
+ end
29
+
30
+ # def will_save_status_as_inactive?
31
+ define_method "will_save_#{enum}_as_#{modifier}?" do
32
+ self.class.send(group).inject(false) {|result, e| result = result || send("will_save_#{enum}_as_#{e}?") }
33
+ end
34
+
35
+ # def saved_status_as_inactive?
36
+ define_method "saved_#{enum}_as_#{modifier}?" do
37
+ self.class.send(group).inject(false) {|result, e| result = result || send("saved_#{enum}_as_#{e}?") }
38
+ end
39
+
40
+ # def inactive_status_select_values
41
+ define_singleton_method("#{singular_group}_select_values") do
42
+ send(group).map {|k| [ k.titleize, k ] }
43
+ end
44
+
45
+ # def inactive_status_filter_values
46
+ define_singleton_method("#{singular_group}_filter_values") do
47
+ send(group).map {|k| [ k.titleize, send(enum_plural)[k] ] }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,43 @@
1
+ require 'kramdown'
2
+
3
+ module Aenea
4
+ module MarkdownAttr
5
+ extend ActiveSupport::Concern
6
+ include ActionView::Helpers::SanitizeHelper
7
+
8
+ class_methods do
9
+ def markdown_attr(attr_base_name)
10
+ attr_name = "#{attr_base_name}_as_md"
11
+
12
+ doc_method = "#{attr_base_name}_doc"
13
+ define_method doc_method do
14
+ return nil unless md = send(attr_name)
15
+
16
+ var_name = :"@#{doc_method}"
17
+ value = instance_variable_get var_name
18
+ return value if value
19
+
20
+ instance_variable_set var_name, Kramdown::Document.new(md)
21
+ end
22
+
23
+ html_method = "#{attr_base_name}_as_html"
24
+ define_method html_method do
25
+ return nil unless doc = send(doc_method)
26
+
27
+ var_name = :"@#{html_method}"
28
+ value = instance_variable_get var_name
29
+ return value if value
30
+
31
+ instance_variable_set var_name, doc.to_html.html_safe
32
+ end
33
+
34
+ text_method = "#{attr_base_name}_as_text"
35
+ define_method text_method do
36
+ return nil unless html = send(html_method)
37
+
38
+ strip_tags html
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ class Time
2
+ def self.pacific_tz_id
3
+ 'America/Los_Angeles'
4
+ end
5
+
6
+ def in_pacific_tz
7
+ in_time_zone Time.pacific_tz_id
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ require 'tod'
2
+ require 'tod/core_extensions'
3
+
4
+ module Tod
5
+ class TimeOfDay
6
+ def to_ampm
7
+ am_or_pm = hour < 12 ? 'am' : 'pm'
8
+ ampm_hour = hour == 0 ? 12 : (hour > 12 ? hour - 12 : hour)
9
+
10
+ sprintf('%d:%02d%s', ampm_hour, minute, am_or_pm)
11
+ end
12
+
13
+ def to_select_value
14
+ [to_ampm, to_s]
15
+ end
16
+
17
+ class Type < ActiveModel::Type::Value
18
+ def cast(value)
19
+ if value.blank?
20
+ nil
21
+ elsif value.is_a?(Tod::TimeOfDay)
22
+ value
23
+ elsif value.is_a?(String)
24
+ begin
25
+ Tod::TimeOfDay.parse(value)
26
+ rescue StandardError => e
27
+ ApplicationRecord.logger.warn "error casting #{value.inspect}: #{e.message}"
28
+ ensure
29
+ nil
30
+ end
31
+ else
32
+ raise "invalid value type #{value.class.name}: #{value.inspect}"
33
+ end
34
+ end
35
+
36
+ def serialize(value)
37
+ value.blank? ? nil : value.to_s
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,86 @@
1
+ module Aenea
2
+ class TimeWindow
3
+ SEPARATOR = ' - '
4
+
5
+ attr_reader :starting, :ending
6
+
7
+ def initialize(start_tod, end_tod_or_window_in_seconds)
8
+ @starting = start_tod.dup
9
+
10
+ if end_tod_or_window_in_seconds.is_a?(Tod::TimeOfDay)
11
+ @ending = end_tod_or_window_in_seconds.dup
12
+ else
13
+ @ending = @starting + end_tod_or_window_in_seconds # number of seconds
14
+ end
15
+
16
+ if ending < starting
17
+ raise ArgumentError.new("invalid window #{starting} - #{ending}")
18
+ end
19
+
20
+ self.freeze
21
+ end
22
+
23
+ def self.parse(str)
24
+ new(*str.split(SEPARATOR).collect {|s| Tod::TimeOfDay.parse(s) })
25
+ end
26
+
27
+ # a row of PL/pgsql return type table(time, time), returned as a varchar
28
+ # using SELECT func_... rather than SELECT * FROM func_... - the returned
29
+ # values are in the form "(11:00:00,13:00:00)"
30
+ def self.parse_tuple(str)
31
+ elements = str.sub(/^\(/, '').sub(/\)$/, '').split(',')
32
+ new(*elements.map {|s| Tod::TimeOfDay.parse(s) })
33
+ end
34
+
35
+ def ==(obj)
36
+ self.starting == obj.starting && ending == obj.ending
37
+ end
38
+
39
+ def <=>(obj)
40
+ start_comp = self.starting <=> obj.starting
41
+ return start_comp if start_comp != 0
42
+
43
+ self.ending <=> obj.ending
44
+ end
45
+
46
+ def to_s
47
+ starting.to_s + SEPARATOR + ending.to_s
48
+ end
49
+
50
+ def to_ampm
51
+ starting.to_ampm + SEPARATOR + ending.to_ampm
52
+ end
53
+
54
+ def to_select_value
55
+ [to_ampm, to_s]
56
+ end
57
+
58
+ def inspect
59
+ "<#{to_s}>"
60
+ end
61
+
62
+ class Type < ActiveModel::Type::ImmutableString
63
+ def cast(value)
64
+ if value.blank?
65
+ nil
66
+ elsif value.is_a?(String)
67
+ begin
68
+ TimeWindow.parse(value)
69
+ rescue StandardError => e
70
+ ApplicationRecord.logger.warn "error casting #{value.inspect}: #{e.message}"
71
+ ensure
72
+ nil
73
+ end
74
+ elsif value.is_a?(TimeWindow)
75
+ value
76
+ else
77
+ raise "invalid value type #{value.class.name}: #{value.inspect}"
78
+ end
79
+ end
80
+
81
+ def serialize(value)
82
+ value.blank? ? nil : value.to_s
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,3 @@
1
+ module Aenea
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :aenea do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,203 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aenea
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Anthony Wang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-02-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: kramdown
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.17'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.17'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tod
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pg
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activeadmin
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.8'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: factory_bot_rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.11'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.11'
111
+ - !ruby/object:Gem::Dependency
112
+ name: listen
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Aenea
126
+ email:
127
+ - anthony@anthonywang.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - README.md
133
+ - Rakefile
134
+ - app/assets/config/aenea_manifest.js
135
+ - app/helpers/inline_boolean_input.rb
136
+ - app/helpers/markdown_input.rb
137
+ - app/helpers/non_input_input.rb
138
+ - app/models/concerns/acts_as_only_flagged.rb
139
+ - app/models/concerns/can_be_inactive.rb
140
+ - app/models/concerns/has_external_id.rb
141
+ - app/models/concerns/has_obfuscated_id.rb
142
+ - app/models/concerns/model.rb
143
+ - app/models/concerns/model/belongs_to.rb
144
+ - app/models/concerns/model/enum.rb
145
+ - app/models/concerns/model/has_many.rb
146
+ - app/models/concerns/model/has_one.rb
147
+ - app/models/concerns/sluggable.rb
148
+ - app/models/countries.rb
149
+ - app/models/country_validator.rb
150
+ - app/models/database_function_helper.rb
151
+ - app/models/google_maps_api.rb
152
+ - app/models/google_maps_api/geocode.rb
153
+ - app/models/google_maps_api/time_zone.rb
154
+ - app/models/json_api.rb
155
+ - app/models/phone_number_validator.rb
156
+ - app/models/us_state_validator.rb
157
+ - app/models/us_states.rb
158
+ - app/models/zip_code_validator.rb
159
+ - lib/aenea.rb
160
+ - lib/aenea/active_admin.rb
161
+ - lib/aenea/active_admin/index_as_table.rb
162
+ - lib/aenea/active_admin/index_as_table/sortable_columns.rb
163
+ - lib/aenea/active_admin/resource_controller.rb
164
+ - lib/aenea/active_admin/resource_controller/find_sluggable.rb
165
+ - lib/aenea/active_admin/resource_dsl.rb
166
+ - lib/aenea/active_admin/resource_dsl/sortable_actions.rb
167
+ - lib/aenea/active_admin/resource_dsl/use_parent_actions.rb
168
+ - lib/aenea/date.rb
169
+ - lib/aenea/engine.rb
170
+ - lib/aenea/enum.rb
171
+ - lib/aenea/enum/changes.rb
172
+ - lib/aenea/enum/ext.rb
173
+ - lib/aenea/enum/groups.rb
174
+ - lib/aenea/markdown_attr.rb
175
+ - lib/aenea/time.rb
176
+ - lib/aenea/time_of_day.rb
177
+ - lib/aenea/time_window.rb
178
+ - lib/aenea/version.rb
179
+ - lib/tasks/aenea_tasks.rake
180
+ homepage: https://github.com/wangthony/aenea
181
+ licenses: []
182
+ metadata: {}
183
+ post_install_message:
184
+ rdoc_options: []
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements: []
198
+ rubyforge_project:
199
+ rubygems_version: 2.7.8
200
+ signing_key:
201
+ specification_version: 4
202
+ summary: Aenea
203
+ test_files: []