easy_creds 1.0.1

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +51 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +165 -0
  5. data/exe/easy_creds +6 -0
  6. data/lib/easy_creds/actions/edit.rb +156 -0
  7. data/lib/easy_creds/actions/editor_edit.rb +70 -0
  8. data/lib/easy_creds/actions/init.rb +93 -0
  9. data/lib/easy_creds/actions/local/delete.rb +44 -0
  10. data/lib/easy_creds/actions/local/edit.rb +13 -0
  11. data/lib/easy_creds/actions/local/editor_edit.rb +13 -0
  12. data/lib/easy_creds/actions/local/init.rb +32 -0
  13. data/lib/easy_creds/actions/local/status.rb +49 -0
  14. data/lib/easy_creds/actions/local/sync_key.rb +63 -0
  15. data/lib/easy_creds/actions/local.rb +31 -0
  16. data/lib/easy_creds/actions/pull.rb +142 -0
  17. data/lib/easy_creds/actions/push.rb +149 -0
  18. data/lib/easy_creds/actions/status.rb +82 -0
  19. data/lib/easy_creds/cli.rb +147 -0
  20. data/lib/easy_creds/config.rb +52 -0
  21. data/lib/easy_creds/configuration.rb +91 -0
  22. data/lib/easy_creds/credentials_io.rb +128 -0
  23. data/lib/easy_creds/differ.rb +41 -0
  24. data/lib/easy_creds/doctor.rb +100 -0
  25. data/lib/easy_creds/env_picker.rb +24 -0
  26. data/lib/easy_creds/flatten.rb +25 -0
  27. data/lib/easy_creds/generators.rb +113 -0
  28. data/lib/easy_creds/init_state.rb +65 -0
  29. data/lib/easy_creds/installer.rb +55 -0
  30. data/lib/easy_creds/onboarding/gem_setup.rb +98 -0
  31. data/lib/easy_creds/onboarding/project_wizard.rb +106 -0
  32. data/lib/easy_creds/onboarding/register_prompt.rb +47 -0
  33. data/lib/easy_creds/onboarding/runner.rb +17 -0
  34. data/lib/easy_creds/onboarding/template_picker.rb +36 -0
  35. data/lib/easy_creds/overlay.rb +71 -0
  36. data/lib/easy_creds/project.rb +74 -0
  37. data/lib/easy_creds/projects/registry.rb +135 -0
  38. data/lib/easy_creds/provider.rb +30 -0
  39. data/lib/easy_creds/providers/base.rb +25 -0
  40. data/lib/easy_creds/providers/one_password.rb +187 -0
  41. data/lib/easy_creds/railtie.rb +10 -0
  42. data/lib/easy_creds/templates/files/default-beastmode.yml +33 -0
  43. data/lib/easy_creds/templates/files/microservice-minimal.yml +7 -0
  44. data/lib/easy_creds/templates/files/rails-api.yml +20 -0
  45. data/lib/easy_creds/templates/files/rails-fullstack.yml +37 -0
  46. data/lib/easy_creds/templates/registry.rb +62 -0
  47. data/lib/easy_creds/templates/renderer.rb +37 -0
  48. data/lib/easy_creds/theme.rb +129 -0
  49. data/lib/easy_creds/thor_cli.rb +299 -0
  50. data/lib/easy_creds/vault_picker.rb +56 -0
  51. data/lib/easy_creds/version.rb +5 -0
  52. data/lib/easy_creds/views/diff_table.rb +36 -0
  53. data/lib/easy_creds/views/header.rb +40 -0
  54. data/lib/easy_creds/views/init_dispatch.rb +132 -0
  55. data/lib/easy_creds/views/init_tree.rb +250 -0
  56. data/lib/easy_creds/views/local_menu.rb +38 -0
  57. data/lib/easy_creds/views/menu.rb +55 -0
  58. data/lib/easy_creds/views/project_picker.rb +56 -0
  59. data/lib/easy_creds/views/settings_menu.rb +108 -0
  60. data/lib/easy_creds/views/templates_menu.rb +142 -0
  61. data/lib/easy_creds/views/welcome_screen.rb +131 -0
  62. data/lib/easy_creds.rb +54 -0
  63. data/lib/tasks/easy_creds.rake +23 -0
  64. metadata +292 -0
@@ -0,0 +1,299 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'yaml'
5
+ require 'shellwords'
6
+ require 'tty-screen'
7
+
8
+ module EasyCreds
9
+ class ThorCLI < Thor
10
+ def self.exit_on_failure? = true
11
+
12
+ # No-args invocation → styled welcome instead of plain Thor help.
13
+ def self.start(given_args = ARGV, config = {})
14
+ given_args.empty? ? new.send(:welcome) : super
15
+ end
16
+
17
+ desc 'sync [ENV]', 'Interactive TUI to sync credentials with 1Password'
18
+ method_option :vault, type: :string, desc: 'Override vault name'
19
+ method_option :'no-register', type: :boolean, default: false,
20
+ desc: 'Skip the auto-register prompt for unregistered projects'
21
+ def sync(env = nil)
22
+ EasyCreds.load_global_config!
23
+ EasyCreds.ensure_default_template!
24
+ check_first_run!
25
+
26
+ EasyCreds.configure { |c| c.default_vault = options[:vault] } if options[:vault]
27
+
28
+ registry = Projects::Registry.load
29
+ project = EasyCreds::Project.detect(Dir.pwd)
30
+
31
+ if (known = registry.find_by_path(project.root))
32
+ registry.touch(known)
33
+ return EasyCreds::CLI.start(project: project, default_env: env)
34
+ end
35
+
36
+ if looks_like_project?(project) && !skip_register?
37
+ result = Onboarding::RegisterPrompt.run(
38
+ prompt: build_prompt, project: project, registry: registry
39
+ )
40
+ return if result == :quit
41
+
42
+ return EasyCreds::CLI.start(project: project, default_env: env)
43
+ end
44
+
45
+ outside_picker_loop(registry)
46
+ end
47
+
48
+ desc 'init', 'First-time gem setup (global config + vault selection)'
49
+ method_option :'global-dir', type: :string, desc: 'Path to global easy_creds dir'
50
+ def init
51
+ EasyCreds.configure { |c| c.global_dir = options[:'global-dir'] } if options[:'global-dir']
52
+ prompt = build_prompt
53
+ EasyCreds::Onboarding::GemSetup.run(prompt: prompt)
54
+ end
55
+
56
+ desc 'onboard', 'Set up example.yml for the current project'
57
+ method_option :template, type: :string, desc: 'Template name to use'
58
+ method_option :force, type: :boolean, default: false, desc: 'Overwrite existing example.yml'
59
+ def onboard
60
+ EasyCreds.load_global_config!
61
+ project = EasyCreds::Project.detect(Dir.pwd)
62
+ EasyCreds::Onboarding::Runner.start(project: project, template: options[:template], force: options[:force])
63
+ end
64
+
65
+ desc 'doctor', 'Check gem health (op CLI, auth, project detection)'
66
+ method_option :verbose, type: :boolean, default: false
67
+ def doctor
68
+ EasyCreds.load_global_config!
69
+ ok = EasyCreds::Doctor.new(verbose: options[:verbose]).run
70
+ exit 1 unless ok
71
+ end
72
+
73
+ desc 'install', 'Install overlay initializer into current Rails project'
74
+ def install
75
+ project = EasyCreds::Project.detect(Dir.pwd)
76
+ unless project.is_a?(EasyCreds::Project::Rails)
77
+ puts EasyCreds::Theme.warn('Not a Rails project — nothing to install.')
78
+ exit 3
79
+ end
80
+ EasyCreds::Installer.new(project.root).run
81
+ end
82
+
83
+ desc 'projects', 'Browse and manage registered projects'
84
+ def projects
85
+ EasyCreds.load_global_config!
86
+ registry = Projects::Registry.load
87
+ outside_picker_loop(registry)
88
+ end
89
+
90
+ desc 'templates', 'Browse and manage global template library'
91
+ def templates
92
+ EasyCreds.load_global_config!
93
+ Views::TemplatesMenu.run(build_prompt)
94
+ end
95
+
96
+ desc 'settings', 'Edit global easy_creds settings'
97
+ def settings
98
+ EasyCreds.load_global_config!
99
+ Views::SettingsMenu.run(build_prompt)
100
+ end
101
+
102
+ desc 'tree', 'Print a tree of all available commands'
103
+ def tree
104
+ render_command_tree
105
+ end
106
+
107
+ desc 'version', 'Print easy_creds version'
108
+ def version
109
+ beastmode_version
110
+ end
111
+
112
+ private
113
+
114
+ COMMAND_GROUPS = {
115
+ 'Setup' => %w[init onboard install doctor],
116
+ 'Sync' => %w[sync],
117
+ 'Manage' => %w[projects templates settings],
118
+ 'Utility' => %w[tree version help]
119
+ }.freeze
120
+ private_constant :COMMAND_GROUPS
121
+
122
+ def welcome
123
+ EasyCreds.load_global_config!
124
+ width = [TTY::Screen.width, 100].min
125
+ inner = width - 4
126
+
127
+ # ASCII art banner in rounded box
128
+ art_lines = Theme::BANNER_ART.map { |l| " #{Theme.accent(l)}" }
129
+ vault = EasyCreds.config.default_vault
130
+ art_lines << ''
131
+ art_lines << " #{Theme.dim("v#{EasyCreds::VERSION} · credentials ↔ 1Password sync · vault: #{vault || '(not set)'}")}"
132
+ rows = art_lines.map { |l| "#{Theme.box_side} #{Theme.rpad(l, inner)} #{Theme.box_side}" }
133
+ puts ''
134
+ puts Theme.box_top(width)
135
+ puts rows
136
+ puts Theme.box_bottom(width)
137
+ puts ''
138
+ puts " #{Theme.tprompt_line}"
139
+ puts ''
140
+
141
+ registry = Projects::Registry.load
142
+ if registry.projects.any?
143
+ puts " #{Theme.bold('Recent projects:')}"
144
+ puts ''
145
+ registry.projects
146
+ .sort_by { |pr| -(pr.last_seen_at&.to_i || 0) }
147
+ .first(3)
148
+ .each do |pr|
149
+ seen = Views::ProjectPicker.relative_time(pr.last_seen_at)
150
+ kind = pr.rails? ? Theme.ok('[rails]') : Theme.accent('[std]')
151
+ puts " #{Theme.accent('›')} #{Theme.bold(pr.name.ljust(22))} #{kind} #{Theme.dim(pr.display_path)} #{Theme.dim(seen)}"
152
+ end
153
+ puts ''
154
+ elsif first_run?
155
+ puts " #{Theme.warn('→ First time?')} Run #{Theme.accent(Theme.bold('easy_creds init'))} to get started."
156
+ puts ''
157
+ end
158
+
159
+ puts " #{Theme.bold('Commands:')}"
160
+ puts ''
161
+ cmds = self.class.commands
162
+ max_w = cmds.keys.map(&:length).max || 0
163
+ cmds.sort.each do |name, cmd|
164
+ puts " easy_creds #{Theme.accent(Theme.bold(name.ljust(max_w + 1)))} #{Theme.dim('# ' + cmd.description)}"
165
+ end
166
+ puts ''
167
+ puts " #{Theme.dim('Run')} #{Theme.bold('easy_creds help [COMMAND]')} #{Theme.dim('for details.')}"
168
+ puts ''
169
+ end
170
+
171
+ def outside_picker_loop(registry)
172
+ prompt = build_prompt
173
+ loop do
174
+ system('clear')
175
+ choice = Views::WelcomeScreen.run(registry)
176
+
177
+ case choice
178
+ when :quit then break
179
+ when :templates
180
+ system('clear')
181
+ Views::TemplatesMenu.run(prompt)
182
+ when :settings
183
+ system('clear')
184
+ Views::SettingsMenu.run(prompt)
185
+ when :init_here
186
+ system('clear')
187
+ init_here(prompt, registry) and break
188
+ when EasyCreds::Projects::Project
189
+ handle_project_pick(choice, registry)
190
+ break
191
+ end
192
+ end
193
+ end
194
+
195
+ def handle_project_pick(project, registry)
196
+ unless project.exists_on_disk?
197
+ prompt = build_prompt
198
+ if prompt.yes?("Path #{project.path} no longer exists. Remove from registry?", default: true)
199
+ registry.remove(project)
200
+ end
201
+ return
202
+ end
203
+
204
+ registry.touch(project)
205
+ Dir.chdir(project.path) do
206
+ detected = EasyCreds::Project.detect(project.path)
207
+ EasyCreds::CLI.start(project: detected, default_env: nil)
208
+ end
209
+ end
210
+
211
+ def init_here(prompt, registry)
212
+ project = EasyCreds::Project.detect(Dir.pwd)
213
+ result = Onboarding::RegisterPrompt.run(prompt: prompt, project: project, registry: registry)
214
+ return false if result == :quit
215
+
216
+ EasyCreds::CLI.start(project: project, default_env: nil)
217
+ true
218
+ end
219
+
220
+ def render_command_tree
221
+ p = Theme.pastel
222
+ puts ''
223
+ puts " #{Theme.header('easy_creds')} #{Theme.dim('v' + EasyCreds::VERSION)}"
224
+ puts " #{Theme.dim('─' * 50)}"
225
+ puts ''
226
+
227
+ rendered = []
228
+ COMMAND_GROUPS.each do |group_name, cmd_names|
229
+ available = cmd_names.select { |n| self.class.commands.key?(n) }
230
+ next if available.empty?
231
+
232
+ puts " #{Theme.bold(group_name)}"
233
+ available.each_with_index do |name, i|
234
+ cmd = self.class.commands[name]
235
+ connector = i == available.size - 1 ? '└─' : '├─'
236
+ puts " #{Theme.dim(connector)} #{Theme.accent(name.ljust(12))} #{Theme.dim(cmd.description)}"
237
+ rendered << name
238
+ end
239
+ puts ''
240
+ end
241
+
242
+ remaining = self.class.commands.keys.sort - rendered
243
+ if remaining.any?
244
+ puts " #{Theme.bold('Other')}"
245
+ remaining.each_with_index do |name, i|
246
+ cmd = self.class.commands[name]
247
+ connector = i == remaining.size - 1 ? '└─' : '├─'
248
+ puts " #{Theme.dim(connector)} #{Theme.accent(name.ljust(12))} #{Theme.dim(cmd.description)}"
249
+ end
250
+ puts ''
251
+ end
252
+ end
253
+
254
+ def beastmode_version
255
+ width = 48
256
+ inner = width - 4
257
+
258
+ lines = [
259
+ " #{Theme.header("#{Theme::ICONS[:lock]} easy_creds")} #{Theme.dim("v#{EasyCreds::VERSION}")}",
260
+ '',
261
+ " #{Theme.dim("Ruby #{RUBY_VERSION} · #{RUBY_PLATFORM}")}"
262
+ ]
263
+
264
+ rows = lines.map { |l| "#{Theme.box_side} #{Theme.rpad(l, inner)} #{Theme.box_side}" }
265
+ box = ([Theme.box_top(width)] + rows + [Theme.box_bottom(width)]).join("\n")
266
+
267
+ puts ''
268
+ puts box.split("\n").map { |l| " #{l}" }.join("\n")
269
+ puts ''
270
+ puts " #{Theme.accent(Theme.bold('B E A S T M O D E'))} #{Theme.dim('activated ⚡')}"
271
+ puts ''
272
+ end
273
+
274
+ def check_first_run!
275
+ return unless first_run?
276
+
277
+ puts ''
278
+ puts " #{Theme.warn("#{Theme::ICONS[:warn]} First run detected!")} #{Theme.dim('Setting up easy_creds...')}"
279
+ puts ''
280
+ EasyCreds::Onboarding::GemSetup.run(prompt: build_prompt)
281
+ end
282
+
283
+ def first_run?
284
+ !File.exist?(File.join(EasyCreds.config.global_dir, 'config.yml'))
285
+ end
286
+
287
+ def looks_like_project?(project)
288
+ project.rails? || File.directory?(File.join(project.root.to_s, '.git'))
289
+ end
290
+
291
+ def skip_register?
292
+ options[:'no-register'] || ENV['EASY_CREDS_NO_REGISTER']
293
+ end
294
+
295
+ def build_prompt
296
+ TTY::Prompt.new(interrupt: :exit, symbols: { selector: '›' })
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyCreds
4
+ module VaultPicker
5
+ CREATE_SENTINEL = :'__create_new__'
6
+ DEFAULT_NEW_NAME = 'easy_creds'
7
+
8
+ # Shows existing 1Password vaults + a "create new" option.
9
+ # Returns the chosen vault name (string), or nil if the user aborts.
10
+ def self.pick(prompt)
11
+ vaults = fetch_vaults
12
+
13
+ choices = vaults.map { |v| { name: " #{v}", value: v } }
14
+
15
+ if choices.any?
16
+ choices << { name: Theme.pastel.dim(' ─────────────────────────────────'), value: nil, disabled: '' }
17
+ end
18
+
19
+ choices << { name: " #{Theme.pastel.bold('✚ create new vault…')}", value: CREATE_SENTINEL }
20
+
21
+ selection = prompt.select('Select default vault:', choices, cycle: true, filter: false,
22
+ per_page: [choices.size, 20].min)
23
+
24
+ return nil if selection.nil?
25
+ return selection unless selection == CREATE_SENTINEL
26
+
27
+ create_vault(prompt)
28
+ end
29
+
30
+ def self.fetch_vaults
31
+ raw = `op vault list --format=json 2>/dev/null`
32
+ return [] unless $CHILD_STATUS.success?
33
+
34
+ JSON.parse(raw).map { |v| v['name'] }
35
+ rescue JSON::ParserError
36
+ []
37
+ end
38
+
39
+ def self.create_vault(prompt)
40
+ name = prompt.ask('New vault name:', default: DEFAULT_NEW_NAME) do |q|
41
+ q.validate(/\S+/, 'Vault name cannot be blank.')
42
+ end
43
+ return nil unless name
44
+
45
+ name = name.strip
46
+ output = `op vault create #{Shellwords.escape(name)} 2>&1`
47
+ if $CHILD_STATUS.success?
48
+ Theme.success("Created vault '#{name}'")
49
+ name
50
+ else
51
+ Theme.failure("Could not create vault: #{output.strip}")
52
+ nil
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyCreds
4
+ VERSION = '1.0.1'
5
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-table'
4
+
5
+ module EasyCreds
6
+ module Views
7
+ module DiffTable
8
+ HEADER = %w[change key where].freeze
9
+
10
+ CHANGE_LABELS = { added: 'remote-only', removed: 'local-only', modified: 'differs' }.freeze
11
+
12
+ def self.render(changes)
13
+ if changes.empty?
14
+ puts Theme.ok(' (no changes — already in sync)')
15
+ return
16
+ end
17
+
18
+ puts build_table(changes)
19
+ puts Theme.dim(" #{changes.size} change(s) — values not shown")
20
+ end
21
+
22
+ private_class_method def self.build_table(changes)
23
+ rows = changes.map { |c| change_row(c) }
24
+ TTY::Table.new(HEADER, rows).render(:unicode, padding: [0, 1])
25
+ end
26
+
27
+ private_class_method def self.change_row(change)
28
+ [
29
+ "#{Theme.change_color(change.kind)} #{CHANGE_LABELS[change.kind]}",
30
+ Theme.key_tag(change.key),
31
+ Theme.dim(change.side.to_s)
32
+ ]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyCreds
4
+ module Views
5
+ module Header
6
+ def self.render(ctx)
7
+ system('clear') || system('cls')
8
+ puts Theme.banner(ctx.config.vault, EnvPicker.available(ctx.root))
9
+ puts ''
10
+ puts " #{Theme.tprompt_line}"
11
+ puts ''
12
+ puts sign_in_line(ctx.op&.account_email)
13
+ puts env_vault_line(ctx)
14
+ puts project_line(ctx.root)
15
+ puts ''
16
+ end
17
+
18
+ private_class_method def self.sign_in_line(email)
19
+ if email
20
+ " #{Theme.ok(Theme::ICONS[:ok])} signed in to 1Password #{Theme.dim("(#{email})")}"
21
+ else
22
+ " #{Theme.error(Theme::ICONS[:error])} not signed in to 1Password"
23
+ end
24
+ end
25
+
26
+ private_class_method def self.env_vault_line(ctx)
27
+ env_label = ctx.env ? Theme.env_tag(ctx.env) : Theme.dim('none selected')
28
+ vault_label = ctx.config.vault ? Theme.bold(ctx.config.vault) : Theme.dim('(not set)')
29
+ " #{Theme.dim('env')} #{env_label} #{Theme.dim('vault')} #{vault_label}"
30
+ end
31
+
32
+ private_class_method def self.project_line(root)
33
+ project = Project.detect(root)
34
+ kind = project.rails? ? Theme.ok('Rails') : Theme.accent('Standalone')
35
+ path = Theme.dim(root.to_s.gsub(Dir.home, '~'))
36
+ " #{Theme.dim('project')} #{kind} #{path}"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyCreds
4
+ module Views
5
+ # Pure keystroke → state mutation dispatcher.
6
+ # Returns nil (normal redraw), or a signal symbol:
7
+ # :manual_entry — caller must prompt for hidden input
8
+ # :save — caller should write the file and exit
9
+ # :quit — caller should confirm and exit
10
+ # :toggle_help — caller flips the help overlay flag
11
+ module InitDispatch
12
+ UP = ["\e[A", 'k'].freeze
13
+ DOWN = ["\e[B", 'j'].freeze
14
+ PAGE_UP = ["\e[5~", "\e[I"].freeze
15
+ PAGE_DOWN = ["\e[6~", "\e[G"].freeze
16
+ HOME_KEY = ["\e[H", "\e[1~", "\eOH"].freeze
17
+ END_KEY = ["\e[F", "\e[4~", "\eOF"].freeze
18
+
19
+ # Keys that return a signal directly without touching state.
20
+ SIGNAL_KEYS = {
21
+ 'm' => :manual_entry,
22
+ 's' => :save, 'S' => :save,
23
+ 'q' => :quit, "\e" => :quit,
24
+ '?' => :toggle_help, 'h' => :toggle_help
25
+ }.freeze
26
+
27
+ # Returns nil (redraw) or a signal symbol (:manual_entry, :save, :quit, :toggle_help).
28
+ def self.handle(key, state, root: nil)
29
+ nav = navigate(key, state)
30
+ return nav unless nav == :not_navigation
31
+
32
+ SIGNAL_KEYS.fetch(key) { mutate(key, state, root: root) }
33
+ end
34
+
35
+ private_class_method def self.navigate(key, state)
36
+ case key
37
+ when *UP then state.move_up
38
+ when *DOWN then state.move_down
39
+ when *PAGE_UP then state.move_up(10)
40
+ when *PAGE_DOWN then state.move_down(10)
41
+ when *HOME_KEY then state.move_first
42
+ when *END_KEY then state.move_last
43
+ else return :not_navigation
44
+ end
45
+ nil
46
+ end
47
+
48
+ private_class_method def self.mutate(key, state, root: nil)
49
+ case key
50
+ when 'g' then apply_generate(state, root: root)
51
+ when 'G' then apply_generate_alt(state)
52
+ when 'b' then apply_ar_batch(state, root: root)
53
+ when 't' then apply_template(state)
54
+ when 'o' then apply_from_op(state)
55
+ when 'c' then state.clear_entry(state.current)
56
+ when 'T' then template_all(state)
57
+ when 'R' then generate_suggested(state, root: root)
58
+ when 'O' then from_op_all(state)
59
+ end
60
+ nil
61
+ end
62
+
63
+ private_class_method def self.apply_generate(state, root: nil)
64
+ entry = state.current
65
+ if entry.hint == :ar_encryption
66
+ apply_ar_batch(state, root: root)
67
+ else
68
+ val = Generators.default_for(entry.hint, root: root)
69
+ state.set_entry(entry, value: val.to_s, source: :generate) if val
70
+ end
71
+ end
72
+
73
+ private_class_method def self.apply_generate_alt(state)
74
+ entry = state.current
75
+ return if entry.hint == :ar_encryption
76
+
77
+ val = Generators.alternative_for(entry.hint)
78
+ state.set_entry(entry, value: val.to_s, source: :generate_alt) if val
79
+ end
80
+
81
+ private_class_method def self.apply_ar_batch(state, root: nil)
82
+ keys = Generators.ar_encryption_keys(root)
83
+ return unless keys
84
+
85
+ keys.each do |key, val|
86
+ entry = state.entries.find { |e| e.key == key }
87
+ state.set_entry(entry, value: val.to_s, source: :ar_batch) if entry
88
+ end
89
+ end
90
+
91
+ private_class_method def self.apply_template(state)
92
+ entry = state.current
93
+ return unless entry.placeholder
94
+
95
+ state.set_entry(entry, value: entry.placeholder.to_s, source: :template)
96
+ end
97
+
98
+ private_class_method def self.apply_from_op(state)
99
+ entry = state.current
100
+ val = entry.op_value
101
+ state.set_entry(entry, value: val.to_s, source: :from_op) if val
102
+ end
103
+
104
+ private_class_method def self.template_all(state)
105
+ state.entries.each do |entry|
106
+ next if entry.set? || entry.placeholder.nil?
107
+
108
+ state.set_entry(entry, value: entry.placeholder.to_s, source: :template)
109
+ end
110
+ end
111
+
112
+ private_class_method def self.generate_suggested(state, root: nil)
113
+ apply_ar_batch(state, root: root) if state.entries.any? { |e| e.hint == :ar_encryption && !e.set? }
114
+
115
+ state.entries.each do |entry|
116
+ next if entry.set? || entry.hint.nil? || entry.hint == :ar_encryption
117
+
118
+ val = Generators.bulk_for(entry.hint)
119
+ state.set_entry(entry, value: val.to_s, source: :generate) if val
120
+ end
121
+ end
122
+
123
+ private_class_method def self.from_op_all(state)
124
+ state.entries.each do |entry|
125
+ next if entry.set? || entry.op_value.nil?
126
+
127
+ state.set_entry(entry, value: entry.op_value.to_s, source: :from_op)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end