tty 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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