tty 0.7.0 → 0.8.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -2
  3. data/.travis.yml +15 -4
  4. data/CHANGELOG.md +22 -0
  5. data/Gemfile +4 -7
  6. data/README.md +648 -58
  7. data/appveyor.yml +3 -4
  8. data/exe/teletype +18 -0
  9. data/lib/tty.rb +7 -4
  10. data/lib/tty/cli.rb +140 -0
  11. data/lib/tty/cmd.rb +132 -0
  12. data/lib/tty/commands/add.rb +321 -0
  13. data/lib/tty/commands/new.rb +256 -0
  14. data/lib/tty/gemspec.rb +30 -0
  15. data/lib/tty/licenses.rb +34 -0
  16. data/lib/tty/path_helpers.rb +38 -0
  17. data/lib/tty/plugins.rb +20 -12
  18. data/lib/tty/templater.rb +54 -0
  19. data/lib/tty/templates/add/command.rb.tt +31 -0
  20. data/lib/tty/templates/add/gitkeep.tt +1 -0
  21. data/lib/tty/templates/add/namespace.rb.tt +17 -0
  22. data/lib/tty/templates/add/spec/integration/command_spec.rb.tt +20 -0
  23. data/lib/tty/templates/add/spec/integration/sub_command_spec.rb.tt +16 -0
  24. data/lib/tty/templates/add/spec/unit/command_spec.rb.tt +15 -0
  25. data/lib/tty/templates/add/spec/unit/sub_command_spec.rb.tt +15 -0
  26. data/lib/tty/templates/add/test/integration/command_test.rb.tt +23 -0
  27. data/lib/tty/templates/add/test/integration/sub_command_test.rb.tt +19 -0
  28. data/lib/tty/templates/add/test/unit/command_test.rb.tt +16 -0
  29. data/lib/tty/templates/add/test/unit/sub_command_test.rb.tt +16 -0
  30. data/lib/tty/templates/new/agplv3_LICENSE.txt.tt +555 -0
  31. data/lib/tty/templates/new/apache_LICENSE.txt.tt +157 -0
  32. data/lib/tty/templates/new/bsd2_LICENSE.txt.tt +22 -0
  33. data/lib/tty/templates/new/bsd3_LICENSE.txt.tt +26 -0
  34. data/lib/tty/templates/new/exe/newcli.tt +18 -0
  35. data/lib/tty/templates/new/gitkeep.tt +1 -0
  36. data/lib/tty/templates/new/gplv2_LICENSE.txt.tt +255 -0
  37. data/lib/tty/templates/new/gplv3_LICENSE.txt.tt +543 -0
  38. data/lib/tty/templates/new/lgplv3_LICENSE.txt.tt +143 -0
  39. data/lib/tty/templates/new/lib/newcli/cli.rb.tt +24 -0
  40. data/lib/tty/templates/new/lib/newcli/command.rb.tt +124 -0
  41. data/lib/tty/templates/new/mit_LICENSE.txt.tt +20 -0
  42. data/lib/tty/templates/new/mplv2_LICENSE.txt.tt +277 -0
  43. data/lib/tty/version.rb +1 -1
  44. data/spec/fixtures/foo-0.0.1.gemspec +4 -4
  45. data/spec/integration/add_desc_args_spec.rb +341 -0
  46. data/spec/integration/add_force_spec.rb +98 -0
  47. data/spec/integration/add_namespaced_spec.rb +291 -0
  48. data/spec/integration/add_spec.rb +535 -0
  49. data/spec/integration/add_subcommand_spec.rb +259 -0
  50. data/spec/integration/new_author_spec.rb +19 -0
  51. data/spec/integration/new_license_spec.rb +39 -0
  52. data/spec/integration/new_namespaced_spec.rb +228 -0
  53. data/spec/integration/new_spec.rb +354 -0
  54. data/spec/integration/new_test_spec.rb +21 -0
  55. data/spec/integration/start_spec.rb +21 -0
  56. data/spec/spec_helper.rb +47 -16
  57. data/spec/unit/gemspec_spec.rb +17 -0
  58. data/spec/{tty/plugins/load_spec.rb → unit/plugins/activate_spec.rb} +2 -4
  59. data/spec/unit/plugins/load_from_spec.rb +28 -0
  60. data/spec/{tty → unit}/plugins/plugin/load_spec.rb +1 -3
  61. data/spec/{tty → unit}/plugins/plugin/new_spec.rb +1 -3
  62. data/spec/{tty → unit}/tty_spec.rb +1 -3
  63. data/tty.gemspec +25 -15
  64. metadata +186 -49
  65. data/spec/tty/plugins/find_spec.rb +0 -20
  66. data/tasks/metrics/cane.rake +0 -14
  67. data/tasks/metrics/flog.rake +0 -17
  68. data/tasks/metrics/heckle.rake +0 -15
  69. data/tasks/metrics/reek.rake +0 -13
@@ -9,7 +9,6 @@ test_script:
9
9
  - bundle exec rake ci
10
10
  environment:
11
11
  matrix:
12
- - ruby_version: "193"
13
12
  - ruby_version: "200"
14
13
  - ruby_version: "200-x64"
15
14
  - ruby_version: "21"
@@ -18,6 +17,6 @@ environment:
18
17
  - ruby_version: "22-x64"
19
18
  - ruby_version: "23"
20
19
  - ruby_version: "23-x64"
21
- matrix:
22
- allow_failures:
23
- - ruby_version: "193"
20
+ - ruby_version: "24"
21
+ - ruby_version: "24-x64"
22
+ - ruby_version: "25-x64"
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib_path = File.expand_path('../lib', __dir__)
5
+ $:.unshift(lib_path) if !$:.include?(lib_path)
6
+ require 'tty/cli'
7
+
8
+ Signal.trap('INT') do
9
+ warn("\n#{caller.join("\n")}: interrupted")
10
+ exit(1)
11
+ end
12
+
13
+ begin
14
+ TTY::CLI.start
15
+ rescue TTY::CLI::Error => err
16
+ puts "ERROR: #{err.message}"
17
+ exit 1 #err.status
18
+ end
data/lib/tty.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'tty/version'
4
- require 'tty/plugins'
5
- require 'tty/plugins/plugin'
3
+ require_relative 'tty/cli'
4
+ require_relative 'tty/plugins'
5
+ require_relative 'tty/version'
6
6
 
7
7
  module TTY
8
+ GEMSPEC_PATH = ::File.expand_path("#{::File.dirname(__FILE__)}/../tty.gemspec")
9
+
8
10
  class << self
9
11
  def included(base)
10
12
  base.send :extend, ClassMethods
@@ -25,4 +27,5 @@ module TTY
25
27
  extend ClassMethods
26
28
  end # TTY
27
29
 
28
- TTY.plugins.find('tty').load
30
+ TTY.plugins.load_from(TTY::GEMSPEC_PATH, /tty-(.*)|pastel/)
31
+ TTY.plugins.activate
@@ -0,0 +1,140 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'thor'
5
+
6
+ require_relative 'licenses'
7
+
8
+ module TTY
9
+ # Main CLI runner
10
+ # @api public
11
+ class CLI < Thor
12
+ extend TTY::Licenses
13
+
14
+ # Error raised by this runner
15
+ Error = Class.new(StandardError)
16
+
17
+ no_commands do
18
+ def self.logo(banner)
19
+ <<-EOS
20
+ ┏━━━┓
21
+ ┏━┳╋┳┳━┻━━┓
22
+ ┣━┫┗┫┗┳┳┳━┫
23
+ ┃ ┃┏┫┏┫┃┃★┃ #{banner}
24
+ ┃ ┗━┻━╋┓┃ ┃
25
+ ┗━━━━━┻━┻━┛
26
+ EOS
27
+ end
28
+
29
+ def self.top_banner
30
+ require 'pastel'
31
+ pastel = Pastel.new
32
+ pastel.red(logo('Terminal apps toolkit'))
33
+ end
34
+
35
+ def self.executable_name
36
+ ::File.basename($PROGRAM_NAME)
37
+ end
38
+ end
39
+
40
+ class_option :"no-color", type: :boolean, default: false,
41
+ desc: 'Disable colorization in output'
42
+ class_option :"dry-run", type: :boolean, aliases: ['-r'],
43
+ desc: 'Run but do not make any changes'
44
+ class_option :debug, type: :boolean, default: false,
45
+ desc: 'Run in debug mode'
46
+
47
+ def self.help(*)
48
+ puts top_banner
49
+ super
50
+ end
51
+
52
+ desc 'add COMMAND [SUBCOMMAND] [OPTIONS]', 'Add a command to the application'
53
+ long_desc <<-D
54
+ The `teletype add` will create a new command and place it into
55
+ appropriate structure in the cli app.
56
+
57
+ Example:
58
+ teletype add config --desc 'Set and get configuration options'
59
+
60
+ This generates a command in app/commands/config.rb
61
+
62
+ You can also add subcommands
63
+
64
+ Example:
65
+ teletype add config server
66
+
67
+ This generates a command in app/commands/config/server.rb
68
+ D
69
+ method_option :args, type: :array, aliases: '-a', default: [],
70
+ desc: 'List command argument names',
71
+ banner: 'arg1 arg2'
72
+ method_option :desc, aliases: '-d', desc: "Describe command's purpose"
73
+ method_option :force, type: :boolean, aliases: '-f',
74
+ desc: 'Overwrite existing command'
75
+ method_option :help, type: :boolean,
76
+ aliases: '-h', desc: 'Display usage information'
77
+ method_option :test, type: :string, aliases: '-t',
78
+ desc: 'Generate a test setup',
79
+ banner: 'rspec', enum: %w(rspec minitest)
80
+ def add(*names)
81
+ if options[:help]
82
+ invoke :help, ['add']
83
+ elsif names.size < 1
84
+ fail Error, "'teletype add' was called with no arguments\n" \
85
+ "Usage: 'teletype add COMMAND_NAME'"
86
+ else
87
+ require_relative 'commands/add'
88
+ TTY::Commands::Add.new(names, options).execute
89
+ end
90
+ end
91
+
92
+ desc 'new PROJECT_NAME [OPTIONS]', 'Create a new command line app skeleton'
93
+ long_desc <<-D
94
+ The 'teletype new' command creates a new command line application
95
+ with a default directory structure and configuration at the
96
+ specified path.
97
+
98
+ The PROJECT_NAME will be the name for the directory that includes all the
99
+ files and be the default binary name.
100
+
101
+ Example:
102
+ teletype new cli_app
103
+ D
104
+ method_option :author, type: :array, aliases: '-a',
105
+ desc: 'Author(s) of this library',
106
+ banner: 'name1 name2'
107
+ method_option :ext, type: :boolean, default: false,
108
+ desc: 'Generate a boilerpalate for C extension'
109
+ method_option :coc, type: :boolean, default: true,
110
+ desc: 'Generate a code of conduct file'
111
+ method_option :force, type: :boolean, aliases: '-f',
112
+ desc: 'Overwrite existing files'
113
+ method_option :help, aliases: '-h', type: :boolean,
114
+ desc: 'Display usage information'
115
+ method_option :license, type: :string, default: 'mit', banner: 'mit',
116
+ aliases: '-l', desc: 'Generate a license file',
117
+ enum: licenses.keys.concat(['custom'])
118
+ method_option :test, type: :string, default: 'rspec',
119
+ aliases: '-t', desc: 'Generate a test setup',
120
+ banner: 'rspec', enum: %w(rspec minitest)
121
+ def new(app_name = nil)
122
+ if options[:help]
123
+ invoke :help, ['new']
124
+ elsif app_name.nil?
125
+ fail Error, "'teletype new' was called with no arguments\n" \
126
+ "Usage: 'teletype new PROJECT_NAME'"
127
+ else
128
+ require_relative 'commands/new'
129
+ TTY::Commands::New.new(app_name, options).execute
130
+ end
131
+ end
132
+
133
+ desc 'version', 'TTY version'
134
+ def version
135
+ require_relative 'version'
136
+ puts "v#{TTY::VERSION}"
137
+ end
138
+ map %w(--version -v) => :version
139
+ end # CLI
140
+ end # TTY
@@ -0,0 +1,132 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'forwardable'
5
+
6
+ require_relative 'path_helpers'
7
+
8
+ module TTY
9
+ class Cmd
10
+ extend Forwardable
11
+ include PathHelpers
12
+
13
+ def_delegators :command, :run
14
+
15
+ def_delegators 'Thor::Util', :snake_case
16
+
17
+ # Execute this command
18
+ #
19
+ # @api public
20
+ def execute(*)
21
+ raise(
22
+ NotImplementedError,
23
+ "#{self.class}##{__method__} must be implemented"
24
+ )
25
+ end
26
+
27
+ # The external commands runner
28
+ #
29
+ # @see http://www.rubydoc.info/gems/tty-command
30
+ #
31
+ # @api public
32
+ def command(**options)
33
+ require 'tty-command'
34
+ TTY::Command.new(options)
35
+ end
36
+
37
+ # The cursor movement
38
+ #
39
+ # @see http://www.rubydoc.info/gems/tty-cursor
40
+ #
41
+ # @api public
42
+ def cursor
43
+ require 'tty-cursor'
44
+ TTY::Cursor
45
+ end
46
+
47
+ # Open a file or text in the user's preferred editor
48
+ #
49
+ # @see http://www.rubydoc.info/gems/tty-editor
50
+ #
51
+ # @api public
52
+ def editor
53
+ require 'tty-editor'
54
+ TTY::Editor
55
+ end
56
+
57
+ # File manipulation utility methods
58
+ #
59
+ # @see http://www.rubydoc.info/gems/tty-file
60
+ #
61
+ # @api public
62
+ def generator
63
+ require 'tty-file'
64
+ TTY::File
65
+ end
66
+
67
+ # Terminal output paging
68
+ #
69
+ # @see http://www.rubydoc.info/gems/tty-pager
70
+ #
71
+ # @api public
72
+ def pager(**options)
73
+ require 'tty-pager'
74
+ TTY::Pager.new(options)
75
+ end
76
+
77
+ # Terminal platform and OS properties
78
+ #
79
+ # @see http://www.rubydoc.info/gems/tty-pager
80
+ #
81
+ # @api public
82
+ def platform
83
+ require 'tty-platform'
84
+ TTY::Platform.new
85
+ end
86
+
87
+ # The interactive prompt
88
+ #
89
+ # @see http://www.rubydoc.info/gems/tty-prompt
90
+ #
91
+ # @api public
92
+ def prompt(**options)
93
+ require 'tty-prompt'
94
+ TTY::Prompt.new(options)
95
+ end
96
+
97
+ # Get terminal screen properties
98
+ #
99
+ # @see http://www.rubydoc.info/gems/tty-screen
100
+ #
101
+ # @api public
102
+ def screen
103
+ require 'tty-screen'
104
+ TTY::Screen
105
+ end
106
+
107
+ # The unix which utility
108
+ #
109
+ # @see http://www.rubydoc.info/gems/tty-which
110
+ #
111
+ # @api public
112
+ def which(*args)
113
+ require 'tty-which'
114
+ TTY::Which.which(*args)
115
+ end
116
+
117
+ # Check if executable exists
118
+ #
119
+ # @see http://www.rubydoc.info/gems/tty-which
120
+ #
121
+ # @api public
122
+ def exec_exist?(*args)
123
+ require 'tty-which'
124
+ TTY::Which.exist?(*args)
125
+ end
126
+
127
+ def constantinize(str)
128
+ str.gsub(/-[_-]*(?![_-]|$)/) { "::" }
129
+ .gsub(/([_-]+|(::)|^)(.|$)/) { $2.to_s + $3.upcase}
130
+ end
131
+ end # Cmd
132
+ end # TTY
@@ -0,0 +1,321 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'ostruct'
5
+
6
+ require_relative '../cmd'
7
+ require_relative '../templater'
8
+
9
+ module TTY
10
+ module Commands
11
+ class Add < TTY::Cmd
12
+ include PathHelpers
13
+
14
+ attr_reader :app_name
15
+
16
+ attr_reader :cmd_name
17
+
18
+ attr_reader :subcmd_name
19
+
20
+ attr_reader :options
21
+
22
+ def initialize(cmd_names, options)
23
+ @cmd_name = cmd_names[0]
24
+ @subcmd_name = cmd_names[1]
25
+ @app_path = relative_path_from(root_path, root_path)
26
+ @app_name = name_from_path(root_path)
27
+ @options = options
28
+
29
+ @templater = Templater.new('add', @app_path)
30
+ end
31
+
32
+ def namespaced_path
33
+ app_name.tr('-', '/')
34
+ end
35
+
36
+ def template_context
37
+ opts = OpenStruct.new
38
+ opts[:cmd_options] = cmd_options
39
+ opts[:cmd_object_parts] = cmd_object_parts
40
+ opts[:cmd_desc_args] = cmd_desc_args
41
+ opts[:cmd_desc] = cmd_desc
42
+ opts[:app_indent] = app_indent
43
+ opts[:cmd_indent] = cmd_indent
44
+ opts[:cmd_path] = "#{namespaced_path}/commands/#{cmd_name_path}"
45
+ opts[:subcmd_path] = subcmd_name &&
46
+ "#{namespaced_path}/commands/#{cmd_name_path}/#{subcmd_name_path}"
47
+ opts[:cmd_name_constantinized] = cmd_name_constantinized
48
+ opts[:subcmd_name_constantinized] = subcmd_name && subcmd_name_constantinized
49
+ opts[:app_name_underscored] = app_name_underscored
50
+ opts[:cmd_name_underscored] = cmd_name_underscored
51
+ opts[:subcmd_name_underscored] = subcmd_name && subcmd_name_underscored
52
+ opts[:app_constantinized_parts] = app_name_constantinized.split('::')
53
+ opts[:cmd_constantinized_parts] = cmd_constantinized_parts
54
+ opts[:cmd_file_path] = cmd_file_path
55
+ opts
56
+ end
57
+
58
+ def file_options
59
+ opts = {}
60
+ opts[:force] = true if options['force']
61
+ opts[:color] = false if options['no-color']
62
+ opts
63
+ end
64
+
65
+ def execute(input: $stdin, output: $stdout)
66
+ validate_cmd_name(cmd_name)
67
+
68
+ test_dir = (options["test"] == 'rspec') || ::Dir.exist?('spec') ? 'spec' : 'test'
69
+ cli_file = "lib/#{namespaced_path}/cli.rb"
70
+ cli_content = ::File.read(cli_file)
71
+ cmd_file = "lib/#{namespaced_path}/commands/#{cmd_name_path}.rb"
72
+ cmd_template_path = "lib/#{namespaced_path}/templates/#{cmd_name_path}"
73
+
74
+ cmd_integ_test_file = "#{test_dir}/integration/#{cmd_name_path}_#{test_dir}.rb"
75
+ cmd_unit_test_file = "#{test_dir}/unit/#{cmd_name_path}_#{test_dir}.rb"
76
+
77
+ if !subcmd_present?
78
+ @templater.add_mapping(
79
+ "#{test_dir}/integration/command_#{test_dir}.rb.tt",
80
+ "#{test_dir}/integration/#{cmd_name_path}_#{test_dir}.rb")
81
+ @templater.add_mapping("#{test_dir}/unit/command_#{test_dir}.rb.tt",
82
+ "#{test_dir}/unit/#{cmd_name_path}_#{test_dir}.rb")
83
+ @templater.add_mapping('command.rb.tt', cmd_file)
84
+ @templater.add_mapping('gitkeep.tt', "#{cmd_template_path}/.gitkeep")
85
+ @templater.generate(template_context, file_options)
86
+
87
+ if !cmd_exists?(cli_content)
88
+ match = cmd_matches.find { |m| cli_content =~ m }
89
+ generator.inject_into_file(
90
+ cli_file, "\n#{cmd_template}",
91
+ {after: match}.merge(file_options))
92
+ end
93
+ else
94
+ subcmd_file = "lib/#{namespaced_path}/commands/#{cmd_name_path}/#{subcmd_name_path}.rb"
95
+ subcmd_template_path = "lib/#{namespaced_path}/templates/#{cmd_name_path}/#{subcmd_name_path}"
96
+ unless ::File.exists?(cmd_integ_test_file)
97
+ @templater.add_mapping(
98
+ "#{test_dir}/integration/command_#{test_dir}.rb.tt",
99
+ cmd_integ_test_file)
100
+ end
101
+ unless ::File.exists?(cmd_unit_test_file)
102
+ @templater.add_mapping(
103
+ "#{test_dir}/unit/#{cmd_name_path}_#{test_dir}.rb",
104
+ cmd_unit_test_file
105
+ )
106
+ end
107
+ @templater.add_mapping(
108
+ "#{test_dir}/integration/sub_command_#{test_dir}.rb.tt",
109
+ "#{test_dir}/integration/#{cmd_name_path}/#{subcmd_name_path}_#{test_dir}.rb")
110
+ @templater.add_mapping(
111
+ "#{test_dir}/unit/sub_command_#{test_dir}.rb.tt",
112
+ "#{test_dir}/unit/#{cmd_name_path}/#{subcmd_name_path}_#{test_dir}.rb"
113
+ )
114
+ unless ::File.exists?(cmd_file) # namespace already present
115
+ @templater.add_mapping('namespace.rb.tt', cmd_file)
116
+ end
117
+ @templater.add_mapping('command.rb.tt', subcmd_file)
118
+ @templater.add_mapping('gitkeep.tt', "#{subcmd_template_path}/.gitkeep")
119
+ @templater.generate(template_context, file_options)
120
+
121
+ if !subcmd_registered?(cli_content)
122
+ match = register_subcmd_matches.find { |m| cli_content =~ m }
123
+ generator.inject_into_file(
124
+ cli_file, "\n#{register_subcmd_template}",
125
+ {after: match}.merge(file_options))
126
+ end
127
+
128
+ content = ::File.read(cmd_file)
129
+ if !subcmd_exists?(content)
130
+ match = subcmd_matches.find {|m| content =~ m }
131
+ generator.inject_into_file(
132
+ cmd_file, "\n#{subcmd_template}",
133
+ {after: match}.merge(file_options))
134
+ end
135
+ end
136
+ end
137
+
138
+ def subcmd_present?
139
+ !subcmd_name.nil?
140
+ end
141
+
142
+ def subcmd_registered?(content)
143
+ content =~%r{\s*require_relative 'commands/#{cmd_name_path}'}
144
+ end
145
+
146
+ def subcmd_exists?(content)
147
+ content =~ %r{\s*def #{subcmd_name_underscored}.*}
148
+ end
149
+
150
+ def cmd_exists?(content)
151
+ content =~ %r{\s*def #{cmd_name_underscored}.*}
152
+ end
153
+
154
+ # Matches for inlining command defition in template
155
+ #
156
+ # @api private
157
+ def cmd_matches
158
+ [
159
+ %r{def version.*?:version\n}m,
160
+ %r{def version.*?#{app_indent} end\n}m,
161
+ %r{class CLI < Thor\n}
162
+ ]
163
+ end
164
+
165
+ def subcmd_matches
166
+ [
167
+ %r{namespace .*?\n},
168
+ %r{class .*? < Thor\n}
169
+ ]
170
+ end
171
+
172
+ def register_subcmd_matches
173
+ [
174
+ %r{require_relative .*?\nregister .*?\n}m
175
+ ].concat(cmd_matches)
176
+ end
177
+
178
+ private
179
+
180
+ def cmd_template
181
+ <<-EOS
182
+ #{app_indent}#{cmd_indent}desc '#{cmd_name_underscored}#{cmd_desc_args}', '#{cmd_desc}'
183
+ #{app_indent}#{cmd_indent}method_option :help, aliases: '-h', type: :boolean,
184
+ #{app_indent}#{cmd_indent} desc: 'Display usage information'
185
+ #{app_indent}#{cmd_indent}def #{cmd_name_underscored}(#{cmd_args.join(', ')})
186
+ #{app_indent}#{cmd_indent} if options[:help]
187
+ #{app_indent}#{cmd_indent} invoke :help, ['#{cmd_name_underscored}']
188
+ #{app_indent}#{cmd_indent} else
189
+ #{app_indent}#{cmd_indent} require_relative 'commands/#{cmd_name_path}'
190
+ #{app_indent}#{cmd_indent} #{cmd_object_parts.join('::')}.new(#{cmd_options.join(', ')}).execute
191
+ #{app_indent}#{cmd_indent} end
192
+ #{app_indent}#{cmd_indent}end
193
+ EOS
194
+ end
195
+
196
+ def register_subcmd_template
197
+ <<-EOS
198
+ #{app_indent} require_relative 'commands/#{cmd_name_path}'
199
+ #{app_indent} register #{cmd_object_parts[0..-2].join('::')}, '#{cmd_name_underscored}', '#{cmd_name_underscored} [SUBCOMMAND]', '#{cmd_desc}'
200
+ EOS
201
+ end
202
+
203
+ def subcmd_template
204
+ <<-EOS
205
+ #{app_indent}#{cmd_indent}desc '#{subcmd_name_underscored}#{cmd_desc_args}', '#{cmd_desc}'
206
+ #{app_indent}#{cmd_indent}method_option :help, aliases: '-h', type: :boolean,
207
+ #{app_indent}#{cmd_indent} desc: 'Display usage information'
208
+ #{app_indent}#{cmd_indent}def #{subcmd_name_underscored}(#{cmd_args.join(', ')})
209
+ #{app_indent}#{cmd_indent} if options[:help]
210
+ #{app_indent}#{cmd_indent} invoke :help, ['#{subcmd_name_underscored}']
211
+ #{app_indent}#{cmd_indent} else
212
+ #{app_indent}#{cmd_indent} require_relative '#{cmd_name_path}/#{subcmd_name_path}'
213
+ #{app_indent}#{cmd_indent} #{cmd_object_parts.join('::')}.new(#{cmd_options.join(', ')}).execute
214
+ #{app_indent}#{cmd_indent} end
215
+ #{app_indent}#{cmd_indent}end
216
+ EOS
217
+ end
218
+
219
+ def cmd_desc_args
220
+ return '' unless @options[:args].any?
221
+ ' ' + @options[:args].map do |arg|
222
+ if arg.start_with?('*')
223
+ arg[1..-1].upcase + '...'
224
+ elsif arg.include?('=')
225
+ "[#{arg.split('=')[0].strip}]"
226
+ else
227
+ arg
228
+ end.upcase
229
+ end.join(' ')
230
+ end
231
+
232
+ def cmd_desc
233
+ @options[:desc].nil? ? 'Command description...' : @options[:desc]
234
+ end
235
+
236
+ def cmd_args
237
+ @options[:args].empty? ? ['*'] : @options[:args]
238
+ end
239
+
240
+ def cmd_options
241
+ @options[:args].map do |arg|
242
+ if arg.start_with?('*')
243
+ arg[1..-1]
244
+ elsif arg.include?('=')
245
+ arg.split('=')[0].strip
246
+ else
247
+ arg
248
+ end
249
+ end + ['options']
250
+ end
251
+
252
+ def app_indent
253
+ ' ' * app_name_constantinized.split('::').size
254
+ end
255
+
256
+ def cmd_indent
257
+ ' ' * cmd_constantinized_parts.size
258
+ end
259
+
260
+ def cmd_object_parts
261
+ [
262
+ app_name_constantinized,
263
+ 'Commands',
264
+ cmd_name && cmd_name_constantinized,
265
+ subcmd_name && subcmd_name_constantinized
266
+ ].compact
267
+ end
268
+
269
+ def cmd_constantinized_parts
270
+ [
271
+ cmd_name && constantinize(cmd_name),
272
+ subcmd_name && constantinize(subcmd_name)
273
+ ].compact
274
+ end
275
+
276
+ def validate_cmd_name(cmd_name)
277
+ # TODO: check if command has correct name
278
+ end
279
+
280
+ def app_name_constantinized
281
+ constantinize(app_name)
282
+ end
283
+
284
+ def app_name_underscored
285
+ snake_case(app_name)
286
+ end
287
+
288
+ def cmd_name_constantinized
289
+ constantinize(cmd_name)
290
+ end
291
+
292
+ def cmd_name_underscored
293
+ snake_case(cmd_name)
294
+ end
295
+
296
+ def cmd_name_path
297
+ cmd_name_underscored.tr('-', '/')
298
+ end
299
+
300
+ def cmd_file_path
301
+ '../' * cmd_constantinized_parts.size + 'command'
302
+ end
303
+
304
+ def subcmd_name_underscored
305
+ snake_case(subcmd_name)
306
+ end
307
+
308
+ def subcmd_name_constantinized
309
+ constantinize(subcmd_name)
310
+ end
311
+
312
+ def subcmd_name_path
313
+ subcmd_name_underscored.tr('-', '/')
314
+ end
315
+
316
+ def spec_root
317
+ Pathname.new('spec')
318
+ end
319
+ end # Add
320
+ end # Commands
321
+ end # TTY