card 1.106.0 → 1.107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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