command_proposal 1.0.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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +77 -0
  4. data/Rakefile +18 -0
  5. data/app/assets/config/command_proposal_manifest.js +1 -0
  6. data/app/assets/javascripts/command_proposal/_codemirror.js +9814 -0
  7. data/app/assets/javascripts/command_proposal/_helpers.js +9 -0
  8. data/app/assets/javascripts/command_proposal/codemirror-addon-searchcursor.js +296 -0
  9. data/app/assets/javascripts/command_proposal/codemirror-keymap-sublime.js +720 -0
  10. data/app/assets/javascripts/command_proposal/codemirror-mode-ruby.js +303 -0
  11. data/app/assets/javascripts/command_proposal/console.js +195 -0
  12. data/app/assets/javascripts/command_proposal/feed.js +51 -0
  13. data/app/assets/javascripts/command_proposal/terminal.js +40 -0
  14. data/app/assets/javascripts/command_proposal.js +1 -0
  15. data/app/assets/stylesheets/command_proposal/_variables.scss +0 -0
  16. data/app/assets/stylesheets/command_proposal/codemirror-rubyblue.scss +27 -0
  17. data/app/assets/stylesheets/command_proposal/codemirror.scss +367 -0
  18. data/app/assets/stylesheets/command_proposal/command_proposal.scss +1 -0
  19. data/app/assets/stylesheets/command_proposal/components.scss +31 -0
  20. data/app/assets/stylesheets/command_proposal/containers.scss +4 -0
  21. data/app/assets/stylesheets/command_proposal/icons.scss +12 -0
  22. data/app/assets/stylesheets/command_proposal/tables.scss +76 -0
  23. data/app/assets/stylesheets/command_proposal/terminal.scss +72 -0
  24. data/app/assets/stylesheets/command_proposal.scss +5 -0
  25. data/app/controllers/command_proposal/engine_controller.rb +6 -0
  26. data/app/controllers/command_proposal/iterations_controller.rb +83 -0
  27. data/app/controllers/command_proposal/runner_controller.rb +86 -0
  28. data/app/controllers/command_proposal/tasks_controller.rb +97 -0
  29. data/app/helpers/command_proposal/application_helper.rb +58 -0
  30. data/app/helpers/command_proposal/icons_helper.rb +15 -0
  31. data/app/helpers/command_proposal/params_helper.rb +63 -0
  32. data/app/helpers/command_proposal/permissions_helper.rb +42 -0
  33. data/app/jobs/command_proposal/application_job.rb +4 -0
  34. data/app/jobs/command_proposal/command_runner_job.rb +11 -0
  35. data/app/models/command_proposal/comment.rb +14 -0
  36. data/app/models/command_proposal/iteration.rb +78 -0
  37. data/app/models/command_proposal/service/external_belong.rb +48 -0
  38. data/app/models/command_proposal/service/json_wrapper.rb +18 -0
  39. data/app/models/command_proposal/service/proposal_presenter.rb +39 -0
  40. data/app/models/command_proposal/task.rb +106 -0
  41. data/app/views/command_proposal/tasks/_console_show.html.erb +44 -0
  42. data/app/views/command_proposal/tasks/_function_show.html.erb +54 -0
  43. data/app/views/command_proposal/tasks/_lines.html.erb +8 -0
  44. data/app/views/command_proposal/tasks/_module_show.html.erb +33 -0
  45. data/app/views/command_proposal/tasks/_past_iterations_list.html.erb +20 -0
  46. data/app/views/command_proposal/tasks/_task_detail_table.html.erb +31 -0
  47. data/app/views/command_proposal/tasks/_task_show.html.erb +55 -0
  48. data/app/views/command_proposal/tasks/error.html.erb +4 -0
  49. data/app/views/command_proposal/tasks/form.html.erb +64 -0
  50. data/app/views/command_proposal/tasks/index.html.erb +44 -0
  51. data/app/views/command_proposal/tasks/show.html.erb +10 -0
  52. data/config/routes.rb +11 -0
  53. data/lib/command_proposal/configuration.rb +41 -0
  54. data/lib/command_proposal/engine.rb +6 -0
  55. data/lib/command_proposal/services/command_interpreter.rb +108 -0
  56. data/lib/command_proposal/services/runner.rb +157 -0
  57. data/lib/command_proposal/version.rb +3 -0
  58. data/lib/command_proposal.rb +27 -0
  59. data/lib/generators/command_proposal/install/install_generator.rb +28 -0
  60. data/lib/generators/command_proposal/install/templates/initializer.rb +47 -0
  61. data/lib/generators/command_proposal/install/templates/install_command_proposal.rb +40 -0
  62. data/lib/tasks/command_proposal_tasks.rake +4 -0
  63. metadata +167 -0
@@ -0,0 +1,108 @@
1
+ # require_relative "command_proposal/permissions_helper"
2
+
3
+ module CommandProposal
4
+ module Services
5
+ class CommandInterpreter
6
+ class Error < StandardError; end
7
+ include ::CommandProposal::PermissionsHelper
8
+
9
+ def self.command(iteration, command, user, params={})
10
+ new(iteration, command, user, params).command
11
+ end
12
+
13
+ def initialize(iteration, command, user, params={})
14
+ @iteration = iteration
15
+ @task = iteration.task
16
+ @command = command.to_s.to_sym
17
+ @user = user
18
+ @params = params
19
+ command_user(@user) if @user.present?
20
+ end
21
+
22
+ def command
23
+ case @command
24
+ when :request then command_request
25
+ when :approve then command_approve
26
+ when :run then command_run
27
+ when :cancel then command_cancel
28
+ when :cancel then command_cancel
29
+ when :close then command_close
30
+ end
31
+
32
+ @iteration
33
+ end
34
+
35
+ def command_request
36
+ check_can_command?
37
+ if @iteration.complete? && (@task.task? || @task.function?)
38
+ previous_iteration = @iteration
39
+ # Creates a new iteration with the same code so we don't lose results
40
+ @task.user = @user # Sets the task user to assign as the requester
41
+ @task.update(code: @iteration.code)
42
+ @iteration = @task.current_iteration
43
+
44
+ if @task.function? && previous_iteration.approved_at?
45
+ @params.merge!(previous_iteration.attributes.slice("approved_at", "approver_id"))
46
+ @params.merge!(status: :approved)
47
+ return # Don't trigger the callback
48
+ end
49
+ end
50
+
51
+ proposal = ::CommandProposal::Service::ProposalPresenter.new(@iteration)
52
+ ::CommandProposal.configuration.proposal_callback&.call(proposal)
53
+ end
54
+
55
+ def command_approve
56
+ error!("Command is not ready for approval.") unless @iteration.pending?
57
+ check_can_command? && check_can_approve?
58
+
59
+ @iteration.update(status: :approved, approver: @user, approved_at: Time.current)
60
+ end
61
+
62
+ def command_run
63
+ check_can_command?
64
+
65
+ # Rollback the create/update if anything fails
66
+ ActiveRecord::Base.transaction do
67
+ command_request if @task.function? && @iteration.approved_at? && @iteration.complete?
68
+ @iteration.update(@params)
69
+
70
+ error!("Cannot run without approval.") unless has_approval?(@task)
71
+ end
72
+
73
+ ::CommandProposal::CommandRunnerJob.perform_later(@iteration.id)
74
+ end
75
+
76
+ def command_cancel
77
+ check_can_command?
78
+ return if @iteration.complete?
79
+
80
+ @iteration.update(status: :cancelling)
81
+ end
82
+
83
+ def command_close
84
+ check_can_command?
85
+ return unless @iteration.task.console?
86
+
87
+ @task.first_iteration.update(status: :success, completed_at: Time.current)
88
+ ::CommandProposal.sessions.delete("task:#{@task.id}")
89
+ end
90
+
91
+ def check_can_command?
92
+ return true if can_command?
93
+
94
+ error!("Sorry, you do not have permission to do this.")
95
+ end
96
+
97
+ def check_can_approve?
98
+ return true if can_approve?(@iteration)
99
+
100
+ error!("You cannot approve your own command.")
101
+ end
102
+
103
+ def error!(msg)
104
+ raise ::CommandProposal::Services::CommandInterpreter::Error.new(msg)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,157 @@
1
+ module CommandProposal
2
+ module Services
3
+ class Runner
4
+ attr_accessor :session
5
+ # Add expiration and things like that...
6
+
7
+ def initialize
8
+ @session = session
9
+ end
10
+
11
+ def execute(iteration, inline=false)
12
+ @iteration = iteration
13
+ @inline = inline
14
+ prepare
15
+
16
+ run
17
+
18
+ complete
19
+ @iteration = nil
20
+ end
21
+
22
+ def quick_run(iteration)
23
+ raise CommandProposal::Error, ":#{iteration.task.friendly_id} does not have approval to run." unless iteration.approved?
24
+
25
+ @session.eval(iteration.code)
26
+ end
27
+
28
+ private
29
+
30
+ def session
31
+ binding
32
+ end
33
+
34
+ def prepare
35
+ raise CommandProposal::Error, "Cannot run task without approval" unless @iteration.approved?
36
+ raise CommandProposal::Error, "Modules cannot be run independently" if @iteration.task.module?
37
+
38
+ @iteration.task.update(last_executed_at: Time.current)
39
+ @iteration.update(started_at: Time.current, status: :started)
40
+ end
41
+
42
+ def run
43
+ begin
44
+ @session.eval("#{bring_function};params = #{@iteration.args || {}}.with_indifferent_access")
45
+ rescue Exception => e # rubocop:disable Lint/RescueException - Yes, rescue full Exception so that we can catch typos in evals as well
46
+ return @iteration.result = results_from_exception(e)
47
+ end
48
+
49
+ stored_stdout = $stdout
50
+ $stdout = StringIO.new
51
+ result = nil # Init var for scope
52
+
53
+ running_thread = Thread.new do
54
+ begin
55
+ # Run bring functions in here so we can capture any string outputs
56
+ # OR! Run the full runner and instead of saving to an iteration, return the string for prepending here
57
+ result = @session.eval("_ = (#{@iteration.code})").inspect # rubocop:disable Security/Eval - Eval is scary, but in this case it's exactly what we need.
58
+ rescue Exception => e # rubocop:disable Lint/RescueException - Yes, rescue full Exception so that we can catch typos in evals as well
59
+ @iteration.status = :failed
60
+
61
+ result = results_from_exception(e)
62
+ end
63
+ end
64
+
65
+ while running_thread.status.present?
66
+ @iteration.reload
67
+
68
+ if $stdout.try(:string) != @iteration.result
69
+ @iteration.update(result: $stdout.try(:string).dup)
70
+ end
71
+
72
+ running_thread.exit if @iteration.cancelling?
73
+
74
+ sleep 1
75
+ end
76
+
77
+ output = $stdout.try(:string)
78
+ output = nil if output == ""
79
+ # Not using presence because we want to maintain other empty objects such as [] and {}
80
+
81
+ $stdout = stored_stdout
82
+ @iteration.result = [output, "#{result || 'nil'}"].compact.join("\n")
83
+ end
84
+
85
+ def bring_function
86
+ "def bring(*func_names); func_names.each { |f| self.quick_run(::CommandProposal::Task.module.find_by!(friendly_id: f).current_iteration) }; end"
87
+ end
88
+
89
+ def complete
90
+ @iteration.completed_at = Time.current
91
+ if @iteration.cancelling? || @iteration.cancelled?
92
+ @iteration.result += "\n\n~~~~~ CANCELLED ~~~~~"
93
+ @iteration.status = :cancelled
94
+ elsif @iteration.failed?
95
+ # No-op
96
+ else
97
+ @iteration.status = :success
98
+ end
99
+ @iteration.save!
100
+
101
+ return if @iteration.task.console? # Don't notify for every console entry
102
+ proposal = ::CommandProposal::Service::ProposalPresenter.new(@iteration)
103
+ if @iteration.success?
104
+ ::CommandProposal.configuration.success_callback&.call(proposal)
105
+ else
106
+ ::CommandProposal.configuration.failed_callback&.call(proposal)
107
+ end
108
+ end
109
+
110
+ def results_from_exception(exc)
111
+ klass = exc.class
112
+ msg = exc.try(:message) || exc.try(:body) || exc.to_s
113
+ # Remove proposal context
114
+ msg.gsub!(/ for \#\<CommandProposal.*/, "")
115
+ msg.gsub!(/(::)?CommandProposal::Services::Runner(::)?/, "")
116
+ # Remove gem lines
117
+ msg.gsub!(/\/?((\w|(\\ ))*\/)*command_proposal\/services(\/(\w|(\\ ))*)*\.\w+\:\d+\: /, "")
118
+ info = gather_exception_info(exc)
119
+
120
+ ["#{klass}: #{msg}", info.presence].compact.join("\n")
121
+ end
122
+
123
+ def gather_exception_info(exception)
124
+ error_info = []
125
+ backtrace = full_trace_from_exception(exception)
126
+
127
+ eval_trace = backtrace.select { |row| row.include?("(eval)") }.presence || []
128
+ eval_trace = eval_trace.map do |row|
129
+ eval_row_number = row[/\(eval\)\:\d+/].to_s[7..-1]
130
+ next if eval_row_number.blank?
131
+
132
+ error_line = @iteration.code.split("\n")[eval_row_number.to_i - 1]
133
+ "#{eval_row_number}: #{error_line}" if error_line.present?
134
+ end.compact
135
+ error_info += ["\n>> Command Trace"] + eval_trace if eval_trace.any?
136
+
137
+ app_trace = backtrace.select { |row|
138
+ row.include?("/app/") && !row.match?(/command_proposal\/(lib|app)/)
139
+ }.presence || []
140
+ error_info += ["\n>> App Trace"] + app_trace if app_trace.any?
141
+
142
+ error_info.uniq.join("\n")
143
+ end
144
+
145
+ def full_trace_from_exception(exception)
146
+ trace = exception.try(:backtrace).presence
147
+ return trace if trace.present?
148
+
149
+ trace = @session.send(:caller).dup
150
+ return trace if trace.present?
151
+
152
+ trace = caller.dup
153
+ trace
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,3 @@
1
+ module CommandProposal
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,27 @@
1
+ require "command_proposal/configuration"
2
+ require "command_proposal/version"
3
+ require "command_proposal/engine"
4
+ require "command_proposal/services/runner"
5
+
6
+ module CommandProposal
7
+ class Error < StandardError; end
8
+ def self.sessions
9
+ @sessions ||= {}
10
+ end
11
+
12
+ def self.clear_sessions
13
+ @sessions = {}
14
+ end
15
+
16
+ def self.configuration
17
+ @configuration ||= ::CommandProposal::Configuration.new
18
+ end
19
+
20
+ def self.reset
21
+ @configuration = ::CommandProposal::Configuration.new
22
+ end
23
+
24
+ def self.configure
25
+ yield(configuration)
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ require "rails/generators/migration"
2
+
3
+ module CommandProposal
4
+ module Generators
5
+ class InstallGenerator < ::Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+ source_root File.expand_path("../templates", __FILE__)
8
+ desc "add the migrations"
9
+
10
+ def self.next_migration_number(path)
11
+ unless @prev_migration_nr
12
+ @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
13
+ else
14
+ @prev_migration_nr += 1
15
+ end
16
+ @prev_migration_nr.to_s
17
+ end
18
+
19
+ def copy_migrations
20
+ migration_template "install_command_proposal.rb", "db/migrate/install_command_proposal.rb"
21
+ end
22
+
23
+ def create_initializer_file
24
+ copy_file "initializer.rb", "config/initializers/command_proposal.rb"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ ::CommandProposal.configure do |config|
2
+ # Change if your base user class has a different model name
3
+ config.user_class_name = "User"
4
+
5
+ # Helper method used by controllers to identify the currently logged in account.
6
+ config.controller_var = :current_user
7
+
8
+ # Scope for your user class that determines users who are permitted to interact with commands
9
+ # It is highly recommended to make this very exclusive, as any users in this scope will be able
10
+ # to interact with your database directly.
11
+ config.role_scope = :admin
12
+
13
+ # Method called to display a user's name
14
+ config.user_name = :name
15
+
16
+ # Callbacks for proposal state changes
17
+ # `proposal` is the current proposal
18
+ # Methods available:
19
+ # `proposal.url`
20
+ # `proposal.type`
21
+ # `proposal.name`
22
+ # `proposal.description`
23
+ # `proposal.args`
24
+ # `proposal.code`
25
+ # `proposal.result`
26
+ # `proposal.status`
27
+ # `proposal.requester`
28
+ # `proposal.approver`
29
+ # `proposal.approved_at`
30
+ # `proposal.started_at`
31
+ # `proposal.completed_at`
32
+ # `proposal.stopped_at`
33
+ # `proposal.duration`
34
+
35
+ # Called when a command is proposed for review
36
+ config.proposal_callback = Proc.new { |proposal|
37
+ # Slack.notify("#{proposal.requester} has proposed #{proposal.name}.\n<Click Here|#{proposal.url}> to view this proposal and approve.")
38
+ }
39
+ # Called when a command runs and completes successfully
40
+ config.success_callback = Proc.new { |iteration|
41
+ # Slack.notify("The task #{proposal.name} has completed in #{proposal.duration}s.\n<Click Here|#{proposal.url}> to view the results.")
42
+ }
43
+ # Called when a command runs but fails to complete
44
+ config.failed_callback = Proc.new { |iteration|
45
+ # Slack.notify("The task #{proposal.name} has failed!\n<Click Here|#{proposal.url}> to see what went wrong.")
46
+ }
47
+ end
@@ -0,0 +1,40 @@
1
+ class InstallCommandProposal < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :command_proposal_tasks do |t|
4
+ # has_many :iterations
5
+ t.text :name
6
+ t.text :friendly_id
7
+ t.text :description
8
+ t.integer :session_type, default: 0 # [task, console, function]
9
+ t.datetime :last_executed_at
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ create_table :command_proposal_iterations do |t|
15
+ # has_many :comments
16
+ t.belongs_to :task
17
+ t.text :args
18
+ t.text :code
19
+ t.text :result
20
+ t.integer :status, default: 0 # [created approved started failed success]
21
+ t.belongs_to :requester
22
+ t.belongs_to :approver
23
+ t.datetime :approved_at
24
+ t.datetime :started_at
25
+ t.datetime :completed_at
26
+ t.datetime :stopped_at
27
+
28
+ t.timestamps
29
+ end
30
+
31
+ create_table :command_proposal_comments do |t|
32
+ t.belongs_to :iteration
33
+ t.integer :line_number
34
+ t.belongs_to :author
35
+ t.text :body
36
+
37
+ t.timestamps
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :command_proposal do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: command_proposal
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Rocco Nicholls
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-08-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: font-awesome-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activejob
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Rather than creating rake tasks, which have to go through CI/CD, then
70
+ some deploy process, eventually make it to production, only to be run once, and
71
+ then never get deleted and litter your code base- this gem allows you to create
72
+ command proposals via a UI that can be immediately reviewed by your peers and then
73
+ run- keeping a history of what happened, when, and what the results were.
74
+ email:
75
+ - rocco11nicholls@gmail.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - MIT-LICENSE
81
+ - README.md
82
+ - Rakefile
83
+ - app/assets/config/command_proposal_manifest.js
84
+ - app/assets/javascripts/command_proposal.js
85
+ - app/assets/javascripts/command_proposal/_codemirror.js
86
+ - app/assets/javascripts/command_proposal/_helpers.js
87
+ - app/assets/javascripts/command_proposal/codemirror-addon-searchcursor.js
88
+ - app/assets/javascripts/command_proposal/codemirror-keymap-sublime.js
89
+ - app/assets/javascripts/command_proposal/codemirror-mode-ruby.js
90
+ - app/assets/javascripts/command_proposal/console.js
91
+ - app/assets/javascripts/command_proposal/feed.js
92
+ - app/assets/javascripts/command_proposal/terminal.js
93
+ - app/assets/stylesheets/command_proposal.scss
94
+ - app/assets/stylesheets/command_proposal/_variables.scss
95
+ - app/assets/stylesheets/command_proposal/codemirror-rubyblue.scss
96
+ - app/assets/stylesheets/command_proposal/codemirror.scss
97
+ - app/assets/stylesheets/command_proposal/command_proposal.scss
98
+ - app/assets/stylesheets/command_proposal/components.scss
99
+ - app/assets/stylesheets/command_proposal/containers.scss
100
+ - app/assets/stylesheets/command_proposal/icons.scss
101
+ - app/assets/stylesheets/command_proposal/tables.scss
102
+ - app/assets/stylesheets/command_proposal/terminal.scss
103
+ - app/controllers/command_proposal/engine_controller.rb
104
+ - app/controllers/command_proposal/iterations_controller.rb
105
+ - app/controllers/command_proposal/runner_controller.rb
106
+ - app/controllers/command_proposal/tasks_controller.rb
107
+ - app/helpers/command_proposal/application_helper.rb
108
+ - app/helpers/command_proposal/icons_helper.rb
109
+ - app/helpers/command_proposal/params_helper.rb
110
+ - app/helpers/command_proposal/permissions_helper.rb
111
+ - app/jobs/command_proposal/application_job.rb
112
+ - app/jobs/command_proposal/command_runner_job.rb
113
+ - app/models/command_proposal/comment.rb
114
+ - app/models/command_proposal/iteration.rb
115
+ - app/models/command_proposal/service/external_belong.rb
116
+ - app/models/command_proposal/service/json_wrapper.rb
117
+ - app/models/command_proposal/service/proposal_presenter.rb
118
+ - app/models/command_proposal/task.rb
119
+ - app/views/command_proposal/tasks/_console_show.html.erb
120
+ - app/views/command_proposal/tasks/_function_show.html.erb
121
+ - app/views/command_proposal/tasks/_lines.html.erb
122
+ - app/views/command_proposal/tasks/_module_show.html.erb
123
+ - app/views/command_proposal/tasks/_past_iterations_list.html.erb
124
+ - app/views/command_proposal/tasks/_task_detail_table.html.erb
125
+ - app/views/command_proposal/tasks/_task_show.html.erb
126
+ - app/views/command_proposal/tasks/error.html.erb
127
+ - app/views/command_proposal/tasks/form.html.erb
128
+ - app/views/command_proposal/tasks/index.html.erb
129
+ - app/views/command_proposal/tasks/show.html.erb
130
+ - config/routes.rb
131
+ - lib/command_proposal.rb
132
+ - lib/command_proposal/configuration.rb
133
+ - lib/command_proposal/engine.rb
134
+ - lib/command_proposal/services/command_interpreter.rb
135
+ - lib/command_proposal/services/runner.rb
136
+ - lib/command_proposal/version.rb
137
+ - lib/generators/command_proposal/install/install_generator.rb
138
+ - lib/generators/command_proposal/install/templates/initializer.rb
139
+ - lib/generators/command_proposal/install/templates/install_command_proposal.rb
140
+ - lib/tasks/command_proposal_tasks.rake
141
+ homepage: https://github.com/Rockster160/command_proposal
142
+ licenses:
143
+ - MIT
144
+ metadata:
145
+ homepage_uri: https://github.com/Rockster160/command_proposal
146
+ source_code_uri: https://github.com/Rockster160/command_proposal
147
+ changelog_uri: https://github.com/Rockster160/command_proposal/blob/master/README.md
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubygems_version: 3.1.4
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: Gives the ability to run approved commands through a UI in your browser
167
+ test_files: []