onoma 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +4 -0
  4. data/CODE_OF_CONDUCT.md +13 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +41 -0
  8. data/Rakefile +93 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/config/locales/arb.yml +5711 -0
  12. data/config/locales/eng.yml +5704 -0
  13. data/config/locales/fra.yml +5869 -0
  14. data/config/locales/jpn.yml +5713 -0
  15. data/config/locales/por.yml +5702 -0
  16. data/config/locales/spa.yml +5708 -0
  17. data/db/migrate/20150813022423_add_data.xml +5740 -0
  18. data/db/migrate/20150813224145_fix_zea.xml +14 -0
  19. data/db/migrate/20150813224155_fix_hordeum.xml +175 -0
  20. data/db/migrate/20150814104907_rename_charts_of_accounts.xml +4 -0
  21. data/db/migrate/20150817202608_rename_abaci.xml +19 -0
  22. data/db/migrate/20150818132801_add_vinifera_product_exchangers.xml +4 -0
  23. data/db/migrate/20150818152001_add_wine_bottles.xml +13 -0
  24. data/db/migrate/20150818232501_add_vinifera_sale_exchangers.xml +4 -0
  25. data/db/migrate/20150820212931_remove_abaci.xml +19 -0
  26. data/db/migrate/20150821082601_add_grain_variants.xml +4 -0
  27. data/db/migrate/20150821230800_rename_invalid_document_natures.xml +12 -0
  28. data/db/migrate/20150825180658_add_production_usages.xml +5 -0
  29. data/db/migrate/20150826091319_add_leguminous_crop.xml +11 -0
  30. data/db/migrate/20150826092813_add_cannabis_varieties.xml +51 -0
  31. data/db/migrate/20150826100013_add_thicket_varieties.xml +9 -0
  32. data/db/migrate/20150826103353_add_eis_plant.xml +31 -0
  33. data/db/migrate/20150902081701_add_garden_varieties.xml +7 -0
  34. data/db/migrate/20150905094701_add_milklic_individual_production_exchangers.xml +4 -0
  35. data/db/migrate/20150914095928_add_meteorological_analysis_nature.xml +46 -0
  36. data/db/migrate/20150916110501_add_grain_analysis.xml +5 -0
  37. data/db/migrate/20150916151652_fix_product_nature_variant_units.xml +515 -0
  38. data/db/migrate/20150919123840_fix_plant_varieties.xml +36 -0
  39. data/db/migrate/20150919223801_change_walnut_nature.xml +6 -0
  40. data/db/migrate/20150920212201_add_fruit_harvest.xml +4 -0
  41. data/db/migrate/20150921104001_add_fuel_consumption_indicator.xml +7 -0
  42. data/db/migrate/20150921110601_add_crumbs_exchangers.xml +4 -0
  43. data/db/migrate/20150921175601_add_units_liter_per_hour.xml +4 -0
  44. data/db/migrate/20151001154701_add_missing_variants.xml +9 -0
  45. data/db/migrate/20151019090110_add_json_exchange_natures.xml +11 -0
  46. data/db/migrate/20151021172901_add_missing_indicator_on_animals.xml +12 -0
  47. data/db/migrate/20151027095601_add_missing_issue_natures.xml +6 -0
  48. data/db/migrate/20151102110723_add_daily_nitrogen_production_indicator.xml +4 -0
  49. data/db/migrate/20151107122501_add_cap_statements_exchangers.xml +4 -0
  50. data/db/migrate/20151111212501_add_missing_cap_productions.xml +15 -0
  51. data/db/migrate/20151117192943_change_procedure_nomenclatures.xml +159 -0
  52. data/db/migrate/20151122101101_add_missing_tropical_cap_varieties.xml +33 -0
  53. data/db/migrate/20151125163801_add_missing_varieties.xml +7 -0
  54. data/db/migrate/20151209000401_add_missing_crop_sets.xml +12 -0
  55. data/db/migrate/20151209011801_add_missing_varieties.xml +45 -0
  56. data/db/migrate/20151209094701_add_oleaginous_missing_varieties.xml +11 -0
  57. data/db/migrate/20151209103601_add_proteaginous_missing_varieties.xml +6 -0
  58. data/db/migrate/20151210150144_add_daucus_carota_varieties.xml +38 -0
  59. data/db/migrate/20151210163440_add_phaseolus_varieties.xml +11 -0
  60. data/db/migrate/20151210164511_add_allium_porrum_varieties.xml +13 -0
  61. data/db/migrate/20151210170103_add_allium_cepa_varieties.xml +6 -0
  62. data/db/migrate/20151211114316_add_beta_varieties.xml +4 -0
  63. data/db/migrate/20151211115500_add_brassica_varieties.xml +50 -0
  64. data/db/migrate/20151211124757_add_allium_schoenoprasum_varieties.xml +4 -0
  65. data/db/migrate/20151211132045_add_cucurbita_varieties.xml +13 -0
  66. data/db/migrate/20151211143806_add_spinacia_oleracea_varieties.xml +5 -0
  67. data/db/migrate/20151211151402_add_lactuca_varieties.xml +6 -0
  68. data/db/migrate/20151211153218_add_zea_mays_varieties.xml +46 -0
  69. data/db/migrate/20151214084817_add_hordeum_varieties.xml +5 -0
  70. data/db/migrate/20151214085342_add_pastinaca_sativa_varieties.xml +4 -0
  71. data/db/migrate/20151214085721_add_pisum_sativum_varieties.xml +8 -0
  72. data/db/migrate/20151214090420_add_solanum_tuberosum_varieties.xml +4 -0
  73. data/db/migrate/20151214091020_add_raphanus_varieties.xml +8 -0
  74. data/db/migrate/20151214092727_add_glycine_max_varieties.xml +4 -0
  75. data/db/migrate/20151215132825_add_abilities.xml +7 -0
  76. data/db/migrate/20151215133320_add_equipment_variants.xml +43 -0
  77. data/db/migrate/20151215214901_add_openwheatermap_identifiers.xml +5 -0
  78. data/db/migrate/20151216095351_add_ridger_equipment_variants.xml +4 -0
  79. data/db/migrate/20151216100708_add_lifter_equipment_variants.xml +4 -0
  80. data/db/migrate/20151216160914_add_raphanus_sativus_varieties.xml +4 -0
  81. data/db/migrate/20151216182551_add_more_units.xml +7 -0
  82. data/db/migrate/20151218081701_add_crops_issue_natures.xml +11 -0
  83. data/db/migrate/20151222162657_add_varieties.xml +18 -0
  84. data/db/migrate/20151222180021_remove_population.xml +402 -0
  85. data/db/reference.xml +5727 -0
  86. data/lib/onoma/database.rb +171 -0
  87. data/lib/onoma/item.rb +272 -0
  88. data/lib/onoma/migration/actions/base.rb +19 -0
  89. data/lib/onoma/migration/actions/item_change.rb +35 -0
  90. data/lib/onoma/migration/actions/item_creation.rb +39 -0
  91. data/lib/onoma/migration/actions/item_merging.rb +19 -0
  92. data/lib/onoma/migration/actions/item_removal.rb +18 -0
  93. data/lib/onoma/migration/actions/nomenclature_change.rb +26 -0
  94. data/lib/onoma/migration/actions/nomenclature_creation.rb +24 -0
  95. data/lib/onoma/migration/actions/nomenclature_removal.rb +24 -0
  96. data/lib/onoma/migration/actions/property_creation.rb +43 -0
  97. data/lib/onoma/migration/actions.rb +9 -0
  98. data/lib/onoma/migration/base.rb +45 -0
  99. data/lib/onoma/migration.rb +11 -0
  100. data/lib/onoma/migrator/reference.rb +77 -0
  101. data/lib/onoma/migrator/translation.rb +30 -0
  102. data/lib/onoma/migrator.rb +3 -0
  103. data/lib/onoma/nomenclature.rb +507 -0
  104. data/lib/onoma/property.rb +98 -0
  105. data/lib/onoma/reference.rb +17 -0
  106. data/lib/onoma/reflection.rb +34 -0
  107. data/lib/onoma/relation.rb +9 -0
  108. data/lib/onoma/version.rb +3 -0
  109. data/lib/onoma.rb +133 -0
  110. data/onoma.gemspec +28 -0
  111. metadata +237 -0
@@ -0,0 +1,9 @@
1
+ require 'onoma/migration/actions/base'
2
+ require 'onoma/migration/actions/item_change'
3
+ require 'onoma/migration/actions/item_creation'
4
+ require 'onoma/migration/actions/item_merging'
5
+ require 'onoma/migration/actions/item_removal'
6
+ require 'onoma/migration/actions/nomenclature_change'
7
+ require 'onoma/migration/actions/nomenclature_creation'
8
+ require 'onoma/migration/actions/nomenclature_removal'
9
+ require 'onoma/migration/actions/property_creation'
@@ -0,0 +1,45 @@
1
+ require 'active_support/inflector'
2
+ require 'active_support/core_ext/array'
3
+
4
+ module Onoma
5
+ module Migration
6
+ class Base
7
+ def self.parse(file)
8
+ f = File.open(file, 'rb')
9
+ document = Nokogiri::XML(f) do |config|
10
+ config.strict.nonet.noblanks.noent
11
+ end
12
+ f.close
13
+ root = document.root
14
+ number = file.basename.to_s.split('_').first.to_i
15
+ new(number, root['name'], root)
16
+ end
17
+
18
+ attr_reader :number, :name
19
+
20
+ def initialize(number, name, element = nil)
21
+ @number = number
22
+ @name = name
23
+ @actions = []
24
+ if element
25
+ element.children.each do |child|
26
+ next unless child.is_a? Nokogiri::XML::Element
27
+ @actions << "Onoma::Migration::Actions::#{child.name.underscore.classify}".constantize.new(child)
28
+ end
29
+ end
30
+ end
31
+
32
+ def label
33
+ "#{number} #{name}"
34
+ end
35
+
36
+ def each_action(&block)
37
+ @actions.each(&block)
38
+ end
39
+
40
+ def inspect
41
+ "#<#{self.class.name}:#{sprintf('%#x', object_id)} ##{number} #{name.inspect} (#{@actions.size} actions)>"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ class ::Hash
2
+
3
+ def simple_print
4
+ map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
5
+ end
6
+
7
+ end
8
+
9
+
10
+ require 'onoma/migration/actions'
11
+ require 'onoma/migration/base'
@@ -0,0 +1,77 @@
1
+ module Onoma
2
+ module Migrator
3
+ class Reference
4
+ def self.run(migration)
5
+ puts ""
6
+ puts "== #{migration.label}: migrating ".ljust(80, "=")
7
+ start = Time.now
8
+ ref = new
9
+ migration.each_action do |action|
10
+ puts "-- #{action.label}"
11
+ ref.send(action.action_name, action)
12
+ end
13
+ ref.version = migration.number
14
+ ref.write
15
+ duration = (Time.now - start).round(4)
16
+ puts "== #{migration.label}: migrated (#{duration}s)".ljust(80, "=")
17
+ # puts "Write DB in #{Onoma.reference_path.relative_path_from(Onoma.root)}".yellow
18
+ end
19
+
20
+ def initialize
21
+ if Onoma.reference_path.exist?
22
+ @set = Onoma::Database.load_file(Onoma.reference_path)
23
+ else
24
+ @set = Onoma::Database.new
25
+ end
26
+ end
27
+
28
+ def version
29
+ @set.version
30
+ end
31
+
32
+ def version=(number)
33
+ @set.version = number
34
+ end
35
+
36
+ def write
37
+ File.write(Onoma.reference_path, @set.to_xml)
38
+ end
39
+
40
+ def nomenclature_creation(action)
41
+ @set.add_nomenclature(action.name, action.options)
42
+ end
43
+
44
+ def nomenclature_change(action)
45
+ @set.change_nomenclature(action.nomenclature, action.changes)
46
+ end
47
+
48
+ def nomenclature_removal(action)
49
+ @set.remove_nomenclature(action.nomenclature)
50
+ end
51
+
52
+ def property_creation(action)
53
+ @set.add_property(action.nomenclature, action.name, action.type, action.options)
54
+ end
55
+
56
+ def property_change(action)
57
+ @set.add_property(action.nomenclature, action.name, action.changes)
58
+ end
59
+
60
+ def item_creation(action)
61
+ @set.add_item(action.nomenclature, action.name, action.options)
62
+ end
63
+
64
+ def item_change(action)
65
+ @set.change_item(action.nomenclature, action.name, action.changes)
66
+ end
67
+
68
+ def item_merging(action)
69
+ @set.merge_item(action.nomenclature, action.name, action.into)
70
+ end
71
+
72
+ def item_removal(action)
73
+ @set.remove_item(action.nomenclature, action.name)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,30 @@
1
+ module Onoma
2
+ module Migrator
3
+ class Translation
4
+ def self.run(migration)
5
+ puts "Migration #{migration.name}"
6
+ I18n.available_locales.each do |locale|
7
+ file = Onoma.root.join('config', 'locales', "#{locale}.yml")
8
+ hash = Clean::Support.yaml_to_hash(file)
9
+ migration.each_action do |action|
10
+ ref = hash[locale.to_sym][:nomenclatures]
11
+ ref[action.nomenclature.to_sym] ||= {}
12
+ ref[action.nomenclature.to_sym][:items] ||= {}
13
+ if action.is_a?(Onoma::Migration::Actions::ItemChange) && action.new_name?
14
+ ref[action.nomenclature.to_sym][:items][action.new_name.to_sym] ||= ref[action.nomenclature.to_sym][:items].delete(action.name.to_sym)
15
+ elsif action.is_a?(Onoma::Migration::Actions::ItemMerging)
16
+ ref[action.nomenclature.to_sym][:items][action.into.to_sym] ||= ref[action.nomenclature.to_sym][:items].delete(action.name.to_sym)
17
+ elsif action.is_a?(Onoma::Migration::Actions::NomenclatureChange) && action.changes[:name]
18
+ ref[action.changes[:name].to_sym] = ref.delete(action.nomenclature.to_sym)
19
+ elsif action.is_a?(Onoma::Migration::Actions::NomenclatureRemoval)
20
+ ref.delete(action.nomenclature.to_sym)
21
+ elsif !action.is_a?(Onoma::Migration::Actions::Base)
22
+ fail "Cannot handle: #{action.inspect}"
23
+ end
24
+ File.write(file, Clean::Support.hash_to_yaml(hash))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ require 'onoma/migration'
2
+ require 'onoma/migrator/reference'
3
+ require 'onoma/migrator/translation'
@@ -0,0 +1,507 @@
1
+ require 'bigdecimal'
2
+ require 'bigdecimal/util'
3
+
4
+ module Onoma
5
+ # This class represents a nomenclature
6
+ class Nomenclature
7
+ attr_reader :properties, :items, :name, :roots
8
+ attr_accessor :name, :notions, :translateable, :forest_right
9
+ alias_method :property_natures, :properties
10
+
11
+ # Instanciate a new nomenclature
12
+ def initialize(name, options = {})
13
+ @name = name.to_sym
14
+ @set = options.delete(:set)
15
+ @items = ActiveSupport::HashWithIndifferentAccess.new
16
+ @forest_right = 0
17
+ @roots = []
18
+ @properties = ActiveSupport::HashWithIndifferentAccess.new
19
+ @translateable = !options[:translateable].is_a?(FalseClass)
20
+ @notions = options[:notions] || []
21
+ end
22
+
23
+ class << self
24
+ def harvest(element, options = {})
25
+ notions = element.attr('notions').to_s.split(/\s*\,\s*/).map(&:to_sym)
26
+ options[:notions] = notions if notions.any?
27
+ options[:translateable] = !(element.attr('translateable').to_s == 'false')
28
+ name = element.attr('name').to_s
29
+ nomenclature = new(name, options)
30
+ for property in element.xpath('xmlns:properties/xmlns:property')
31
+ nomenclature.harvest_property(property)
32
+ end
33
+ for item in element.xpath('xmlns:items/xmlns:item')
34
+ nomenclature.harvest_item(item)
35
+ end
36
+ nomenclature.rebuild_tree!
37
+ nomenclature
38
+ end
39
+ end
40
+
41
+ def roots
42
+ @items.values.select(&:root?)
43
+ end
44
+
45
+ def name=(value)
46
+ @name = value.to_sym
47
+ end
48
+
49
+ def update_attributes(attributes = {})
50
+ attributes.each do |attribute, value|
51
+ send("#{attribute}=", value)
52
+ end
53
+ end
54
+
55
+ def references
56
+ unless @references
57
+ @references = []
58
+ properties.each do |_p, property|
59
+ if property.item_reference?
60
+ @references << Onoma::Reference.new(@set, property, @set.find(property.source), property.item_list? ? :array : :key)
61
+ end
62
+ end
63
+ end
64
+ @references
65
+ end
66
+
67
+ # Write CSV file with all items an there properties
68
+ def to_csv(file, _options = {})
69
+ properties = @properties.values
70
+ CSV.open(file, 'wb') do |csv|
71
+ csv << [:name] + properties.map do |p|
72
+ suffix = (p.decimal? ? ' D' : p.integer? ? ' I' : p.boolean? ? ' B' : p.item? ? " R(#{p.choices_nomenclature})" : '')
73
+ "#{p.name}#{suffix}"
74
+ end
75
+ @items.values.each do |i|
76
+ csv << [i.name] + properties.map { |p| i.attributes[p.name] }
77
+ end
78
+ end
79
+ end
80
+
81
+ def to_xml_attrs
82
+ attrs = { name: name, translateable: translateable.to_s }
83
+ attrs[:notions] = @notions.join(', ') if @notions.any?
84
+ attrs
85
+ end
86
+
87
+ # Build a nested set index on items
88
+ # Returns last right value
89
+ def rebuild_tree!
90
+ @forest_right = 0
91
+ roots.each(&:rebuild_tree!)
92
+ end
93
+
94
+ # Add an item to the nomenclature from an XML element
95
+ def harvest_item(element, attributes = {})
96
+ name = element.attr('name').to_s
97
+ parent = attributes[:parent] || (element.key?('parent') ? element['parent'] : nil)
98
+ attributes = element.attributes.each_with_object(HashWithIndifferentAccess.new) do |(k, v), h|
99
+ next if %w(name parent).include?(k)
100
+ h[k] = cast_property(k, v.to_s)
101
+ end
102
+ attributes[:parent] = parent if parent
103
+ item = add_item(name, attributes, rebuild: false)
104
+ item
105
+ end
106
+
107
+ # Add an property to the nomenclature from an XML element
108
+ def harvest_property(element)
109
+ name = element.attr('name').to_sym
110
+ type = element.attr('type').to_sym
111
+ options = {}
112
+ if element.has_attribute?('fallbacks')
113
+ options[:fallbacks] = element.attr('fallbacks').to_s.strip.split(/[[:space:]]*\,[[:space:]]*/).map(&:to_sym)
114
+ end
115
+ if element.has_attribute?('default')
116
+ options[:default] = element.attr('default').to_sym
117
+ end
118
+ options[:required] = !!(element.attr('required').to_s == 'true')
119
+ # options[:inherit] = !!(element.attr('inherit').to_s == 'true')
120
+ if type == :list
121
+ type = element.has_attribute?('nomenclature') ? :item_list : :choice_list
122
+ elsif type == :choice
123
+ type = :item if element.has_attribute?('nomenclature')
124
+ end
125
+ if type == :choice || type == :choice_list
126
+ if element.has_attribute?('choices')
127
+ options[:choices] = element.attr('choices').to_s.strip.split(/[[:space:]]*\,[[:space:]]*/).map(&:to_sym)
128
+ else
129
+ type = :string_list
130
+ end
131
+ elsif type == :item || type == :item_list
132
+ if element.has_attribute?('choices')
133
+ options[:choices] = element.attr('choices').to_s.strip.to_sym
134
+ elsif element.has_attribute?('nomenclature')
135
+ options[:choices] = element.attr('nomenclature').to_s.strip.to_sym
136
+ else
137
+ fail MissingChoices, "[#{@name}] Property #{name} must have nomenclature as choices"
138
+ end
139
+ end
140
+ unless Property::TYPES.include?(type)
141
+ fail ArgumentError, "Property #{name} type is unknown: #{type.inspect}"
142
+ end
143
+ add_property(name, type, options)
144
+ end
145
+
146
+ # Add an item to the nomenclature
147
+ def add_item(name, attributes = {}, options = {})
148
+ i = Item.new(self, name, attributes)
149
+ if @items[i.name]
150
+ fail "Item #{i.name} is already defined in nomenclature #{@name}"
151
+ end
152
+ @items[i.name] = i
153
+ @roots << i unless i.parent?
154
+ i.rebuild_tree! unless options[:rebuild].is_a?(FalseClass)
155
+ i
156
+ end
157
+
158
+ # Add an item to the nomenclature
159
+ def change_item(name, changes = {})
160
+ i = find!(name)
161
+ has_parent = changes.key?(:parent)
162
+ new_parent = changes[:parent]
163
+ new_name = changes[:name]
164
+ changes.each do |k, v|
165
+ next if [:parent, :name].include? k
166
+ i.set(k, v)
167
+ end
168
+ if has_parent
169
+ @roots << i if i.parent? && new_parent.nil?
170
+ @roots.delete(i) if i.root? && new_parent
171
+ i.parent = new_parent
172
+ end
173
+ i = rename_item(name, new_name) if new_name
174
+ i
175
+ end
176
+
177
+ def rename_item(name, new_name)
178
+ if @items[new_name]
179
+ fail "Item #{new_name} is already defined in nomenclature #{@name}. Use merging instead."
180
+ end
181
+ i = find!(name)
182
+ i.children.each do |child|
183
+ child.parent_name = new_name
184
+ end
185
+ cascade_item_renaming(name.to_sym, new_name.to_sym)
186
+ i = @items.delete(i.name)
187
+ i.name = new_name
188
+ @items[new_name] = i
189
+ i
190
+ end
191
+
192
+ # name and new_name are Symbol
193
+ def cascade_item_renaming(name, new_name)
194
+ @set.references.each do |reference|
195
+ next unless reference.foreign_nomenclature == self
196
+ p = reference.property
197
+ if p.list?
198
+ reference.nomenclature.find_each do |item|
199
+ v = item.property(p.name)
200
+ if v && v.include?(name)
201
+ l = v.map do |n|
202
+ n == name ? new_name : n
203
+ end
204
+ item.set(p.name, l)
205
+ end
206
+ end
207
+ else
208
+ reference.nomenclature.find_each do |item|
209
+ v = item.property(p.name)
210
+ item.set(p.name, new_name) if v == name
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ def merge_item(name, into)
217
+ i = find!(name)
218
+ dest = find!(into)
219
+ i.children.each do |child|
220
+ child.parent = dest
221
+ end
222
+ cascade_item_renaming(name.to_sym, into.to_sym)
223
+ @items.delete(name)
224
+ end
225
+
226
+ def remove_item(name)
227
+ i = find!(name)
228
+ @items.delete(name)
229
+ end
230
+
231
+ # Add an property to the nomenclature
232
+ def add_property(name, type, options = {})
233
+ p = Property.new(self, name, type, options)
234
+ if @properties[p.name]
235
+ fail "Property #{p.name} is already defined in nomenclature #{@name}"
236
+ end
237
+ @properties[p.name] = p
238
+ @references = nil
239
+ p
240
+ end
241
+
242
+ def sibling(name)
243
+ @set.find(name)
244
+ end
245
+
246
+ def check!
247
+ # Check properties
248
+ for property in @properties.values
249
+ if property.choices_nomenclature && !property.inline_choices? && !Onoma[property.choices_nomenclature.to_s]
250
+ fail InvalidProperty, "[#{name}] #{property.name} nomenclature property must refer to an existing nomenclature. Got #{property.choices_nomenclature.inspect}. Expecting: #{Onoma.names.inspect}"
251
+ end
252
+ next unless property.type == :choice && property.default
253
+ unless property.choices.include?(property.default)
254
+ fail InvalidProperty, "The default choice #{property.default.inspect} is invalid (in #{name}##{property.name}). Pick one from #{property.choices.sort.inspect}."
255
+ end
256
+ end
257
+
258
+ # Check items
259
+ for item in list
260
+ for property in @properties.values
261
+ choices = property.choices
262
+ if item.property(property.name) && property.type == :choice
263
+ # Cleans for parametric reference
264
+ name = item.property(property.name).to_s.split(/\(/).first.to_sym
265
+ unless choices.include?(name)
266
+ fail InvalidProperty, "The given choice #{name.inspect} is invalid (in #{self.name}##{item.name}). Pick one from #{choices.sort.inspect}."
267
+ end
268
+ elsif item.property(property.name) && property.type == :list && property.choices_nomenclature
269
+ for name in item.property(property.name) || []
270
+ # Cleans for parametric reference
271
+ name = name.to_s.split(/\(/).first.to_sym
272
+ unless choices.include?(name)
273
+ fail InvalidProperty, "The given choice #{name.inspect} is invalid (in #{self.name}##{item.name}). Pick one from #{choices.sort.inspect}."
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ # Default return
281
+ true
282
+ end
283
+
284
+ def inspect
285
+ "Onoma::#{name.to_s.classify}"
286
+ end
287
+
288
+ # Returns hash with items in tree: {a => nil, b => {c => nil}}
289
+ def tree
290
+ x = @roots.collect(&:tree).join
291
+ return x
292
+ i.attributes.merge(parent: i.parent_name, name: i.name, left: i.left, right: i.right, depth: i.depth).deep_stringify_keys
293
+ return x
294
+ @roots.map do |_i|
295
+ end
296
+ end
297
+
298
+ def translateable?
299
+ @translateable
300
+ end
301
+
302
+ # Return human name
303
+ def human_name(options = {})
304
+ "nomenclatures.#{name}.name".t(options.merge(default: ["labels.#{name}".to_sym, name.to_s.humanize]))
305
+ end
306
+ alias_method :humanize, :human_name
307
+
308
+ def new_boundaries(count = 2)
309
+ boundaries = []
310
+ count.times do
311
+ @forest_right += 1
312
+ boundaries << @forest_right
313
+ end
314
+ boundaries
315
+ end
316
+
317
+ # Returns the given item
318
+ def [](item_name)
319
+ @items[item_name]
320
+ end
321
+
322
+ def select(&block)
323
+ list.select(&block)
324
+ end
325
+
326
+ # List all item names. Can filter on a given item name and its children
327
+ def to_a(item_name = nil)
328
+ if item_name.present? && @items[item_name]
329
+ return @items[item_name].self_and_children.map(&:name)
330
+ else
331
+ return @items.keys.sort
332
+ end
333
+ end
334
+ alias_method :all, :to_a
335
+
336
+ def <=>(other)
337
+ name <=> other.name
338
+ end
339
+
340
+ def dependency_index
341
+ unless @dependency_index
342
+ @dependency_index = 0
343
+ properties.each do |_n, p|
344
+ if p.choices_nomenclature && !p.inline_choices?
345
+ @dependency_index += 1 + Onoma[p.choices_nomenclature].dependency_index
346
+ end
347
+ end
348
+ end
349
+ @dependency_index
350
+ end
351
+
352
+ # Returns a list for select as an array of pair (array)
353
+ def selection(item_name = nil)
354
+ items = (item_name ? @items[item_name].self_and_children : @items.values)
355
+ items.collect do |item|
356
+ [item.human_name, item.name.to_s]
357
+ end.sort do |a, b|
358
+ a.first.lower_ascii <=> b.first.lower_ascii
359
+ end
360
+ end
361
+
362
+ # Returns a list for select as an array of pair (hash)
363
+ def selection_hash(item_name = nil)
364
+ @items[item_name].self_and_children.map do |item|
365
+ { label: item.human_name, value: item.name }
366
+ end.sort { |a, b| a[:label].lower_ascii <=> b[:label].lower_ascii }
367
+ end
368
+
369
+ # Returns a list for select, without specified items
370
+ def select_without(already_imported)
371
+ selection = @items.values.collect do |item|
372
+ [item.human_name, item.name.to_s] unless already_imported[item.name.to_s]
373
+ end
374
+ selection.compact!
375
+ selection.sort! do |a, b|
376
+ a.first <=> b.first
377
+ end
378
+ selection
379
+ end
380
+
381
+ # Return first item name
382
+ def first(item_name = nil)
383
+ all(item_name).first
384
+ end
385
+
386
+ # Return the default item name
387
+ def default(item_name = nil)
388
+ first(item_name)
389
+ end
390
+
391
+ # Return the Item for the given name
392
+ def find(item_name)
393
+ @items[item_name]
394
+ end
395
+ alias_method :item, :find
396
+
397
+ def property(property_name)
398
+ @properties[property_name]
399
+ end
400
+
401
+ def find!(item_name)
402
+ unless i = @items[item_name]
403
+ fail "Cannot find item #{item_name} in #{name}"
404
+ end
405
+ i
406
+ end
407
+
408
+ # Returns list of items as an Array
409
+ def list
410
+ Onoma::Relation.new(@items.values)
411
+ end
412
+
413
+ # Iterates on items
414
+ def find_each(&block)
415
+ list.each(&block)
416
+ end
417
+
418
+ # List items with properties filtering
419
+ def where(properties)
420
+ list.select do |item|
421
+ valid = true
422
+ properties.each do |name, value|
423
+ item_value = item.property(name)
424
+ if value.is_a?(Array)
425
+ one_found = false
426
+ value.each do |val|
427
+ if val.is_a?(Onoma::Item)
428
+ one_found = true if item_value == val.name.to_sym
429
+ else
430
+ one_found = true if item_value == val
431
+ end
432
+ end
433
+ valid = false unless one_found
434
+ elsif value.is_a?(Onoma::Item)
435
+ valid = false unless item_value == value.name.to_sym
436
+ else
437
+ valid = false unless item_value == value
438
+ end
439
+ end
440
+ valid
441
+ end
442
+ end
443
+
444
+ def find_by(properties)
445
+ items = where(properties)
446
+ return nil unless items.any?
447
+ items.first
448
+ end
449
+
450
+ # Returns the best match on nomenclature properties
451
+ def best_match(property_name, searched_item)
452
+ items = []
453
+ begin
454
+ list.select do |item|
455
+ items << item if item.property(property_name) == searched_item.name
456
+ end
457
+ break if items.any?
458
+ searched_item = searched_item.parent
459
+ end while searched_item
460
+ items
461
+ end
462
+
463
+ # Returns property nature
464
+ def method_missing(method_name, *args)
465
+ @properties[method_name] || super
466
+ end
467
+
468
+ def cast_options(options)
469
+ return {} if options.nil?
470
+ hash = options.each_with_object({}) do |(k, v), h|
471
+ if properties[k]
472
+ h[k.to_sym] = cast_property(k, v.to_s)
473
+ else
474
+ h[k.to_sym] = v
475
+ end
476
+ end
477
+ end
478
+
479
+ def cast_property(name, value)
480
+ value = value.to_s
481
+ if property = properties[name]
482
+ if property.type == :choice || property.type == :item
483
+ if value =~ /\,/
484
+ fail InvalidProperty, 'A property nature of choice type cannot contain commas'
485
+ end
486
+ value = value.strip.to_sym
487
+ elsif property.list?
488
+ value = value.strip.split(/[[:space:]]*\,[[:space:]]*/).map(&:to_sym)
489
+ elsif property.type == :boolean
490
+ value = (value == 'true' ? true : value == 'false' ? false : nil)
491
+ elsif property.type == :decimal
492
+ value = value.to_d
493
+ elsif property.type == :integer
494
+ value = value.to_i
495
+ elsif property.type == :symbol
496
+ unless value =~ /\A\w+\z/
497
+ fail InvalidProperty, "A property '#{name}' must contains a symbol. /[a-z0-9_]/ accepted. No spaces. Got #{value.inspect}"
498
+ end
499
+ value = value.to_sym
500
+ end
501
+ elsif !%w(name parent aliases).include?(name.to_s)
502
+ fail ArgumentError, "Undefined property '#{name}' in #{@name}"
503
+ end
504
+ value
505
+ end
506
+ end
507
+ end