card 1.106.0 → 1.107.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/config/environments/development.rb +0 -3
  4. data/config/environments/production.rb +0 -3
  5. data/config/environments/test.rb +0 -2
  6. data/lib/card/auth/current.rb +1 -1
  7. data/lib/card/auth/permissions.rb +1 -9
  8. data/lib/card/fetch/all.rb +1 -1
  9. data/lib/card/query/abstract_query/tie.rb +2 -2
  10. data/lib/card/view/permission.rb +1 -1
  11. data/lib/cardio/command/rspec_command/parser.rb +1 -9
  12. data/lib/cardio/command.rb +1 -0
  13. data/lib/cardio/migration.rb +6 -1
  14. data/lib/cardio/mod/dirs.rb +6 -5
  15. data/lib/cardio/mod/eat/edibles.rb +1 -1
  16. data/lib/cardio/mod/sow/card_source.rb +48 -0
  17. data/lib/cardio/mod/sow/yaml_dump.rb +25 -0
  18. data/lib/cardio/mod/sow.rb +22 -64
  19. data/lib/cardio/mod.rb +3 -2
  20. data/lib/cardio/railtie.rb +0 -1
  21. data/lib/generators/deck/templates/cypress.json.erb +1 -1
  22. data/mod/core/config/admin.yml +10 -0
  23. data/mod/core/data/real.yml +0 -1
  24. data/mod/core/data/transform/20141204061304_watchers_to_following.rb +1 -1
  25. data/mod/core/lib/admin_item.rb +21 -0
  26. data/mod/core/lib/tasks/card/seed.rake +1 -1
  27. data/mod/core/lib/tasks/card.rake +56 -25
  28. data/mod/core/set/abstract/task_table.rb +16 -0
  29. data/mod/core/set/all/admin.rb +108 -0
  30. data/mod/core/set/self/admin.rb +3 -12
  31. data/mod/core/set/self/mod.rb +39 -0
  32. data/mod/core/set/self/version.rb +1 -1
  33. data/mod/core/set/type/mod.rb +227 -0
  34. data/mod/core/spec/set/all/admin_spec.rb +34 -0
  35. data/mod/core/spec/set/type/mod_spec.rb +30 -0
  36. data/mod/core/spec/shared_examples/mod_admin_config.rb +10 -0
  37. metadata +16 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a8e1bc39a0131940fbec71fd5a92601c334c895d67d4781e8b03c109d7e0f59
4
- data.tar.gz: 14bbcc9ac8d90d9a4f3a361710510d43f516ebe7a147f028c31b6cf903b3fd67
3
+ metadata.gz: df537d34eb8d348b41930c7ce3e6de5ed600b4d7e8df87ba2d935b2e259ae8e6
4
+ data.tar.gz: e108f9e1f0833c0aac0248c6ec59bf41c18aef70e09e42ea8bd364f94705fe7c
5
5
  SHA512:
6
- metadata.gz: d215a5fa5a0557d60a9a17ee7a6a4ee134206281ea96c7d3c3665be0f81756fc251055ea27a52c7f0ec2652cc2b91b559006f17816d43594c0fbebcee1c7baf9
7
- data.tar.gz: e2af61cd07919b7759d9af5f66507f06d5320d8b430974ffa296512466e7d61fd4b5bb56fe2af3120ac0928c19420daa53156cce7fee2b1b3244bfcd434da582
6
+ metadata.gz: ec38c34b70331bf7a919963b7337682e1456286ec5007db1d6d5408f270bb326bb6c55cd2c8af455bb7ed2794d8bd7ae1012dc3eb741d89ba2ce575a63c804bd
7
+ data.tar.gz: 901b1dc8198578fac3b59040919c02520b177ff8e240c4cebb0a1adb6c978054491b62aa4cc44a3a3514b114645b914a23515ffb36d71ab39b36054fe31c7cd7
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.16.0
1
+ 0.17.0
@@ -38,9 +38,6 @@ Cardio.application.class.configure do
38
38
  # in the nest where the error occurred
39
39
  config.raise_all_rendering_errors = true
40
40
 
41
- # if false, application will raise errors that make it to controller.
42
- config.rescue_all_in_controller = false
43
-
44
41
  # config.performance_logger = {
45
42
  # methods: [:event, :search, :fetch, :view], # choose methods to log
46
43
  # min_time: 100, # show only method calls that are slower than 100ms
@@ -69,7 +69,4 @@ Cardio.application.class.configure do
69
69
 
70
70
  # cache the list of set module objects on card objects
71
71
  config.cache_set_module_list = true
72
-
73
- # if false, application will raise errors that make it to controller.
74
- config.rescue_all_in_controller = true
75
72
  end
@@ -59,6 +59,4 @@ Cardio.application.class.configure do
59
59
  config.active_support.deprecation = :stderr
60
60
 
61
61
  config.raise_all_rendering_errors = true
62
-
63
- config.rescue_all_in_controller = false
64
62
  end
@@ -56,7 +56,7 @@ class Card
56
56
  # get :user id from session and set Auth.current_id
57
57
  def signin_with_session
58
58
  card_id = session[session_user_key]
59
- card_id = nil unless Card.exists? card_id
59
+ card_id = nil unless card_id.card&.account?
60
60
  signin card_id
61
61
  end
62
62
 
@@ -34,15 +34,7 @@ class Card
34
34
  # @param user_mark [Cardish]
35
35
  # @return [true/false]
36
36
  def admin? user_mark=nil
37
- user_mark ||= as_id
38
- has_role? Card::AdministratorID, user_mark
39
- end
40
-
41
- def has_role? role_mark, user_mark=nil
42
- user_mark ||= as_id
43
- return false unless (role_id = role_mark&.card_id)
44
-
45
- Card[user_mark].all_enabled_roles.include? role_id
37
+ (user_mark || as_id).card&.admin?
46
38
  end
47
39
 
48
40
  def update_always_cache value
@@ -6,7 +6,7 @@ class Card
6
6
  def fetch field_marks, opts={}
7
7
  opts[:new][:supercard] = self if opts[:new]
8
8
  Array.wrap(field_marks).inject(self) do |card, mark|
9
- Card.fetch card.name.field(mark.cardname), opts
9
+ Card.fetch (card.id || card.name), mark, opts
10
10
  end
11
11
  end
12
12
 
@@ -2,9 +2,9 @@ class Card
2
2
  module Query
3
3
  class AbstractQuery
4
4
  # The "Tie" methods support tying two queries (CardQuery, ReferenceQuery, etc)
5
- # together. The "fasten" variable determines which tying strategy is used.
5
+ # together. The "subquery_type" variable determines which tying strategy is used.
6
6
  #
7
- # We currently support three values for "fasten": :join, :exist, and :in
7
+ # We currently support three values for "subquery_type": :join, :exist, and :in
8
8
  #
9
9
  # In concept, here's how the different strategies would tie table A to table B
10
10
  # in SQL assuming A.id = B.a_id
@@ -46,7 +46,7 @@ class Card
46
46
  def denial
47
47
  return unless (task = denied_task)
48
48
 
49
- format.view_for_denial requested_view, task
49
+ format.view_for_denial requested_view, (crud?(task) && task)
50
50
  end
51
51
 
52
52
  def crud? task
@@ -24,12 +24,10 @@ module Cardio
24
24
 
25
25
  RSPEC ARGS
26
26
 
27
- See https://relishapp.com/rspec/rspec-core/docs/command-line
27
+ See https://rspec.info/features/3-12/rspec-core/command-line/ or run card rspec -- -hbe
28
28
  BANNER
29
29
 
30
30
  DESC = {
31
- d: "Run spec for a Decko deck file",
32
- c: "Run spec for a Decko core file",
33
31
  m: "Run all specs for a mod or matching a mod"
34
32
  }.freeze
35
33
 
@@ -47,15 +45,9 @@ module Cardio
47
45
  private
48
46
 
49
47
  def file_options parser, opts
50
- parser.on("-d", "--spec FILENAME(:LINE)", DESC[:d]) do |file|
51
- opts[:files] = find_spec_file(file, "#{Decko.root}/mod")
52
- end
53
48
  parser.on("-m", "--mod MODNAME", DESC[:m]) do |file|
54
49
  opts[:files] = find_mod_file(file, Cardio.gem_root)
55
50
  end
56
- parser.on("-c", "--core-spec FILENAME(:LINE)", DESC[:c]) do |file|
57
- opts[:files] = find_spec_file(file, Cardio.gem_root)
58
- end
59
51
  end
60
52
 
61
53
  def other_options parser, opts
@@ -38,6 +38,7 @@ module Cardio
38
38
  runner: { desc: "run code in app environment", group: :monkey, alias: :r },
39
39
  rspec: { desc: "run rspec tests", group: :monkey, alias: :rs, via: :call },
40
40
  generate: { desc: "generate templated code", group: :monkey, alias: :g },
41
+ reset: { desc: "reset cache and tmpfiles", group: :monkey, via: :rake },
41
42
  sow: { desc: "export card data to mod yaml", group: :monkey, via: :rake },
42
43
  eat: { desc: "ingest card data from mod yaml", group: :monkey, via: :rake }
43
44
  }
@@ -25,12 +25,17 @@ module Cardio
25
25
  def port
26
26
  return unless connection.table_exists? old_deck_table
27
27
  rename_old_tables
28
- connection.execute "INSERT INTO #{table} (SELECT * from #{old_deck_table})"
28
+ connection.execute "INSERT INTO #{table} (#{select_nonduplicate_versions})"
29
29
  connection.drop_table old_deck_table
30
30
  end
31
31
 
32
32
  private
33
33
 
34
+ def select_nonduplicate_versions
35
+ "SELECT * FROM #{old_deck_table} o WHERE NOT EXISTS " \
36
+ "(SELECT * FROM #{table} n WHERE o.version = n.version)"
37
+ end
38
+
34
39
  def rename_old_tables
35
40
  old_tables.each do |old_table_name|
36
41
  next unless connection.table_exists? old_table_name
@@ -63,7 +63,7 @@ module Cardio
63
63
  end
64
64
 
65
65
  # Add a mod to mod load paths
66
- def add_mod mod_name, path: nil, group: nil
66
+ def add_mod mod_name, path: nil, group: nil, spec: nil
67
67
  if @mods_by_name.key? Mod.normalize_name(mod_name)
68
68
  raise StandardError,
69
69
  "name conflict: mod with name \"#{mod_name}\" already loaded"
@@ -72,7 +72,7 @@ module Cardio
72
72
  path ||= File.join @current_path, mod_name
73
73
  group ||= @current_group
74
74
 
75
- mod = Mod.new mod_name, path, group, @mods.size
75
+ mod = Mod.new mod_name, path, group: group, index: @mods.size, spec: spec
76
76
  @mods << mod
77
77
  @mods_by_name[mod.name] = mod
78
78
  end
@@ -139,15 +139,16 @@ module Cardio
139
139
 
140
140
  def add_gem_mods
141
141
  Cardio::Mod.gem_specs.each do |mod_name, spec|
142
- add_gem_mod mod_name, spec.full_gem_path, spec.metadata["card-mod-group"]
142
+ add_gem_mod mod_name, spec
143
143
  end
144
144
  end
145
145
 
146
- def add_gem_mod mod_name, mod_path, group
146
+ def add_gem_mod mod_name, spec
147
147
  return if @loaded_gem_mods.include?(mod_name)
148
148
 
149
149
  @loaded_gem_mods << mod_name
150
- add_mod mod_name, path: mod_path, group: (group || "gem")
150
+ group = spec.metadata["card-mod-group"] || "gem"
151
+ add_mod mod_name, path: spec.full_gem_path, group: group, spec: spec
151
152
  end
152
153
 
153
154
  def add_core_mods
@@ -14,7 +14,7 @@ module Cardio
14
14
  def explicit_edibles
15
15
  return yield unless @name
16
16
 
17
- yield.reject do |edible|
17
+ yield.select do |edible|
18
18
  if @name.match?(/^\:/)
19
19
  explicit_codename_match? edible[:codename]
20
20
  else
@@ -0,0 +1,48 @@
1
+ module Cardio
2
+ class Mod
3
+ class Sow
4
+ # Fetch sow data form cards
5
+ module CardSource
6
+ def new_data_from_cards
7
+ cards.map { |c| c.pod_hash field_tags: field_tag_marks }
8
+ end
9
+
10
+ def field_tag_marks
11
+ @field_tag_marks ||= @field_tags.to_s.split(",").map do |mark|
12
+ mark.strip.cardname.codename_or_string
13
+ end
14
+ end
15
+
16
+ def cards
17
+ if @name
18
+ cards_from_name
19
+ elsif @cql
20
+ Card.search JSON.parse(@cql).reverse_merge(limit: 0)
21
+ else
22
+ raise Card::Error::NotFound, "must specify either name (-n) or CQL (-c)"
23
+ end
24
+ end
25
+
26
+ def cards_from_name
27
+ case @items
28
+ when :only then item_cards
29
+ when true then main_cards + item_cards
30
+ else main_cards
31
+ end
32
+ end
33
+
34
+ def item_cards
35
+ main_cards.map(&:item_cards).flatten
36
+ end
37
+
38
+ def main_cards
39
+ @main_cards ||= @name.split(",").map { |n| require_card n }
40
+ end
41
+
42
+ def require_card name
43
+ Card.fetch(name) || raise(Card::Error::NotFound, "card not found: #{name}")
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,25 @@
1
+ module Cardio
2
+ class Mod
3
+ class Sow
4
+ # Writing the card representations to yaml files in mod directories
5
+ module YamlDump
6
+ # write yaml to file
7
+ def dump hash
8
+ File.write filename, hash.to_yaml
9
+ puts "#{filename} now contains #{hash.size} items".green
10
+ end
11
+
12
+ # @return [String] -- MOD_DIR/data/ENVIRONMENT.yml
13
+ def filename
14
+ @filename ||= File.join mod_path, "#{@podtype}.yml"
15
+ end
16
+
17
+ # @return Path
18
+ def mod_path
19
+ Mod.dirs.subpaths("data")[@mod] ||
20
+ raise(Card::Error::NotFound, "no data directory found for mod: #{@mod}")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -5,10 +5,14 @@ module Cardio
5
5
  #
6
6
  # https://docs.google.com/document/d/13K_ynFwfpHwc3t5gnLeAkZJZHco1wK063nJNYwU8qfc/edit#
7
7
  class Sow
8
+ include YamlDump
9
+ include CardSource
10
+
8
11
  def initialize **args
9
12
  @mod = args[:mod]
10
13
  @name = args[:name]
11
14
  @cql = args[:cql]
15
+ @url = args[:url]
12
16
  @podtype = args[:podtype] || (Rails.env.test? ? :test : :real)
13
17
  @items = args[:items]
14
18
  @field_tags = args[:field_tags]
@@ -17,7 +21,7 @@ module Cardio
17
21
  # if output mod given,
18
22
  def out
19
23
  Card::Cache.reset_all
20
- @mod ? dump : puts(new_data.to_yaml.yellow)
24
+ @mod ? dump(output_hash) : puts(new_data.to_yaml.yellow)
21
25
  :success
22
26
  rescue Card::Error::NotFound => e
23
27
  e.message
@@ -27,59 +31,6 @@ module Cardio
27
31
 
28
32
  private
29
33
 
30
- # @return [Array <Hash>]
31
- def new_data
32
- @new_data ||= cards.map { |c| c.pod_hash field_tags: field_tag_marks }
33
- end
34
-
35
- def field_tag_marks
36
- @field_tag_marks ||= @field_tags.to_s.split(",").map do |mark|
37
- mark.strip.cardname.codename_or_string
38
- end
39
- end
40
-
41
- # @return [String] -- MOD_DIR/data/ENVIRONMENT.yml
42
- def filename
43
- @filename ||= File.join mod_path, "#{@podtype}.yml"
44
- end
45
-
46
- # write yaml to file
47
- def dump
48
- hash = output_hash
49
- File.write filename, hash.to_yaml
50
- puts "#{filename} now contains #{hash.size} items".green
51
- end
52
-
53
- def cards
54
- if @name
55
- cards_from_name
56
- elsif @cql
57
- Card.search JSON.parse(@cql).reverse_merge(limit: 0)
58
- else
59
- raise Card::Error::NotFound, "must specify either name (-n) or CQL (-c)"
60
- end
61
- end
62
-
63
- def cards_from_name
64
- case @items
65
- when :only then item_cards
66
- when true then main_cards + item_cards
67
- else main_cards
68
- end
69
- end
70
-
71
- def item_cards
72
- main_cards.map(&:item_cards).flatten
73
- end
74
-
75
- def main_cards
76
- @main_cards ||= @name.split(",").map { |n| require_card n }
77
- end
78
-
79
- def require_card name
80
- Card.fetch(name) || raise(Card::Error::NotFound, "card not found: #{name}")
81
- end
82
-
83
34
  def output_hash
84
35
  if target.present?
85
36
  merge_data
@@ -89,6 +40,12 @@ module Cardio
89
40
  end
90
41
  end
91
42
 
43
+ # @return [Array <Hash>]
44
+ def new_data
45
+ @new_data ||=
46
+ @url ? pod_hash_from_url : new_data_from_cards
47
+ end
48
+
92
49
  def merge_data
93
50
  new_data.each do |item|
94
51
  if (index = target_index item)
@@ -99,6 +56,10 @@ module Cardio
99
56
  end
100
57
  end
101
58
 
59
+ def target
60
+ @target ||= (old_data || nil)
61
+ end
62
+
102
63
  def target_index new_item
103
64
  new_code = new_item[:codename]
104
65
  new_name = new_item[:name].to_name
@@ -109,20 +70,17 @@ module Cardio
109
70
  end
110
71
  end
111
72
 
112
- def target
113
- @target ||= (old_data || nil)
114
- end
115
-
116
73
  def old_data
117
74
  return unless File.exist? filename
118
- # YAML.safe_load File.read(filename), [Symbol] if File.exist? filename
119
- YAML.safe_load File.read(filename), permitted_classes: [Symbol]
75
+ parse_pod_yaml File.read(filename)
76
+ end
77
+
78
+ def pod_hash_from_url
79
+ parse_pod_yaml URI.open(@url).read
120
80
  end
121
81
 
122
- # @return Path
123
- def mod_path
124
- Mod.dirs.subpaths("data")[@mod] ||
125
- raise(Card::Error::NotFound, "no data directory found for mod: #{@mod}")
82
+ def parse_pod_yaml pod_yaml
83
+ YAML.safe_load pod_yaml, permitted_classes: [Symbol]
126
84
  end
127
85
  end
128
86
  end
data/lib/cardio/mod.rb CHANGED
@@ -42,13 +42,14 @@ module Cardio
42
42
  class Mod
43
43
  extend ClassMethods
44
44
 
45
- attr_reader :name, :path, :group, :index
45
+ attr_reader :name, :path, :group, :index, :spec
46
46
 
47
- def initialize name, path, group, index
47
+ def initialize name, path, group:, index:, spec: nil
48
48
  @name = Mod.normalize_name name
49
49
  @path = required_path path
50
50
  @group = group || :custom
51
51
  @index = index
52
+ @spec = spec
52
53
  end
53
54
 
54
55
  def mod_card_name
@@ -36,7 +36,6 @@ module Cardio
36
36
 
37
37
  config.allow_irreversible_admin_tasks = false
38
38
  config.raise_all_rendering_errors = false
39
- config.rescue_all_in_controller = true
40
39
 
41
40
  config.cache_set_module_list = false
42
41
 
@@ -4,5 +4,5 @@
4
4
  "watchForFileChanges": false,
5
5
  "projectId": "n4h7vq",
6
6
  "integrationFolder": "<%= expanded_repo_path %>/decko/spec/cypress/integration",
7
- "supportFile": "<%= expanded_repo_path %>/decko/spec/cypress/support/index.js"
7
+ "supportFile": "<%= expanded_repo_path %>/decko/spec/cypress/support/e2e.js"
8
8
  }
@@ -0,0 +1,10 @@
1
+ cardtypes:
2
+ admin:
3
+ - mod
4
+ - user
5
+ - setting
6
+ - cardtype
7
+ tasks:
8
+ - empty_trash
9
+ - clear_cash
10
+ - regenerate_assets
@@ -30,7 +30,6 @@
30
30
  :type: :cardtype
31
31
  :codename: setting
32
32
 
33
-
34
33
  - :name: "*version"
35
34
  :codename: version
36
35
  - :name: "*autoname"
@@ -20,7 +20,7 @@ class WatchersToFollowing < Cardio::Migration::Transform
20
20
  end
21
21
 
22
22
  follower_hash.each do |user, items|
23
- next unless (card = Card.fetch(user)) && card.account
23
+ next unless (card = user.card)&.account?
24
24
 
25
25
  following = card.fetch "following", new: { type_code: :pointer }
26
26
  items.each { |item| following.add_item item }
@@ -0,0 +1,21 @@
1
+ # represents an entry in admin.yml
2
+ class AdminItem
3
+ attr_reader :mod, :category, :subcategory, :codename
4
+ attr_accessor :roles
5
+
6
+ def initialize mod, category, subcategory, codename
7
+ @mod = mod
8
+ @category = category
9
+ @subcategory = subcategory
10
+ @codename = codename
11
+ end
12
+
13
+ def title
14
+ config_titles = Card::Set::All::Admin.basket[:config_title]
15
+ if subcategory
16
+ config_titles[subcategory.to_sym] || subcategory.capitalize
17
+ else
18
+ config_titles[category.to_sym] || category.capitalize
19
+ end
20
+ end
21
+ end
@@ -45,7 +45,7 @@ namespace :card do
45
45
  ENV["CARD_UPDATE_SEED"] = "true"
46
46
  # tells Cardio::Seed to use fixtures upon which the seeds being updated depend
47
47
 
48
- invoke_card_tasks %w[reset_tmp seed:replant]
48
+ invoke_card_tasks %w[reset seed:replant]
49
49
  end
50
50
 
51
51
  def invoke_card_tasks tasks
@@ -4,6 +4,8 @@ namespace :card do
4
4
  desc "Creates the database, loads the schema, initializes seed data, " \
5
5
  "and adds symlinks to public directories"
6
6
  task setup: %w[db:setup card:mod:symlink]
7
+ # task setup: %w[db:setup card:update] # can't do update yet, because it overrides
8
+ # coded assets, which breaks testing
7
9
 
8
10
  desc "Runs migrations, installs mods, and updates symlinks"
9
11
  task :update do
@@ -11,7 +13,7 @@ namespace :card do
11
13
  ENV["NO_RAILS_CACHE"] = "true"
12
14
  # Benchmark.bm do |x|
13
15
  ["migrate:port", "migrate:schema", "migrate:recode", :eat, "migrate:transform",
14
- :reset_tmp, :reset_cache,
16
+ :reset,
15
17
  "mod:uninstall", "mod:install", "mod:symlink"].each do |task|
16
18
  Rake::Task["card:#{task}"].invoke
17
19
  end
@@ -22,15 +24,26 @@ namespace :card do
22
24
  task eat: :environment do
23
25
  parse_options :eat do
24
26
  add_opt :m, :mod, "only eat cards in given mod"
25
- add_opt :n, :name, "only eat card with name"
26
- # FIXME: - name seems not to work, especially in combination with other options
27
-
28
- add_opt :u, :user, "user to credit unless specified (otherwise uses Decko Bot)"
27
+ add_opt :n, :name, "only eat card with name (handles : for codenames)"
28
+ add_opt :u, :user, "user to credit unless specified (default is Decko Bot)"
29
29
  add_opt :p, :podtype, "pod type: real, test, or all " \
30
30
  "(defaults to all in test env, otherwise real)"
31
+ add_opt :e, :env, "environment (test, production, etc)"
31
32
  flag_opt :v, :verbose, "output progress info and error backtraces"
32
33
  end
33
- rake_result(:eat) { Cardio::Mod::Eat.new(**options).up }
34
+
35
+ adjust_environment options, :eat do
36
+ rake_result(:eat) { Cardio::Mod::Eat.new(**options).up }
37
+ end
38
+ end
39
+
40
+ def adjust_environment options, task
41
+ if (env = options.delete(:env))
42
+ task_options = options.map { |k, v| "--#{k}=#{v}" }.join(" ")
43
+ system "env RAILS_ENV=#{env} bundle exec rake card:#{task} #{task_options}"
44
+ else
45
+ yield
46
+ end
34
47
  end
35
48
 
36
49
  desc "Exports card data to mod yaml"
@@ -40,30 +53,25 @@ namespace :card do
40
53
  flag_opt :i, :items, "also export card items (with -n)"
41
54
  flag_opt :o, :only_items, "only export card items (with -n)", items: :only
42
55
  add_opt :c, :cql, "export cards found by CQL (in JSON format)"
56
+ add_opt_without_shortcut :url, "source card details from url"
43
57
  add_opt :m, :mod, "output yaml file in mod"
44
58
  add_opt :p, :podtype, "podtype to dump (real or test. default based on current env)"
45
59
  add_opt :t, :field_tags, "comma-separated list of field tag marks"
60
+ add_opt :e, :env, "environment (test, production, etc)"
61
+ end
62
+ adjust_environment options, :sow do
63
+ rake_result(:sow) { Cardio::Mod::Sow.new(**options).out }
46
64
  end
47
- rake_result(:sow) { Cardio::Mod::Sow.new(**options).out }
48
- end
49
-
50
- desc "Resets cache"
51
- task reset_cache: :environment do
52
- Card::Cache.reset_all
53
65
  end
54
66
 
55
- desc "reset with an empty tmp directory"
56
- task :reset_tmp do
57
- tmp_dir = Cardio.paths["tmp"].first
58
- if Cardio.paths["tmp"].existent
59
- Dir.foreach(tmp_dir) do |filename|
60
- next if filename.match?(/^\./)
61
-
62
- FileUtils.rm_rf File.join(tmp_dir, filename), secure: true
63
- end
64
- else
65
- Dir.mkdir tmp_dir
67
+ desc "Clears both cache and tmpfiles"
68
+ task reset: :environment do
69
+ parse_options :reset do
70
+ flag_opt :c, :cache, "cache only"
71
+ flag_opt :t, :tmpfiles, "tmpfiles only"
66
72
  end
73
+ reset_tmpfiles unless options[:cache]
74
+ Card::Cache.reset_all unless options[:tmpfiles]
67
75
  end
68
76
 
69
77
  desc "Loads seed data"
@@ -89,17 +97,27 @@ namespace :card do
89
97
  end
90
98
 
91
99
  def add_opt letter, key, desc
92
- op.on "-#{letter}", "--#{key.to_s.tr '_', '-'} #{key.to_s.upcase}", desc do |val|
100
+ op.on "-#{letter}", key_to_option_description(key), desc do |val|
93
101
  options[key] = val
94
102
  end
95
103
  end
96
104
 
105
+ def add_opt_without_shortcut key, desc
106
+ op.on key_to_option_description(key), desc do |val|
107
+ options[key] = val
108
+ end
109
+ end
110
+
111
+ def key_to_option_description key
112
+ "--#{key.to_s.tr '_', '-'} #{key.to_s.upcase}"
113
+ end
114
+
97
115
  def op
98
116
  @op ||= OptionParser.new
99
117
  end
100
118
 
101
119
  def parse_options task
102
- op.banner = "Usage: rake card:#{task} -- [options]"
120
+ op.banner = "Usage: card #{task} [options]"
103
121
  yield if block_given?
104
122
  args = op.order!(ARGV) {}
105
123
  # args << "-h" if args.empty?
@@ -114,4 +132,17 @@ namespace :card do
114
132
  raise "\n>>>>>> FAILURE! #{task} did not complete successfully." \
115
133
  "\n>>>>>> Please address errors and re-run:\n\n\n"
116
134
  end
135
+
136
+ def reset_tmpfiles
137
+ tmp_dir = Cardio.paths["tmp"].first
138
+ if Cardio.paths["tmp"].existent
139
+ Dir.foreach(tmp_dir) do |filename|
140
+ next if filename.match?(/^\./)
141
+
142
+ FileUtils.rm_rf File.join(tmp_dir, filename), secure: true
143
+ end
144
+ else
145
+ Dir.mkdir tmp_dir
146
+ end
147
+ end
117
148
  end
@@ -0,0 +1,16 @@
1
+ format :html do
2
+ def task_row task, mod
3
+ base = "#{mod}_task_#{task}"
4
+ [
5
+ link_to_card(:admin, t("#{base}_link_text"), path: { action: :update, task: task }),
6
+ t("#{base}_description")
7
+ ]
8
+ end
9
+
10
+ def task_table tasks
11
+ table_content = tasks.map do |task, task_config|
12
+ task_row task, task_config[:mod]
13
+ end
14
+ table table_content, header: %w[Task Description]
15
+ end
16
+ end
@@ -1,5 +1,9 @@
1
1
 
2
2
  basket[:tasks] = {}
3
+ basket[:config_title] = {
4
+ basic: "Basic configuration",
5
+ editor: "Editor configuration"
6
+ }
3
7
  # to add an admin task:
4
8
  #
5
9
  # basket[:tasks][TASK_NAME] = {
@@ -12,3 +16,107 @@ basket[:tasks] = {}
12
16
 
13
17
  # MOD_task_TASK_NAME_link_text: LINK_TEXT
14
18
  # MOD_task_TASK_NAME_description: DESCRIPTION
19
+
20
+ def mod_cards_with_config
21
+ Card.search(type: :mod).select { |mod| mod.admin_config.present? }
22
+ end
23
+
24
+ def create_admin_items mod, category, subcategory, values
25
+ Array.wrap(values).map do |value|
26
+ ::AdminItem.new(mod, category, subcategory, value).tap do |config|
27
+ codenamed = Card::Codename.exist? config.codename.to_sym
28
+ config.roles = codenamed ? Card[config.codename.to_sym].responsible_role : []
29
+ end
30
+ end
31
+ end
32
+
33
+ def scoping_rule_card
34
+ Card.fetch([self, :self, :update], new: {})
35
+ end
36
+
37
+ def responsible_role
38
+ scoping_rule_card.find_existing_rule_card.item_cards.map(&:codename)
39
+ end
40
+
41
+ def all_configs
42
+ mod_cards_with_config.map(&:admin_config_objects).flatten
43
+ end
44
+
45
+ def all_admin_configs_grouped_by property1, property2=nil
46
+ return admin_config_group_by_properties property1, property2 if property2
47
+
48
+ result = Hash.new { |hash, k| hash[k] = [] }
49
+ all_configs.each_with_object(result) do |config, h|
50
+ property_values = Array.wrap(config.send(property1))
51
+ property_values.each do |value|
52
+ h[value] << config
53
+ end
54
+ end
55
+ end
56
+
57
+ def all_admin_configs_of_category category
58
+ all_admin_configs_grouped_by(:category)[category]
59
+ end
60
+
61
+ def config_codenames_grouped_by_title configs
62
+ configs&.group_by { |c| c.title }&.map do |title, grouped_configs|
63
+ [title, grouped_configs.map { |config| config.codename.to_sym }]
64
+ end
65
+ end
66
+
67
+ format :html do
68
+ def section title, content
69
+ "<p>#{section_title(title)}#{content}</p>"
70
+ end
71
+
72
+ def section_title title
73
+ "<h3>#{title}</h3>"
74
+ end
75
+
76
+ def list_section title, items, item_view=:bar
77
+ return unless items.present?
78
+
79
+ section title, list_section_content(items, item_view)
80
+ end
81
+
82
+ def nested_list_section title, grouped_items
83
+ output [
84
+ section_title(title),
85
+ wrap_with(:div, accordion_sections(grouped_items), class: "accordion")
86
+ ]
87
+ end
88
+
89
+ def accordion_sections grouped_items
90
+ return unless grouped_items.present?
91
+
92
+ grouped_items.map do |title, codenames|
93
+ accordion_item(title,
94
+ subheader: nil,
95
+ body: list_section_content(codenames),
96
+ open: false,
97
+ context: title.hash)
98
+ end.join " "
99
+ end
100
+
101
+ def list_section_content items, item_view=:bar
102
+ items&.map do |card|
103
+ nest card, view: item_view
104
+ end&.join(" ")
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def admin_config_group_by_properties property1, property2
111
+ result = Hash.new { |hash, k| hash[k] = Hash.new { |hash2, k2| hash2[k2] = [] } }
112
+ all_configs.each_with_object(result) do |config, h|
113
+ property1_values = Array.wrap(config.send(property1))
114
+ property2_values = Array.wrap(config.send(property2))
115
+
116
+ property1_values.each do |p1v|
117
+ property2_values.each do |p2v|
118
+ h[p1v][p2v] << config
119
+ end
120
+ end
121
+ end
122
+ end
@@ -1,3 +1,5 @@
1
+ include_set Abstract::TaskTable
2
+
1
3
  basket[:tasks].merge!(
2
4
  clear_cache: {
3
5
  mod: :core,
@@ -54,18 +56,7 @@ end
54
56
 
55
57
  format :html do
56
58
  view :core, cache: :never do
57
- table_content = basket[:tasks].map do |task, task_config|
58
- task_row task, task_config[:mod]
59
- end
60
- table table_content, header: %w[Task Description]
61
- end
62
-
63
- def task_row task, mod
64
- base = "#{mod}_task_#{task}"
65
- [
66
- link_to_card(:admin, t("#{base}_link_text"), path: { action: :update, task: task }),
67
- t("#{base}_description")
68
- ]
59
+ task_table basket[:tasks]
69
60
  end
70
61
 
71
62
  view :warning do
@@ -0,0 +1,39 @@
1
+ include_set Abstract::List
2
+
3
+ def item_codenames
4
+ Cardio.mods.map do |mod|
5
+ "#{mod}_mod"
6
+ end
7
+ end
8
+
9
+ def content
10
+ item_codenames.map(&:cardname).compact.to_pointer_content
11
+ end
12
+
13
+ format :html do
14
+ %i[cardtypes settings tasks configurations].each do |view_name|
15
+ view view_name do
16
+ [
17
+ content_tag(:h1, view_name),
18
+ card.all_admin_configs_grouped_by(:category, :mod)[view_name.to_s]
19
+ .map do |(mod, configs)|
20
+ list_section(mod.name, configs.map { |c| c.codename.to_sym })
21
+ end.join("<br\>")
22
+ ]
23
+ end
24
+ end
25
+
26
+ view :roles do
27
+ [
28
+ content_tag(:h1, "Roles"),
29
+ card.all_admin_configs_grouped_by(:roles, :category).map do |(role, configs_by_cat)|
30
+ output [
31
+ content_tag(:h2, Card[role.to_sym].name),
32
+ (configs_by_cat.map do |(cat, configs)|
33
+ list_section cat, configs.map(&:codename)
34
+ end)
35
+ ]
36
+ end.join("<br\>")
37
+ ]
38
+ end
39
+ end
@@ -1,6 +1,6 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
- def ok_to_read
3
+ def ok_to_read?
4
4
  true
5
5
  end
6
6
 
@@ -0,0 +1,227 @@
1
+ # TODO: We can't detect file removal for folder group
2
+
3
+ include_set Abstract::List
4
+ include_set Abstract::TaskTable
5
+
6
+ format :html do
7
+ view :core do
8
+ render_section_views(%i[
9
+ description
10
+ settings
11
+ configurations
12
+ cardtypes
13
+ styles
14
+ scripts
15
+ tasks
16
+ views
17
+ depends_on
18
+ ].select { |name| card.send("#{name}?") })
19
+ end
20
+
21
+ view :description do
22
+ content_tag(:p, card.description)
23
+ end
24
+
25
+ def render_section_views list
26
+ list.map { |view_name| send("render_#{view_name}") }.compact.join "<br/>"
27
+ end
28
+
29
+ view :settings do
30
+ list_section "Settings", card.settings
31
+ end
32
+
33
+ view :cardtypes do
34
+ nested_list_section "Cardtypes", card.cardtypes
35
+ end
36
+
37
+ view :configurations do
38
+ return unless card.configurations
39
+
40
+ card.configurations.map do |category, names|
41
+ list_section("#{category.capitalize} Configuration",
42
+ names.map(&:to_sym))
43
+ end.join " "
44
+ end
45
+
46
+ view :tasks do
47
+ tasks = card.tasks
48
+ return unless tasks.present?
49
+
50
+ section "Tasks", task_table(tasks)
51
+ end
52
+
53
+ view :styles do
54
+ style = card.fetch :style
55
+ return unless style
56
+ section "Styles", nest(style, view: :core)
57
+ end
58
+
59
+ view :scripts do
60
+ style = card.fetch :script
61
+ return unless style
62
+ section "Scripts", nest(style, view: :core)
63
+ end
64
+
65
+ view :gem_info do
66
+ return unless card.mod&.spec
67
+ properties =
68
+ %w[name summary version authors description email homepage].map do |property|
69
+ "#{property}: #{card.mod.spec.send(property)}"
70
+ end
71
+
72
+ section "Gem info",
73
+ list_group(properties) +
74
+ accordion_item("files",
75
+ body: list_group(card.mod.spec.files),
76
+ context: "files") +
77
+ accordion_item("depends on ",
78
+ body: list_section_content(card.depends_on),
79
+ context: "depends_on")
80
+ end
81
+
82
+ view :depends_on do
83
+ list_section "Depends on", card.depends_on
84
+ end
85
+
86
+ view :views do
87
+ section "Views", list_group(card.views)
88
+ end
89
+ end
90
+
91
+ def settings?
92
+ settings.present?
93
+ end
94
+
95
+ def cardtypes?
96
+ cardtypes.present?
97
+ end
98
+
99
+ def configurations?
100
+ configurations.present?
101
+ end
102
+
103
+ def tasks?
104
+ tasks.present?
105
+ end
106
+
107
+ def styles?
108
+ fetch(:style).present?
109
+ end
110
+
111
+ def scripts?
112
+ fetch(:script).present?
113
+ end
114
+
115
+ def depends_on?
116
+ mod&.spec&.dependencies.present?
117
+ end
118
+
119
+ def description?
120
+ true
121
+ end
122
+
123
+ def views?
124
+ views.present?
125
+ end
126
+
127
+ def depends_on
128
+ mod&.spec&.dependencies
129
+ &.map { |dep| dep.name }
130
+ &.select { |name| name.starts_with? "card-mod" }
131
+ &.map { |name| "mod_#{name[8..-1]}" }
132
+ end
133
+
134
+ def tasks
135
+ basket[:tasks].select { |_k, v| v[:mod] == modname.to_sym }
136
+ end
137
+
138
+ def settings
139
+ return unless admin_config
140
+
141
+ admin_config["settings"]&.map do |setting|
142
+ setting.to_sym
143
+ end
144
+ end
145
+
146
+ def configurations
147
+ return unless admin_config
148
+
149
+ admin_config["configurations"]
150
+ end
151
+
152
+ def cardtypes
153
+ return unless admin_config
154
+
155
+ config_codenames_grouped_by_title admin_config_section(:cardtypes)
156
+ end
157
+
158
+ def views
159
+ return unless admin_config
160
+
161
+ admin_config["views"]
162
+ end
163
+
164
+ def description
165
+ default = if mod&.spec&.description.present?
166
+ mod&.spec&.description
167
+ else
168
+ mod&.spec&.summary
169
+ end
170
+ t("#{modname}_mod_description", default: default)
171
+ end
172
+
173
+ def modname
174
+ codename.to_s.gsub(/^mod_/, "")
175
+ end
176
+
177
+ def mod
178
+ @mod ||= Cardio::Mod.fetch modname
179
+ end
180
+
181
+ def admin_config_section category
182
+ admin_config_objects_by_category[category.to_s]
183
+ end
184
+
185
+ def admin_config
186
+ @admin_config ||= load_admin_config
187
+ end
188
+
189
+ def admin_config_objects
190
+ @admin_config_objects ||= admin_config.map do |category, values|
191
+ if values.is_a? Hash
192
+ values.map do |subcategory, subvalues|
193
+ create_admin_items mod, category, subcategory, subvalues
194
+ end.flatten
195
+ else
196
+ create_admin_items mod, category, nil, values
197
+ end
198
+ end.flatten
199
+ end
200
+
201
+ def admin_config_objects_by_category
202
+ @admin_config_objects_by_category ||=
203
+ admin_config_objects.group_by(&:category)
204
+ end
205
+
206
+ def load_admin_config
207
+ return unless admin_config_exists?
208
+ admin_config = YAML.load_file admin_config_path
209
+ return {} unless admin_config # blank manifest
210
+ # validate_manifest manifest
211
+ admin_config
212
+ end
213
+
214
+ def admin_config_exists?
215
+ @admin_config_exists = !admin_config_path.nil? if @admin_config_exists.nil?
216
+ @admin_config_exists
217
+ end
218
+
219
+ def admin_config_path
220
+ @admin_config_path ||= mod&.subpath "config", "admin.yml"
221
+ end
222
+
223
+ private
224
+
225
+ def read_admin_yml
226
+ YAML.safe_load File.read(filename), [Symbol] if File.exist? filename
227
+ end
@@ -0,0 +1,34 @@
1
+ RSpec.describe Card::Set::All::Admin do
2
+ describe "all_admin_configs_of_category" do
3
+ it "finds settings" do
4
+ expect(Card[:all].all_admin_configs_of_category("settings").map(&:codename))
5
+ .to include("create")
6
+ end
7
+
8
+ it "create setting has the correct role" do
9
+ create_config = Card[:all].all_admin_configs_of_category("settings")
10
+ .find { |x| x.codename == "create" }
11
+ expect(create_config.roles).to eq([:shark])
12
+ end
13
+
14
+ it "finds views" do
15
+ views = Card[:all].all_admin_configs_of_category("views").map(&:codename)
16
+ expect(views).to include("name", "link", "content")
17
+ end
18
+ end
19
+
20
+ specify "admin_config_by_role" do
21
+ roles = Card[:all].all_admin_configs_grouped_by(:roles)
22
+ expect(roles[:anyone_signed_in].map(&:codename))
23
+ .to contain_exactly("mod", "user", "setting", "json", "number", "plain_text",
24
+ "toggle", "phrase", "uri",
25
+ "list", "pointer", "email_template", "file", "image",
26
+ "link_list",
27
+ "local_script_folder_group", "local_script_manifest_group",
28
+ "local_style_folder_group",
29
+ "nest_list", "remote_manifest_group", "role",
30
+ "local_style_manifest_group", "bootswatch_skin",
31
+ "date", "notification_template", "session", "basic",
32
+ "search_type", "signup")
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ RSpec.describe Card::Set::Type::Mod do
2
+ check_views_for_errors
3
+
4
+ specify "cardtypes" do
5
+ expect(Card[:mod_format].cardtypes)
6
+ .to eq [["Text", %i[basic plain_text html phrase]],
7
+ ["Scripting", [:json]], ["Data", %i[number toggle uri]]]
8
+ end
9
+
10
+ specify "admin_config_objects" do
11
+ config_objects = Card[:mod_core].admin_config_objects
12
+ expect(config_objects.size).to eq 7
13
+
14
+ expect(config_objects[0].category).to eq "cardtypes"
15
+ expect(config_objects[0].subcategory).to eq "admin"
16
+ expect(config_objects[0].title).to eq "Admin"
17
+ expect(config_objects[0].codename).to eq "mod"
18
+ end
19
+
20
+ describe "core view" do
21
+ it "renders Cardtypes and Tasks section for core mod" do
22
+ view = render_card :core, :mod_core
23
+ expect(view).to have_tag :h3, "Cardtypes"
24
+ %w[Mod User Setting Cardtype].each do |cardtype_name|
25
+ expect(view).to have_tag :span, class: "card-title", content: cardtype_name
26
+ end
27
+ expect(view).to have_tag :h3, "Tasks"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ RSpec.shared_examples "mod admin config" do |codename, settings, configs, cardtypes|
2
+ card = codename.card
3
+ specify "admin.yml of #{codename} loaded correctly" do
4
+ aggregate_failures do
5
+ expect(card.settings).to eq settings
6
+ expect(card.configurations).to eq configs
7
+ expect(card.cardtypes).to eq cardtypes
8
+ end
9
+ end
10
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: card
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.106.0
4
+ version: 1.107.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan McCutchen
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-11-18 00:00:00.000000000 Z
13
+ date: 2024-06-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: cardname
@@ -18,14 +18,14 @@ dependencies:
18
18
  requirements:
19
19
  - - '='
20
20
  - !ruby/object:Gem::Version
21
- version: 0.16.0
21
+ version: 0.17.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - '='
27
27
  - !ruby/object:Gem::Version
28
- version: 0.16.0
28
+ version: 0.17.0
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: rake
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -616,6 +616,8 @@ files:
616
616
  - lib/cardio/mod/modfile_loader.rb
617
617
  - lib/cardio/mod/module_template.rb
618
618
  - lib/cardio/mod/sow.rb
619
+ - lib/cardio/mod/sow/card_source.rb
620
+ - lib/cardio/mod/sow/yaml_dump.rb
619
621
  - lib/cardio/railtie.rb
620
622
  - lib/cardio/record.rb
621
623
  - lib/cardio/script_loader.rb
@@ -673,6 +675,7 @@ files:
673
675
  - lib/generators/set/set_generator.rb
674
676
  - lib/generators/set/templates/set_spec_template.erb
675
677
  - lib/generators/set/templates/set_template.erb
678
+ - mod/core/config/admin.yml
676
679
  - mod/core/config/locales/de.yml
677
680
  - mod/core/config/locales/en.yml
678
681
  - mod/core/data/fixtures/real/card_actions.yml
@@ -744,10 +747,12 @@ files:
744
747
  - mod/core/data/transform/20190909104250_add_cardtype_input_types.rb
745
748
  - mod/core/data/transform/20191115160748_history_cleanup.rb
746
749
  - mod/core/data/transform/20230502094848_repair_all_references.rb
750
+ - mod/core/lib/admin_item.rb
747
751
  - mod/core/lib/tasks/card.rake
748
752
  - mod/core/lib/tasks/card/migrate.rake
749
753
  - mod/core/lib/tasks/card/mod.rake
750
754
  - mod/core/lib/tasks/card/seed.rake
755
+ - mod/core/set/abstract/task_table.rb
751
756
  - mod/core/set/all/admin.rb
752
757
  - mod/core/set/all/assign_attributes.rb
753
758
  - mod/core/set/all/autoname.rb
@@ -767,9 +772,11 @@ files:
767
772
  - mod/core/set/self/admin.rb
768
773
  - mod/core/set/self/admin/warning_alert.haml
769
774
  - mod/core/set/self/autoname.rb
775
+ - mod/core/set/self/mod.rb
770
776
  - mod/core/set/self/trash.rb
771
777
  - mod/core/set/self/version.rb
772
778
  - mod/core/set/type/cardtype.rb
779
+ - mod/core/set/type/mod.rb
773
780
  - mod/core/set_pattern/01_all.rb
774
781
  - mod/core/set_pattern/02_all_plus.rb
775
782
  - mod/core/set_pattern/03_type.rb
@@ -779,6 +786,7 @@ files:
779
786
  - mod/core/set_pattern/07_right.rb
780
787
  - mod/core/set_pattern/08_type_plus_right.rb
781
788
  - mod/core/set_pattern/09_self.rb
789
+ - mod/core/spec/set/all/admin_spec.rb
782
790
  - mod/core/spec/set/all/assign_attributes_spec.rb
783
791
  - mod/core/spec/set/all/autoname_spec.rb
784
792
  - mod/core/spec/set/all/codename_spec.rb
@@ -791,6 +799,8 @@ files:
791
799
  - mod/core/spec/set/self/trash_spec.rb
792
800
  - mod/core/spec/set/self/version_spec.rb
793
801
  - mod/core/spec/set/type/cardtype_spec.rb
802
+ - mod/core/spec/set/type/mod_spec.rb
803
+ - mod/core/spec/shared_examples/mod_admin_config.rb
794
804
  homepage: https://decko.org
795
805
  licenses:
796
806
  - GPL-3.0
@@ -808,14 +818,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
808
818
  requirements:
809
819
  - - ">="
810
820
  - !ruby/object:Gem::Version
811
- version: '2.5'
821
+ version: '3.0'
812
822
  required_rubygems_version: !ruby/object:Gem::Requirement
813
823
  requirements:
814
824
  - - ">="
815
825
  - !ruby/object:Gem::Version
816
826
  version: '0'
817
827
  requirements: []
818
- rubygems_version: 3.4.10
828
+ rubygems_version: 3.5.10
819
829
  signing_key:
820
830
  specification_version: 4
821
831
  summary: a simple engine for emergent data structures