ronin-core 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.github/workflows/ruby.yml +41 -0
  4. data/.gitignore +12 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +160 -0
  7. data/.ruby-version +1 -0
  8. data/.yardopts +1 -0
  9. data/COPYING.txt +165 -0
  10. data/ChangeLog.md +11 -0
  11. data/Gemfile +30 -0
  12. data/README.md +299 -0
  13. data/Rakefile +34 -0
  14. data/examples/ruby_shell.rb +11 -0
  15. data/gemspec.yml +28 -0
  16. data/lib/ronin/core/class_registry.rb +246 -0
  17. data/lib/ronin/core/cli/command.rb +87 -0
  18. data/lib/ronin/core/cli/command_shell/command.rb +110 -0
  19. data/lib/ronin/core/cli/command_shell.rb +345 -0
  20. data/lib/ronin/core/cli/generator/options/author.rb +106 -0
  21. data/lib/ronin/core/cli/generator/options/description.rb +54 -0
  22. data/lib/ronin/core/cli/generator/options/reference.rb +60 -0
  23. data/lib/ronin/core/cli/generator/options/summary.rb +54 -0
  24. data/lib/ronin/core/cli/generator.rb +238 -0
  25. data/lib/ronin/core/cli/logging.rb +59 -0
  26. data/lib/ronin/core/cli/options/param.rb +68 -0
  27. data/lib/ronin/core/cli/options/values/arches.rb +45 -0
  28. data/lib/ronin/core/cli/options/values/oses.rb +32 -0
  29. data/lib/ronin/core/cli/printing/arch.rb +71 -0
  30. data/lib/ronin/core/cli/printing/metadata.rb +113 -0
  31. data/lib/ronin/core/cli/printing/os.rb +54 -0
  32. data/lib/ronin/core/cli/printing/params.rb +69 -0
  33. data/lib/ronin/core/cli/ruby_shell.rb +131 -0
  34. data/lib/ronin/core/cli/shell.rb +186 -0
  35. data/lib/ronin/core/git.rb +73 -0
  36. data/lib/ronin/core/home.rb +86 -0
  37. data/lib/ronin/core/metadata/authors/author.rb +241 -0
  38. data/lib/ronin/core/metadata/authors.rb +120 -0
  39. data/lib/ronin/core/metadata/description.rb +100 -0
  40. data/lib/ronin/core/metadata/id.rb +88 -0
  41. data/lib/ronin/core/metadata/references.rb +87 -0
  42. data/lib/ronin/core/metadata/summary.rb +78 -0
  43. data/lib/ronin/core/metadata/version.rb +74 -0
  44. data/lib/ronin/core/params/exceptions.rb +38 -0
  45. data/lib/ronin/core/params/mixin.rb +317 -0
  46. data/lib/ronin/core/params/param.rb +137 -0
  47. data/lib/ronin/core/params/types/boolean.rb +64 -0
  48. data/lib/ronin/core/params/types/enum.rb +107 -0
  49. data/lib/ronin/core/params/types/float.rb +68 -0
  50. data/lib/ronin/core/params/types/integer.rb +100 -0
  51. data/lib/ronin/core/params/types/numeric.rb +106 -0
  52. data/lib/ronin/core/params/types/regexp.rb +67 -0
  53. data/lib/ronin/core/params/types/string.rb +118 -0
  54. data/lib/ronin/core/params/types/type.rb +54 -0
  55. data/lib/ronin/core/params/types/uri.rb +72 -0
  56. data/lib/ronin/core/params/types.rb +62 -0
  57. data/lib/ronin/core/params.rb +19 -0
  58. data/lib/ronin/core/version.rb +24 -0
  59. data/ronin-core.gemspec +59 -0
  60. data/spec/class_registry_spec.rb +224 -0
  61. data/spec/cli/command_shell/command_spec.rb +113 -0
  62. data/spec/cli/command_shell_spec.rb +1114 -0
  63. data/spec/cli/command_spec.rb +16 -0
  64. data/spec/cli/fixtures/irb_command +8 -0
  65. data/spec/cli/fixtures/template/dir/file1.txt +1 -0
  66. data/spec/cli/fixtures/template/dir/file2.txt +1 -0
  67. data/spec/cli/fixtures/template/file.erb +1 -0
  68. data/spec/cli/fixtures/template/file.txt +1 -0
  69. data/spec/cli/generator/options/author_spec.rb +121 -0
  70. data/spec/cli/generator/options/description_spec.rb +45 -0
  71. data/spec/cli/generator/options/reference_spec.rb +53 -0
  72. data/spec/cli/generator/options/summary_spec.rb +45 -0
  73. data/spec/cli/generator_spec.rb +244 -0
  74. data/spec/cli/logging_spec.rb +95 -0
  75. data/spec/cli/options/param_spec.rb +67 -0
  76. data/spec/cli/options/values/arches_spec.rb +62 -0
  77. data/spec/cli/printing/arch_spec.rb +130 -0
  78. data/spec/cli/printing/metadata_spec.rb +211 -0
  79. data/spec/cli/printing/os_spec.rb +64 -0
  80. data/spec/cli/printing/params_spec.rb +63 -0
  81. data/spec/cli/ruby_shell.rb +99 -0
  82. data/spec/cli/shell_spec.rb +211 -0
  83. data/spec/fixtures/example_class_registry/base_class.rb +9 -0
  84. data/spec/fixtures/example_class_registry/classes/loaded_class.rb +9 -0
  85. data/spec/fixtures/example_class_registry/classes/name_mismatch.rb +9 -0
  86. data/spec/fixtures/example_class_registry/classes/no_module.rb +4 -0
  87. data/spec/fixtures/example_class_registry.rb +8 -0
  88. data/spec/git_spec.rb +58 -0
  89. data/spec/home_spec.rb +64 -0
  90. data/spec/metadata/authors/author_spec.rb +335 -0
  91. data/spec/metadata/authors_spec.rb +126 -0
  92. data/spec/metadata/description_spec.rb +74 -0
  93. data/spec/metadata/id_spec.rb +92 -0
  94. data/spec/metadata/references_spec.rb +100 -0
  95. data/spec/metadata/summary_spec.rb +74 -0
  96. data/spec/metadata/version_spec.rb +72 -0
  97. data/spec/params/mixin_spec.rb +484 -0
  98. data/spec/params/param_spec.rb +164 -0
  99. data/spec/params/types/boolean_spec.rb +56 -0
  100. data/spec/params/types/enum_spec.rb +94 -0
  101. data/spec/params/types/float_spec.rb +107 -0
  102. data/spec/params/types/integer_spec.rb +155 -0
  103. data/spec/params/types/numeric_spec.rb +138 -0
  104. data/spec/params/types/regexp_spec.rb +64 -0
  105. data/spec/params/types/string_spec.rb +174 -0
  106. data/spec/params/types/type_spec.rb +14 -0
  107. data/spec/params/types/uri_spec.rb +62 -0
  108. data/spec/spec_helper.rb +11 -0
  109. metadata +252 -0
data/README.md ADDED
@@ -0,0 +1,299 @@
1
+ # ronin-core
2
+
3
+ [![CI](https://github.com/ronin-rb/ronin-core/actions/workflows/ruby.yml/badge.svg)](https://github.com/ronin-rb/ronin-core/actions/workflows/ruby.yml)
4
+ [![Code Climate](https://codeclimate.com/github/ronin-rb/ronin-core.svg)](https://codeclimate.com/github/ronin-rb/ronin-core)
5
+
6
+ * [Website](https://ronin-rb.dev/)
7
+ * [Source](https://github.com/ronin-rb/ronin-core)
8
+ * [Issues](https://github.com/ronin-rb/ronin-core/issues)
9
+ * [Documentation](https://ronin-rb.dev/docs/ronin-core/frames)
10
+ * [Discord](https://discord.gg/6WAb3PsVX9) |
11
+ [Twitter](https://twitter.com/ronin_rb) |
12
+ [Mastodon](https://infosec.exchange/@ronin_rb)
13
+
14
+ ## Description
15
+
16
+ ronin-core is a core library providing common functionality for all ronin
17
+ libraries.
18
+
19
+ ronin-core is part of the [ronin-rb] project, a toolkit for security research
20
+ and development.
21
+
22
+ ## Features
23
+
24
+ * Provides access to the XDG directories (`~/.config/`, `~/.cache/`,
25
+ `~/.local/share`).
26
+ * Allows querying `~/.gitconfig` for common git settings.
27
+ * Provides a common `Command` base class for all ronin libraries.
28
+ * Provides a `Shell` and `CommandShell` base classes for writing interactive
29
+ shells.
30
+ * Provides a `Params` API for adding user configurable parameters to classes.
31
+ * Has 85% documentation coverage.
32
+ * Has 99% test coverage.
33
+
34
+ ## Requirements
35
+
36
+ * [Ruby] >= 3.0.0
37
+ * [reline] ~> 0.1
38
+ * [command_kit] ~> 0.4
39
+ * [irb] ~> 1.0
40
+
41
+ ## Install
42
+
43
+ ### Gemfile
44
+
45
+ ```ruby
46
+ gem 'ronin-core', '~> 0.1'
47
+ ```
48
+
49
+ ### gemspec
50
+
51
+ ```ruby
52
+ gem.add_depedency 'ronin-core', '~> 0.1'
53
+ ```
54
+
55
+ ### [gemspec.yml]
56
+
57
+ ```yaml
58
+ dependencies:
59
+ ronin-core: ~> 0.1
60
+ ```
61
+
62
+ ## Examples
63
+
64
+ ### Params
65
+
66
+ ```ruby
67
+ class BaseClass
68
+
69
+ include Ronin::Core::Params::Mixin
70
+
71
+ end
72
+ ```
73
+
74
+ ```ruby
75
+ class MyModule < BaseClass
76
+
77
+ param :str, desc: 'A basic string param'
78
+
79
+ param :feature_flag, Boolean, desc: 'A boolean param'
80
+
81
+ param :enum, Enum[:one, :two, :three],
82
+ desc: 'An enum param'
83
+
84
+ param :num1, Integer, desc: 'An integer param'
85
+
86
+ param :num2, Integer, default: 42,
87
+ desc: 'A param with a default value'
88
+
89
+ param :num3, Integer, default: ->{ rand(42) },
90
+ desc: 'A param with a dynamic default value'
91
+
92
+ param :float, Float, 'Floating point param'
93
+
94
+ param :url, URI, desc: 'URL param'
95
+
96
+ param :pattern, Regexp, desc: 'Regular Expression param'
97
+
98
+ end
99
+
100
+ mod = MyModule.new(params: {num1: 1, enum: :two})
101
+ mod.params
102
+ # => {:num2=>42, :num3=>25, :num1=>1, :enum=>:two}
103
+ ```
104
+
105
+ ### CLI
106
+
107
+ Define a main command for `ronin-foo`:
108
+
109
+ ```ruby
110
+ # lib/ronin/foo/cli.rb
111
+ require 'command_kit/commands'
112
+ require 'command_kit/commands/auto_load'
113
+
114
+ module Ronin
115
+ module Foo
116
+ class CLI
117
+
118
+ include CommandKit::Commands
119
+ include CommandKit::Commands::AutoLoad.new(
120
+ dir: "#{__dir__}/cli/commands",
121
+ namespace: "#{self}::Commands"
122
+ )
123
+
124
+ command_name 'ronin-foo'
125
+
126
+ command_aliases['ls'] = 'list'
127
+ # ...
128
+
129
+ end
130
+ end
131
+ end
132
+ ```
133
+
134
+ Add a `bin/ronin-foo` file (don't forget to `chmod +x` it) that invokes the
135
+ main command:
136
+
137
+ ```ruby
138
+ #!/usr/bin/env ruby
139
+
140
+ root = File.expand_path(File.join(__dir__,'..'))
141
+ if File.file?(File.join(root,'Gemfile.lock'))
142
+ Dir.chdir(root) do
143
+ begin
144
+ require 'bundler/setup'
145
+ rescue LoadError => e
146
+ warn e.message
147
+ warn "Run `gem install bundler` to install Bundler"
148
+ exit -1
149
+ end
150
+ end
151
+ end
152
+
153
+ require 'ronin/foo/cli'
154
+ Ronin::Foo::CLI.start
155
+ ```
156
+
157
+ Define a common command class for all `ronin-foo`'s commands:
158
+
159
+ ```ruby
160
+ # lib/ronin/foo/cli/command.rb
161
+ require 'ronin/core/cli/command'
162
+
163
+ module Ronin
164
+ module Foo
165
+ class CLI
166
+ class Command < Core::CLI::Command
167
+
168
+ man_dir File.join(__dir__,'..','..','..','..','man')
169
+
170
+ end
171
+ end
172
+ end
173
+ end
174
+ ```
175
+
176
+ Define a `list` sub-command under the `ronin-foo` main command:
177
+
178
+ ```ruby
179
+ # lib/ronin/foo/cli/commands/list.rb
180
+ require 'ronin/foo/cli/command'
181
+
182
+ module Ronin
183
+ module Foo
184
+ class CLI
185
+ module Commands
186
+ class List < Command
187
+
188
+ usage '[options] [NAME]'
189
+
190
+ argument :name, required: false,
191
+ desc: 'Optional name to list'
192
+
193
+ description 'Lists all things'
194
+
195
+ man_page 'ronin-foo-list.1'
196
+
197
+ def run(name=nil)
198
+ # ...
199
+ end
200
+
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ ```
207
+
208
+ Test it out:
209
+
210
+ ```shell
211
+ $ ./bin/ronin-foo
212
+ Usage: ronin-foo [options] [COMMAND [ARGS...]]
213
+
214
+ Options:
215
+ -h, --help Print help information
216
+
217
+ Arguments:
218
+ [COMMAND] The command name to run
219
+ [ARGS ...] Additional arguments for the command
220
+
221
+ Commands:
222
+ help
223
+ list, ls
224
+ $ ./bin/ronin-foo ls
225
+ ```
226
+
227
+ ### CLI::CommandShell
228
+
229
+ Define a custom command shell:
230
+
231
+ ```ruby
232
+ class HTTPShell < Ronin::Core::CLI::CommandShell
233
+
234
+ shell_name 'http'
235
+
236
+ command :get, usage: 'PATH [HEADERS...]',
237
+ summary: 'Sends a GET request'
238
+ def get(path,*headers)
239
+ # ...
240
+ end
241
+
242
+ command :post, usage: 'PATH DATA [HEADERS...]',
243
+ summary: 'Sends a POST request'
244
+ def post(path,data,*headers)
245
+ # ...
246
+ end
247
+
248
+ end
249
+ ```
250
+
251
+ Then start it:
252
+
253
+ ```ruby
254
+ HTTPShell.start
255
+ ```
256
+
257
+ ```
258
+ http> get /foo
259
+ ...
260
+ http> post /foo var=bar
261
+ ...
262
+ ```
263
+
264
+ ## Development
265
+
266
+ 1. [Fork It!](https://github.com/ronin-rb/ronin-core/fork)
267
+ 2. Clone It!
268
+ 3. `cd ronin-core/`
269
+ 4. `bundle install`
270
+ 5. `git checkout -b my_feature`
271
+ 6. Code It!
272
+ 7. `bundle exec rake spec`
273
+ 8. `git push origin my_feature`
274
+
275
+ ## License
276
+
277
+ Copyright (c) 2021-2022 Hal Brodigan (postmodern.mod3@gmail.com)
278
+
279
+ ronin-core is free software: you can redistribute it and/or modify
280
+ it under the terms of the GNU Lesser General Public License as published
281
+ by the Free Software Foundation, either version 3 of the License, or
282
+ (at your option) any later version.
283
+
284
+ ronin-core is distributed in the hope that it will be useful,
285
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
286
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
287
+ GNU Lesser General Public License for more details.
288
+
289
+ You should have received a copy of the GNU Lesser General Public License
290
+ along with ronin-core. If not, see <https://www.gnu.org/licenses/>.
291
+
292
+ [ronin-rb]: https://ronin-rb.dev/
293
+
294
+ [Ruby]: https://www.ruby-lang.org
295
+ [command_kit]: https://github.com/postmodern/command_kit.rb#readme
296
+ [reline]: https://github.com/ruby/reline#readme
297
+ [irb]: https://github.com/ruby/irb#readme
298
+
299
+ [gemspec.yml]: https://github.com/postmodern/gemspec.yml#readme
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+
3
+ begin
4
+ require 'bundler'
5
+ rescue LoadError => e
6
+ warn e.message
7
+ warn "Run `gem install bundler` to install Bundler"
8
+ exit(-1)
9
+ end
10
+
11
+ begin
12
+ Bundler.setup(:development)
13
+ rescue Bundler::BundlerError => e
14
+ warn e.message
15
+ warn "Run `bundle install` to install missing gems"
16
+ exit e.status_code
17
+ end
18
+
19
+ require 'rake'
20
+
21
+ require 'rubygems/tasks'
22
+ Gem::Tasks.new(sign: {checksum: true, pgp: true})
23
+
24
+ require 'rspec/core/rake_task'
25
+ RSpec::Core::RakeTask.new
26
+ task :test => :spec
27
+ task :default => :spec
28
+
29
+ require 'yard'
30
+ YARD::Rake::YardocTask.new
31
+ task :docs => :yard
32
+
33
+ require 'kramdown/man/task'
34
+ Kramdown::Man::Task.new
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+
4
+ require 'ronin/core/cli/ruby_shell'
5
+
6
+ module Ronin
7
+ module Test
8
+ end
9
+ end
10
+
11
+ Ronin::Core::CLI::RubyShell.start(name: 'ronin-test', context: Ronin::Test)
data/gemspec.yml ADDED
@@ -0,0 +1,28 @@
1
+ name: ronin-core
2
+ summary: A core library for all ronin libraries.
3
+ description: |
4
+ ronin-core is a core library providing common functionality for all ronin
5
+ libraries.
6
+
7
+ license: LGPL-3.0
8
+ authors: Postmodern
9
+ email: postmodern.mod3@gmail.com
10
+ homepage: https://ronin-rb.dev/
11
+ has_yard: true
12
+
13
+ metadata:
14
+ documentation_uri: https://rubydoc.info/gems/ronin-core
15
+ source_code_uri: https://github.com/ronin-rb/ronin-core
16
+ bug_tracker_uri: https://github.com/ronin-rb/ronin-core/issues
17
+ changelog_uri: https://github.com/ronin-rb/ronin-core/blob/main/ChangeLog.md
18
+ rubygems_mfa_required: 'true'
19
+
20
+ required_ruby_version: ">= 3.0.0"
21
+
22
+ dependencies:
23
+ reline: ~> 0.1
24
+ command_kit: ~> 0.4
25
+ irb: ~> 1.0
26
+
27
+ development_dependencies:
28
+ bundler: ~> 2.0
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright (c) 2021-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
4
+ #
5
+ # ronin-core is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # ronin-core is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with ronin-core. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ module Ronin
20
+ module Core
21
+ #
22
+ # A mixin that adds a class registry to a library:
23
+ #
24
+ # ### Example
25
+ #
26
+ # `lib/ronin/exploits.rb`:
27
+ #
28
+ # require 'ronin/core/class_registry'
29
+ #
30
+ # module Ronin
31
+ # module Exploits
32
+ # include Ronin::Core::ClassRegistry
33
+ #
34
+ # class_dir "#{__dir__}/classes"
35
+ # end
36
+ # end
37
+ #
38
+ # `lib/ronin/exploits/exploit.rb`:
39
+ #
40
+ # module Ronin
41
+ # module Exploits
42
+ # class Exploit
43
+ #
44
+ # def self.register(name)
45
+ # Exploits.register(name,self)
46
+ # end
47
+ #
48
+ # end
49
+ # end
50
+ # end
51
+ #
52
+ # `lib/ronin/exploits/my_exploit.rb`:
53
+ #
54
+ # require 'ronin/exploits/exploit'
55
+ #
56
+ # module Ronin
57
+ # module Exploits
58
+ # class MyExploit < Exploit
59
+ #
60
+ # register 'my_exploit'
61
+ #
62
+ # end
63
+ # end
64
+ # end
65
+ #
66
+ # @api semipublic
67
+ #
68
+ module ClassRegistry
69
+ class ClassNotFound < RuntimeError
70
+ end
71
+
72
+ #
73
+ # Extends {ClassMethods}.
74
+ #
75
+ # @param [Module] namespace
76
+ # The module that is including {ClassRegistry}.
77
+ #
78
+ def self.included(namespace)
79
+ namespace.extend ClassMethods
80
+ end
81
+
82
+ module ClassMethods
83
+ #
84
+ # Gets or sets the class directory path.
85
+ #
86
+ # @param [String, nil] new_dir
87
+ # The new class directory path.
88
+ #
89
+ # @return [String]
90
+ # The class directory path.
91
+ #
92
+ # @raise [NotImplementedError]
93
+ # The `class_dir` method was not defined in the module.
94
+ #
95
+ # @example
96
+ # class_dir "#{__dir__}/classes"
97
+ #
98
+ def class_dir(new_dir=nil)
99
+ if new_dir
100
+ @class_dir = new_dir
101
+ else
102
+ @class_dir || raise(NotImplementedError,"#{self} did not define a class_dir")
103
+ end
104
+ end
105
+
106
+ #
107
+ # Lists all class files within {#class_dir}.
108
+ #
109
+ # @return [Array<String>]
110
+ # The list of class paths within {#class_dir}.
111
+ #
112
+ def list_files
113
+ paths = Dir.glob('{**/}*.rb', base: class_dir)
114
+ paths.each { |path| path.chomp!('.rb') }
115
+ return paths
116
+ end
117
+
118
+ #
119
+ # The class registry.
120
+ #
121
+ # @return [Hash{String => Class}]
122
+ # The mapping of class `id` and classes.
123
+ #
124
+ def registry
125
+ @registry ||= {}
126
+ end
127
+
128
+ #
129
+ # Registers a class with the registry.
130
+ #
131
+ # @param [String] id
132
+ # The class `id` to be registered.
133
+ #
134
+ # @param [Class] mod
135
+ # The class to be registered.
136
+ #
137
+ # @example
138
+ # Exploits.register('myexploit',MyExploit)
139
+ #
140
+ def register(id,mod)
141
+ registry[id] = mod
142
+ end
143
+
144
+ #
145
+ # Finds the path for the class `id`.
146
+ #
147
+ # @param [String] id
148
+ # The class `id`.
149
+ #
150
+ # @return [String, nil]
151
+ # The path for the module. If the module file does not exist in
152
+ # {#class_dir} then `nil` will be returned.
153
+ #
154
+ # @example
155
+ # Exploits.path_for('my_exploit')
156
+ # # => "/path/to/lib/ronin/exploits/classes/my_exploit.rb"
157
+ #
158
+ def path_for(id)
159
+ path = File.join(class_dir,"#{id}.rb")
160
+
161
+ if File.file?(path)
162
+ return path
163
+ end
164
+ end
165
+
166
+ #
167
+ # Loads a class from a file.
168
+ #
169
+ # @param [String] file
170
+ # The file to load.
171
+ #
172
+ # @return [Class]
173
+ # The loaded class.
174
+ #
175
+ # @raise [ClassNotFound]
176
+ # The file does not exist or the class `id` was not found within the
177
+ # file.
178
+ #
179
+ # @raise [LoadError]
180
+ # A load error curred while requiring the other files required by
181
+ # the class file.
182
+ #
183
+ def load_class_from_file(file)
184
+ file = File.expand_path(file)
185
+
186
+ unless File.file?(file)
187
+ raise(ClassNotFound,"no such file or directory: #{file.inspect}")
188
+ end
189
+
190
+ previous_entries = registry.keys
191
+ require(file)
192
+ new_entries = registry.keys - previous_entries
193
+
194
+ if new_entries.empty?
195
+ raise(ClassNotFound,"file did not register a class: #{file.inspect}")
196
+ end
197
+
198
+ return registry[new_entries.last]
199
+ end
200
+
201
+ #
202
+ # Loads a class from the {#class_dir}.
203
+ #
204
+ # @param [String] id
205
+ # The class `id` to load.
206
+ #
207
+ # @return [Class]
208
+ # The loaded class.
209
+ #
210
+ # @raise [ClassNotFound]
211
+ # The class file could not be found within {#class_dir}.or has
212
+ # a file/registered-name mismatch.
213
+ #
214
+ # @raise [LoadError]
215
+ # A load error curred while requiring the other files required by
216
+ # the class file.
217
+ #
218
+ def load_class(id)
219
+ # short-circuit if the module is already loaded
220
+ if (klass = registry[id])
221
+ return klass
222
+ end
223
+
224
+ unless (path = path_for(id))
225
+ raise(ClassNotFound,"could not find file for #{id.inspect}")
226
+ end
227
+
228
+ previous_entries = registry.keys
229
+ require(path)
230
+
231
+ unless (klass = registry[id])
232
+ new_entries = registry.keys - previous_entries
233
+
234
+ if new_entries.empty?
235
+ raise(ClassNotFound,"file did not register a class: #{path.inspect}")
236
+ else
237
+ raise(ClassNotFound,"file registered a class with a different id (#{new_entries.map(&:inspect).join(', ')}): #{path.inspect}")
238
+ end
239
+ end
240
+
241
+ return klass
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright (c) 2021-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
4
+ #
5
+ # ronin-core is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Lesser General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # ronin-core is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public License
16
+ # along with ronin-core. If not, see <https://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ require 'command_kit/command'
20
+ require 'command_kit/help/man'
21
+ require 'command_kit/bug_report'
22
+
23
+ module Ronin
24
+ module Core
25
+ module CLI
26
+ #
27
+ # Common base class for all CLI commands.
28
+ #
29
+ # ## Example
30
+ #
31
+ # Define a common CLI command base class for all commands in the
32
+ # `ronin-foo` library:
33
+ #
34
+ # # lib/ronin/foo/cli/command.rb
35
+ # require 'ronin/core/cli/command'
36
+ #
37
+ # module Ronin
38
+ # module Foo
39
+ # class CLI
40
+ # class Command < Core::CLI::Command
41
+ #
42
+ # man_dir File.join(__dir__,'..','..','..','..','man')
43
+ #
44
+ # end
45
+ # end
46
+ # end
47
+ # end
48
+ #
49
+ # Define a sub-command named `list` under the `ronin-foo` main command:
50
+ #
51
+ # # lib/ronin/foo/cli/commands/list.rb
52
+ # require 'ronin/foo/cli/command'
53
+ #
54
+ # module Ronin
55
+ # module Foo
56
+ # class CLI
57
+ # module Commands
58
+ # class List < Command
59
+ #
60
+ # usage '[options] [NAME]'
61
+ #
62
+ # argument :name, required: false,
63
+ # desc: 'Optional name to list'
64
+ #
65
+ # description 'Lists all things'
66
+ #
67
+ # man_page 'ronin-foo-list.1'
68
+ #
69
+ # def run(name=nil)
70
+ # # ...
71
+ # end
72
+ #
73
+ # end
74
+ # end
75
+ # end
76
+ # end
77
+ # end
78
+ #
79
+ class Command < CommandKit::Command
80
+
81
+ include CommandKit::Help::Man
82
+ include CommandKit::BugReport
83
+
84
+ end
85
+ end
86
+ end
87
+ end