ergane 0.0.1 → 0.2.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.
data/lib/ergane/tool.rb CHANGED
@@ -1,116 +1,70 @@
1
- module Ergane
2
- class Tool < CommandDefinition
3
- attr_reader :title #capitalized :label
4
- attr_reader :version
1
+ # frozen_string_literal: true
5
2
 
6
- def initialize(label, *paths)
7
- super(label)
3
+ module Ergane
4
+ class Tool < Command
5
+ self.abstract_class = true
8
6
 
9
- @title = label.capitalize
10
- @version = VERSION
7
+ class << self
8
+ extend DSL::Macros
11
9
 
12
- # Dogfood-ing
13
- define do
14
- description "Basic CLI Tool"
15
- switches do
16
- switch :help, default: false, kind: TrueClass, description: "Display this help block" do
17
- raise Help
18
- end
19
- switch :version, default: false, kind: TrueClass, description: "Display the version" do
20
- # TODO: Push ARGV into a variable at the command level that can be manipulated by flags/switches
21
- # NOTE: This would allow a --version to morph into a version command and we could push all this logic to that.
22
- # Additional logic for --version would be -1 or --short etc.
23
- puts "#{@title} Version: #{Ergane::VERSION}"
24
- exit
25
- end
26
- # switch verbose: FalseClass, short: :v, "Turn on verbose logging"
27
- end
10
+ dsl_value :version
28
11
 
29
- run do
30
- begin
31
- command, args = self, []
32
- Process.setproctitle(label.to_s)
33
- command, args = self.parse_args(ARGV.dup)
34
-
35
- command.run(*args)
36
- puts "Finished running #{label}"
37
- rescue Interrupt
38
- puts "\nOkay. Aborting."
39
- rescue RuntimeError
40
- puts "RuntimeError"
41
- binding.pry
42
- rescue Help
43
- puts help(command, args)
44
- ensure
45
- system "printf '\033]0;\007'"
46
- end
12
+ def command_class(klass = nil)
13
+ if klass
14
+ @command_class = klass
15
+ wire_command_class(klass)
16
+ else
17
+ @command_class
47
18
  end
48
19
  end
49
20
 
50
- Pry.config.prompt_name = "#{title} ".light_blue
21
+ def tool_name(name = nil)
22
+ name ? (self.command_name = name) : command_name
23
+ end
51
24
 
52
- load_commands(paths)
53
- end
25
+ def start(argv = ARGV)
26
+ Runner.new(self, argv.dup).execute
27
+ rescue Interrupt
28
+ $stderr.puts "\nAborted."
29
+ exit 130
30
+ rescue Ergane::Error => e
31
+ $stderr.puts e.message
32
+ exit 1
33
+ end
54
34
 
55
- def help(command, args=[])
56
- missing_args = command.arguments.product([false]).to_h.merge(args.product([true]).to_h).map do |arg, is_missing|
57
- a = arg.to_s.light_black.underline.tap do |b|
58
- b.blink if is_missing
35
+ def load_commands(*patterns)
36
+ patterns.flatten.each do |pattern|
37
+ Dir[pattern].sort.each { |file| require file }
59
38
  end
60
- end.join(' ')
39
+ end
61
40
 
62
- command.switch_parser.banner = [].tap do |text|
63
- text << "About: #{description}"
64
- text << "Version: #{version.to_s.light_blue}"
65
- text << "Usage: #{([label] + chain).join(' ').light_red} #{'[options]'.light_cyan} "
66
- if commands.any?
67
- text.last << "[subcommand]".light_black.underline
68
- text << (" ┌" + ("─" * (text.last.uncolorize.length - 12)) + "┘").light_black
69
- commands.each do |key, command|
70
- # text << " ├─┐".light_black + " #{(klass.terms.join(', ')).ljust(24, ' ')} ".send(Athena::Util.next_color) + klass.description.light_black
71
- text << " ├─┐".light_black + " #{key.to_s.ljust(24, ' ')} " + command.description.light_black
72
- end
73
- text << (" └" + "─" * 64).light_black
74
- else
75
- # text.last << command.arguments(missing_args.keys)
76
- end
77
- # text << list_examples if examples.any?
78
- text << "Options:".light_cyan
79
- end.join("\n")
80
- switch_parser
81
- end
41
+ def inherited(subclass)
42
+ super
43
+ create_command_base(subclass) if self == Tool
44
+ end
82
45
 
83
- def self.define(label, chain: [], &block)
84
- c = CommandDefinition.define(label, chain: chain, &block)
46
+ private
85
47
 
86
- parent_command = if chain.any?
87
- Ergane.active_tool.dig(*chain)
88
- else
89
- Ergane.active_tool
48
+ def command_base_for(_name)
49
+ command_class || self
90
50
  end
91
51
 
92
- parent_command[label] = c
93
- end
52
+ def create_command_base(tool_subclass)
53
+ return if tool_subclass.command_class
94
54
 
95
- def load_commands(paths)
96
- activate_tool do
97
- Ergane.logger.debug "Loading paths:"
98
- Array.wrap(paths).each do |path|
99
- Ergane.logger.debug " - #{path}"
100
- Dir[path].each do |path|
101
- file = path.split('/').last
102
- Ergane.logger.debug " - loading #{path.split('/').last(4).join('/')}"
103
- instance_eval(File.read(path), file)
104
- end
105
- end
55
+ base = Class.new(Ergane::Command)
56
+ base.abstract_class = true
57
+ tool_subclass.const_set(:Command, base)
58
+ tool_subclass.command_class(base)
106
59
  end
107
- end
108
-
109
- private
110
60
 
111
- def activate_tool(&block)
112
- Ergane.activate_tool(self, &block)
61
+ # Gives the tool's command base (and everything below it) a reference
62
+ # back to the tool, so Command#registration_target can route
63
+ # subcommands to the tool's registry.
64
+ def wire_command_class(klass)
65
+ tool = self
66
+ klass.define_singleton_method(:tool) { tool }
67
+ end
113
68
  end
114
-
115
69
  end
116
70
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ergane
4
+ module Util
5
+ module Formatting
6
+ extend self
7
+
8
+ COLORS = %i[light_red light_yellow light_green light_blue light_cyan light_magenta].freeze
9
+
10
+ def color_cycle
11
+ @color_cycle ||= COLORS.cycle
12
+ end
13
+
14
+ def next_color
15
+ color_cycle.next
16
+ end
17
+
18
+ def reset_colors!
19
+ @color_cycle = COLORS.cycle
20
+ end
21
+
22
+ def rainbow(string, delimiter = " ")
23
+ string.split(delimiter).map { |word| word.to_s.send(next_color) }.join(delimiter)
24
+ end
25
+
26
+ def colorize_list(items)
27
+ items.map { |item| item.to_s.send(next_color) }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Ergane
2
- VERSION = '0.0.1'
4
+ VERSION = "0.2.0"
3
5
  end
data/lib/ergane.rb CHANGED
@@ -1,85 +1,40 @@
1
- require 'active_support'
2
- require 'active_support/core_ext'
3
- require 'active_support/dependencies'
4
- require 'optparse'
5
- require 'colorize'
6
- require 'notifier'
7
- require 'pry-byebug'
1
+ # frozen_string_literal: true
8
2
 
9
- $LOAD_PATH.unshift File.expand_path("#{__dir__}/ergane")
3
+ require "zeitwerk"
4
+ require "optparse"
5
+ require "colorize"
10
6
 
11
- require 'ergane/version'
12
- require 'ergane/command_definition'
13
- require 'ergane/tool'
7
+ # Eager-loaded (not autoloadable by convention)
8
+ require_relative "ergane/errors"
9
+ require_relative "ergane/core_ext/string"
10
+ require_relative "ergane/core_ext/object"
11
+ require_relative "ergane/core_ext/array"
12
+ require_relative "ergane/core_ext/hash"
13
+ require_relative "ergane/core_ext/option_parser"
14
14
 
15
15
  module Ergane
16
-
17
- Help = Class.new(StandardError)
18
- Interrupt = Class.new(StandardError)
19
-
20
- class << self
21
- attr_reader :logger
22
-
23
- def root(*args)
24
- (@root ||= Pathname.new(File.expand_path('../', __dir__))).join(*args)
25
- end
26
-
27
- def logger
28
- @logger ||= Logger.new($stdout).tap do |log|
29
- log.progname = @@active_tool&.title || self.name
30
- log.level = :warn
31
- end
32
- end
33
-
34
- # def debug(string)
35
- # old_level = logger.level
36
- # logger.level = :debug if $debug
37
- # logger.debug(string)
38
- # ensure
39
- # logger.level = old_level
40
- # end
41
-
42
- @@active_tool = nil
43
-
44
- def active_tool
45
- @@active_tool
46
- end
47
-
48
- def activate_tool(tool)
49
- previous_tool = @@active_tool
50
- @@active_tool = tool
51
- yield if block_given?
52
- ensure
53
- @@active_tool = previous_tool
54
- end
55
-
56
- def notify(message, title: nil, sound: nil, icon: nil, group: nil)
57
- cmd = ['terminal-notifier']
58
- cmd << "-group #{Process.pid}#{group}"
59
- cmd << "-title #{active_tool.label}"
60
- cmd << "-subtitle #{title}" if title
61
- cmd << "-appIcon 'assets/athena md-light-shadow.png'" if icon
62
- cmd << "-message '#{message}'"
63
- cmd << "-activate 'com.apple.Terminal'" # if macos
64
- cmd << "-sound #{sound}" if sound
65
- Thread.new { `afplay /System/Library/Sounds/#{sound == true ? 'Blow' : sound}.aiff` } unless sound == false
66
-
67
- `#{cmd.join(' ')}`
68
- # Notifier.notify(title: title, message: message, image: 'assets/athena md-light-shadow.png')
69
- end
70
-
71
- # Show a list of available extensions to use
72
- def extensions
73
- Extension.library
74
- end
16
+ LOADER = Zeitwerk::Loader.new.tap do |loader|
17
+ loader.tag = "ergane"
18
+ loader.inflector.inflect(
19
+ "dsl" => "DSL",
20
+ "command_dsl" => "CommandDSL",
21
+ "block_dsl" => "BlockDSL"
22
+ )
23
+ loader.push_dir(File.expand_path("..", __FILE__))
24
+ loader.ignore(File.expand_path("ergane/errors.rb", __dir__))
25
+ loader.ignore(File.expand_path("ergane/core_ext", __dir__))
26
+ loader.ignore(File.expand_path("ergane.rb", __dir__))
27
+ loader.setup
75
28
  end
76
- end
77
29
 
78
- if ARGV.include?('--debug')
79
- $debug = true
80
- Ergane.logger.level = :debug
81
- end
30
+ def self.root
31
+ @root ||= Pathname.new(File.expand_path("../..", __FILE__))
32
+ end
82
33
 
83
- Dir[Ergane.root('lib', 'core_ext', "*.rb")].each do |path|
84
- require path
34
+ # The shared registry of path-prefix abbreviations used by
35
+ # Command#abbreviate_path. Seeded with $HOME → "~"; consumers may
36
+ # register additional substitutions.
37
+ def self.paths
38
+ @paths ||= PathRegistry.new.register("~", "~")
39
+ end
85
40
  end
metadata CHANGED
@@ -1,65 +1,50 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ergane
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dale Stevens
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-05-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: activesupport
13
+ name: zeitwerk
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - ">="
16
+ - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '0'
18
+ version: '2.6'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
- - - ">="
23
+ - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '0'
25
+ version: '2.6'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: colorize
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  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: terminal-notifier
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
30
+ - - "~>"
46
31
  - !ruby/object:Gem::Version
47
- version: '0'
32
+ version: '1.1'
48
33
  type: :runtime
49
34
  prerelease: false
50
35
  version_requirements: !ruby/object:Gem::Requirement
51
36
  requirements:
52
- - - ">="
37
+ - - "~>"
53
38
  - !ruby/object:Gem::Version
54
- version: '0'
39
+ version: '1.1'
55
40
  - !ruby/object:Gem::Dependency
56
- name: notifier
41
+ name: bundler
57
42
  requirement: !ruby/object:Gem::Requirement
58
43
  requirements:
59
44
  - - ">="
60
45
  - !ruby/object:Gem::Version
61
46
  version: '0'
62
- type: :runtime
47
+ type: :development
63
48
  prerelease: false
64
49
  version_requirements: !ruby/object:Gem::Requirement
65
50
  requirements:
@@ -73,7 +58,7 @@ dependencies:
73
58
  - - ">="
74
59
  - !ruby/object:Gem::Version
75
60
  version: '0'
76
- type: :runtime
61
+ type: :development
77
62
  prerelease: false
78
63
  version_requirements: !ruby/object:Gem::Requirement
79
64
  requirements:
@@ -81,105 +66,117 @@ dependencies:
81
66
  - !ruby/object:Gem::Version
82
67
  version: '0'
83
68
  - !ruby/object:Gem::Dependency
84
- name: bundler
69
+ name: rake
85
70
  requirement: !ruby/object:Gem::Requirement
86
71
  requirements:
87
- - - ">="
72
+ - - "~>"
88
73
  - !ruby/object:Gem::Version
89
- version: '0'
74
+ version: '13.0'
90
75
  type: :development
91
76
  prerelease: false
92
77
  version_requirements: !ruby/object:Gem::Requirement
93
78
  requirements:
94
- - - ">="
79
+ - - "~>"
95
80
  - !ruby/object:Gem::Version
96
- version: '0'
81
+ version: '13.0'
97
82
  - !ruby/object:Gem::Dependency
98
- name: ruby-prof
83
+ name: rspec
99
84
  requirement: !ruby/object:Gem::Requirement
100
85
  requirements:
101
- - - ">="
86
+ - - "~>"
102
87
  - !ruby/object:Gem::Version
103
- version: '0'
88
+ version: '3.12'
104
89
  type: :development
105
90
  prerelease: false
106
91
  version_requirements: !ruby/object:Gem::Requirement
107
92
  requirements:
108
- - - ">="
93
+ - - "~>"
109
94
  - !ruby/object:Gem::Version
110
- version: '0'
95
+ version: '3.12'
111
96
  - !ruby/object:Gem::Dependency
112
- name: rspec
97
+ name: rubocop
113
98
  requirement: !ruby/object:Gem::Requirement
114
99
  requirements:
115
- - - ">="
100
+ - - "~>"
116
101
  - !ruby/object:Gem::Version
117
- version: '0'
102
+ version: '1.50'
118
103
  type: :development
119
104
  prerelease: false
120
105
  version_requirements: !ruby/object:Gem::Requirement
121
106
  requirements:
122
- - - ">="
107
+ - - "~>"
123
108
  - !ruby/object:Gem::Version
124
- version: '0'
109
+ version: '1.50'
125
110
  - !ruby/object:Gem::Dependency
126
- name: pry-byebug
111
+ name: simplecov
127
112
  requirement: !ruby/object:Gem::Requirement
128
113
  requirements:
129
- - - ">="
114
+ - - "~>"
130
115
  - !ruby/object:Gem::Version
131
- version: '0'
116
+ version: '0.22'
132
117
  type: :development
133
118
  prerelease: false
134
119
  version_requirements: !ruby/object:Gem::Requirement
135
120
  requirements:
136
- - - ">="
121
+ - - "~>"
137
122
  - !ruby/object:Gem::Version
138
- version: '0'
123
+ version: '0.22'
139
124
  - !ruby/object:Gem::Dependency
140
- name: rake
125
+ name: simplecov-json
141
126
  requirement: !ruby/object:Gem::Requirement
142
127
  requirements:
143
- - - ">="
128
+ - - "~>"
144
129
  - !ruby/object:Gem::Version
145
- version: '0'
130
+ version: '0.2'
146
131
  type: :development
147
132
  prerelease: false
148
133
  version_requirements: !ruby/object:Gem::Requirement
149
134
  requirements:
150
- - - ">="
135
+ - - "~>"
151
136
  - !ruby/object:Gem::Version
152
- version: '0'
137
+ version: '0.2'
153
138
  description: |
154
- Library for creating lightweight, yet powerful CLI tools in Ruby.
155
- Emphasis and priority on load speed and flexibility.
139
+ A lightweight, powerful CLI framework for Ruby. Define commands using
140
+ class inheritance or block DSL both produce the same command tree.
141
+ An alternative to Thor with a cleaner, more Ruby-native design.
156
142
  email:
157
143
  - dale@twilightcoders.net
158
- executables:
159
- - ergane
144
+ executables: []
160
145
  extensions: []
161
146
  extra_rdoc_files: []
162
147
  files:
163
148
  - CHANGELOG.md
164
149
  - LICENSE
165
150
  - README.md
166
- - app/commands/ergane/console.rb
167
- - bin/ergane
168
- - lib/core_ext/option_parser.rb
169
151
  - lib/ergane.rb
170
- - lib/ergane/command_definition.rb
171
- - lib/ergane/debug.rb
172
- - lib/ergane/helpers/hashall.rb
173
- - lib/ergane/switch_definition.rb
152
+ - lib/ergane/argument_definition.rb
153
+ - lib/ergane/command.rb
154
+ - lib/ergane/concerns/inheritance.rb
155
+ - lib/ergane/concerns/option_handling.rb
156
+ - lib/ergane/core_ext/array.rb
157
+ - lib/ergane/core_ext/hash.rb
158
+ - lib/ergane/core_ext/object.rb
159
+ - lib/ergane/core_ext/option_parser.rb
160
+ - lib/ergane/core_ext/string.rb
161
+ - lib/ergane/dsl/block_dsl.rb
162
+ - lib/ergane/dsl/command_dsl.rb
163
+ - lib/ergane/dsl/macros.rb
164
+ - lib/ergane/errors.rb
165
+ - lib/ergane/formatter.rb
166
+ - lib/ergane/help_formatter.rb
167
+ - lib/ergane/option_definition.rb
168
+ - lib/ergane/path_registry.rb
169
+ - lib/ergane/runner.rb
174
170
  - lib/ergane/tool.rb
175
- - lib/ergane/util.rb
171
+ - lib/ergane/util/formatting.rb
176
172
  - lib/ergane/version.rb
177
173
  homepage: https://github.com/TwilightCoders/ergane
178
174
  licenses:
179
175
  - MIT
180
176
  metadata:
181
- allowed_push_host: https://rubygems.org
182
- post_install_message:
177
+ rubygems_mfa_required: 'true'
178
+ source_code_uri: https://github.com/TwilightCoders/ergane
179
+ changelog_uri: https://github.com/TwilightCoders/ergane/blob/main/CHANGELOG.md
183
180
  rdoc_options: []
184
181
  require_paths:
185
182
  - lib
@@ -187,15 +184,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
187
184
  requirements:
188
185
  - - ">="
189
186
  - !ruby/object:Gem::Version
190
- version: '2.7'
187
+ version: '3.1'
191
188
  required_rubygems_version: !ruby/object:Gem::Requirement
192
189
  requirements:
193
190
  - - ">="
194
191
  - !ruby/object:Gem::Version
195
192
  version: '0'
196
193
  requirements: []
197
- rubygems_version: 3.1.6
198
- signing_key:
194
+ rubygems_version: 3.6.9
199
195
  specification_version: 4
200
- summary: Ergane — The patron goddess of craftsmen and artisans
196
+ summary: Ergane — a CLI framework forged for mortals
201
197
  test_files: []
@@ -1,22 +0,0 @@
1
- define :console do
2
-
3
- description "Jump into an interactive REPL console"
4
-
5
- requirements inherit: true do
6
- require 'colorize'
7
-
8
- end
9
-
10
- switches(inherit: false) do
11
- # option(:help, description: "display help")
12
- end
13
-
14
- run do
15
- Pry.config.prompt_name = "#{Process.argv0.split('/').last.capitalize} #{label} ".light_blue
16
- Pry.start
17
- end
18
-
19
- def support_method
20
-
21
- end
22
- end
data/bin/ergane DELETED
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- profile = nil
4
- if ARGV.include?('--profile')
5
- require 'ruby-prof'
6
- profile = RubyProf::Profile.new
7
- profile.exclude_common_methods!
8
- profile.start
9
- end
10
-
11
- require 'bundler/setup'
12
- require 'ergane'
13
- require 'ergane/tool'
14
-
15
- tool = Ergane::Tool.new :ergane, 'lib/ergane/commands/**/*.rb', 'app/**/commands/**/*.rb'
16
- tool.run
17
-
18
- if profile
19
- result = profile.stop
20
- File.open "profile-graph.html", 'w+' do |file|
21
- # RubyProf::GraphHtmlPrinter.new(results).print(file)
22
- RubyProf::CallStackPrinter.new(result).print(file)
23
- end
24
- end
@@ -1,15 +0,0 @@
1
- class OptionParser
2
-
3
- # Like order!, but leave any unrecognized --switches alone
4
- def order_recognized!(args)
5
- extra_opts = []
6
- begin
7
- order!(args) { |a| extra_opts << a }
8
- rescue OptionParser::InvalidOption => e
9
- extra_opts << e.args[0]
10
- retry
11
- end
12
- args[0, 0] = extra_opts
13
- end
14
-
15
- end