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
@@ -0,0 +1,256 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'pastel'
5
+ require 'pathname'
6
+ require 'ostruct'
7
+
8
+ require_relative '../cmd'
9
+ require_relative '../gemspec'
10
+ require_relative '../licenses'
11
+ require_relative '../plugins'
12
+ require_relative '../templater'
13
+ require_relative '../version'
14
+
15
+
16
+ module TTY
17
+ module Commands
18
+ # The `new` command
19
+ #
20
+ # @api private
21
+ class New < TTY::Cmd
22
+ include TTY::Licenses
23
+
24
+ GEMSPEC_PATH = Pathname(__dir__).join('../../../tty.gemspec').realpath.to_s
25
+
26
+ # @api private
27
+ attr_reader :app_name
28
+
29
+ attr_reader :app_path
30
+
31
+ # @api private
32
+ attr_reader :options
33
+
34
+ # @api private
35
+ attr_reader :target_path
36
+
37
+ attr_reader :templates
38
+
39
+ def initialize(app_path, options)
40
+ @app_path = relative_path_from(root_path, app_path)
41
+ @app_name = name_from_path(app_path)
42
+ @options = options
43
+ @pastel = Pastel.new(enabled: !options['no-color'])
44
+
45
+ @target_path = root_path.join(@app_path)
46
+ @templater = Templater.new('new', @app_path)
47
+ @runner = command(printer: :null)
48
+ end
49
+
50
+ def git_exist?
51
+ exec_exist?('git')
52
+ end
53
+
54
+ def git_author
55
+ git_exist? ? `git config user.name`.chomp : ''
56
+ end
57
+
58
+ def author
59
+ if !Array(options['author']).empty?
60
+ options['author'].join(', ')
61
+ elsif !git_author.empty?
62
+ git_author
63
+ else
64
+ 'TODO: Write your name'
65
+ end
66
+ end
67
+
68
+ def namespaced_path
69
+ app_name.tr('-', '/')
70
+ end
71
+
72
+ def underscored_name
73
+ app_name.tr('-', '_')
74
+ end
75
+
76
+ def constantinized_name
77
+ constantinize(app_name)
78
+ end
79
+
80
+ def template_context
81
+ opts = OpenStruct.new
82
+ opts[:app_name] = app_name
83
+ opts[:author] = author
84
+ opts[:namespaced_path] = namespaced_path
85
+ opts[:underscored_name] = underscored_name
86
+ opts[:constantinized_name] = constantinized_name
87
+ opts[:constantinized_parts] = constantinized_name.split('::')
88
+ opts[:indent] = ' ' * constantinized_name.split('::').size
89
+ opts
90
+ end
91
+
92
+ def file_options
93
+ opts = {}
94
+ opts[:force] = true if options['force']
95
+ opts[:color] = false if options['no-color']
96
+ opts
97
+ end
98
+
99
+ def gemspec_name
100
+ "#{app_name}.gemspec"
101
+ end
102
+
103
+ def gemspec_path
104
+ target_path.join("#{app_name}.gemspec").to_s
105
+ end
106
+
107
+ # Execute the command
108
+ #
109
+ # @api public
110
+ def execute(input: $stdin, output: $stdout)
111
+ output.puts "OPTS: #{options}" if options['debug']
112
+
113
+ coc_opt = options['coc'] ? '--coc' : '--no-coc'
114
+ ext_opt = options['ext'] ? '--ext' : '--no-ext'
115
+ test_opt = options['test']
116
+ command = [
117
+ "bundle gem #{target_path}",
118
+ '--no-mit',
119
+ '--no-exe',
120
+ coc_opt,
121
+ ext_opt,
122
+ "-t #{test_opt}"
123
+ ].join(' ')
124
+
125
+ install_info = []
126
+
127
+ @runner.run(command) do |out, err|
128
+ next unless out
129
+ out.each_line do |line|
130
+ if line =~ /^Initializing git/
131
+ install_info << line.dup
132
+ next
133
+ elsif line =~ /^(Gem.*?was successfully created)/
134
+ next
135
+ end
136
+
137
+ if !options['no-color']
138
+ output.puts color_actions(line)
139
+ else
140
+ output.puts line
141
+ end
142
+ end
143
+ end
144
+
145
+ add_app_templates
146
+ add_empty_directories
147
+ add_required_libs_to_gemspec
148
+ @templater.generate(template_context, file_options)
149
+ make_executable
150
+ output.puts install_info.join("\n") unless install_info.empty?
151
+
152
+ output.puts "\n" + @pastel.green("Your teletype project has been created successfully.")
153
+ output.puts "\n" + @pastel.green("Run \"teletype help\" for more commands.\n")
154
+ end
155
+
156
+ private
157
+
158
+ def color_actions(out)
159
+ out.gsub(/^(\s+)(create)/, '\1' + @pastel.green('\2'))
160
+ .gsub(/^(\s+)(identical)/, '\1' + @pastel.blue('\2'))
161
+ .gsub(/^(\s+)(conflict)/, '\1' + @pastel.red('\2'))
162
+ .gsub(/^(\s+)(forced|skipped)/, '\1' + @pastel.yellow('\2'))
163
+ end
164
+
165
+ def make_executable
166
+ exec_path = target_path.join("exe/#{app_name}")
167
+ exec_path.chmod(exec_path.stat.mode | 0o111)
168
+ end
169
+
170
+ def add_app_templates
171
+ @templater.add_mapping('lib/newcli/cli.rb.tt',
172
+ "lib/#{namespaced_path}/cli.rb")
173
+ @templater.add_mapping('lib/newcli/command.rb.tt',
174
+ "lib/#{namespaced_path}/command.rb")
175
+ @templater.add_mapping('exe/newcli.tt', "exe/#{app_name}")
176
+
177
+ license = options['license'] == 'none' ? false : options['license']
178
+ if license
179
+ @templater.add_mapping("#{license}_LICENSE.txt.tt", 'LICENSE.txt')
180
+ add_license_to_gemspec(license)
181
+ add_license_to_readme(license)
182
+ end
183
+ end
184
+
185
+ def test_dir
186
+ options['test'] == 'rspec' ? 'spec' : 'test'
187
+ end
188
+
189
+ def add_empty_directories
190
+ @templater.add_mapping('gitkeep.tt', "lib/#{namespaced_path}/commands/.gitkeep")
191
+ @templater.add_mapping('gitkeep.tt', "lib/#{namespaced_path}/templates/.gitkeep")
192
+ @templater.add_mapping('gitkeep.tt', "#{test_dir}/integration/.gitkeep")
193
+ @templater.add_mapping('gitkeep.tt', "#{test_dir}/support/.gitkeep")
194
+ @templater.add_mapping('gitkeep.tt', "#{test_dir}/unit/.gitkeep")
195
+ end
196
+
197
+ # Add license definition to gemspec
198
+ #
199
+ # @api private
200
+ def add_license_to_gemspec(license)
201
+ gemspec = TTY::Gemspec.new
202
+ gemspec.read(gemspec_path)
203
+ license_regex = /(^\s*#{gemspec.var_name}\.license\s*=\s*).*/
204
+
205
+ if gemspec.content =~ license_regex
206
+ gemspec.content.gsub!(liecense_regex,
207
+ "\\1\"#{licenses[license][:name]}\"")
208
+ else
209
+ gem_license = ' ' * gemspec.pre_var_indent
210
+ gem_license << "#{gemspec.var_name}.license"
211
+ gem_license << ' ' * (gemspec.post_var_indent - '.license'.size)
212
+ gem_license << "= \"#{licenses[license][:name]}\""
213
+ gemspec.content.gsub!(/(^\s*#{gemspec.var_name}\.name\s*=\s*.*$)/,
214
+ "\\1\n#{gem_license}")
215
+ end
216
+ gemspec.write(gemspec_path)
217
+ end
218
+
219
+ def add_license_to_readme(license)
220
+ desc = licenses[license][:desc]
221
+ readme_path = Dir["#{app_path}/README*"].first
222
+ content = "\n## Copyright\n\n" \
223
+ "Copyright (c) #{Time.now.year} #{author}. "\
224
+ "See [#{desc}](LICENSE.txt) for further details."
225
+ within_root_path do
226
+ generator.append_to_file(readme_path, content, file_options)
227
+ end
228
+ end
229
+
230
+ def add_required_libs_to_gemspec
231
+ gemspec = TTY::Gemspec.new
232
+ gemspec.read(gemspec_path)
233
+ dependencies = ['']
234
+ plugins = TTY::Plugins.new
235
+ plugins.load_from(GEMSPEC_PATH, /^tty-(.*)|pastel|thor/)
236
+
237
+ plugins.each do |plugin|
238
+ dependency = ' ' * gemspec.pre_var_indent
239
+ dependency << "#{gemspec.var_name}.add_dependency "
240
+ dependency << "\"#{plugin.gem.name}\", "
241
+ dependency << "\"#{plugin.gem.requirements_list.join(', ')}\""
242
+ dependencies << dependency.dup
243
+ end
244
+ dependencies << '' # add extra line
245
+ content = dependencies.join("\n")
246
+
247
+ within_root_path do
248
+ path = app_path.join(gemspec_name)
249
+ generator.inject_into_file(path, content,
250
+ { before: /^\s*spec\.add_development_dependency\s*"bundler.*$/ }
251
+ .merge(file_options))
252
+ end
253
+ end
254
+ end # New
255
+ end # Commands
256
+ end # TTY
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module TTY
5
+ class Gemspec
6
+ # The gemspec content
7
+ attr_accessor :content
8
+
9
+ # The gemspec variable name
10
+ attr_accessor :var_name
11
+
12
+ # The gemspec indentation before variable name
13
+ attr_accessor :pre_var_indent
14
+
15
+ # The gemspec indentation size afater variable name
16
+ attr_accessor :post_var_indent
17
+
18
+ def read(path)
19
+ self.content = ::File.read(path.to_s)
20
+ self.var_name = content.match(/(\w+)\.name/)[1]
21
+ matches = content.match(/^(\s*)#{var_name}(\.name\s*)=.*$/)
22
+ self.pre_var_indent = matches[1].size
23
+ self.post_var_indent = matches[2].size
24
+ end
25
+
26
+ def write(path)
27
+ ::File.write(path, content)
28
+ end
29
+ end # Gemspec
30
+ end # TTY
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ module TTY
5
+ module Licenses
6
+ CUSTOM = 'custom'.freeze
7
+
8
+ LICENSES = {
9
+ 'agplv3' => { name: 'AGPL-3.0',
10
+ desc: 'GNU Affero General Public License v3.0' },
11
+ 'apache' => { name: 'Apache-2.0', desc: 'Apache License 2.0' },
12
+ 'bsd2' => { name: 'BSD-2-Clause',
13
+ desc: 'BSD 2-Clause License (FreeBSD/Simplified)'},
14
+ 'bsd3' => { name: 'BSD-3-Clause',
15
+ desc: 'BSD 3-Clause License (Revised)'},
16
+ 'gplv2' => { name: 'GPL-2.0',
17
+ desc: 'GNU General Public License v2.0' },
18
+ 'gplv3' => { name: 'GPL-3.0',
19
+ desc: 'GNU General Public License v3.0' },
20
+ 'lgplv3' => { name: 'LGPL-3.0',
21
+ desc: 'GNU Lesser General Public License v3.0' },
22
+ 'mit' => { name: 'MIT', desc: 'MIT License' },
23
+ 'mplv2' => { name: 'MPL-2.0', desc: 'Mozilla Public License 2.0' }
24
+ }.freeze
25
+
26
+ def licenses
27
+ LICENSES
28
+ end
29
+
30
+ def license_identifiers
31
+ licenses.values.map { |key, val| val if key == :name }
32
+ end
33
+ end # Licenses
34
+ end # TTY
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module TTY
5
+ module PathHelpers
6
+ # The root path of the app running this command
7
+ #
8
+ # @return [Pathname]
9
+ #
10
+ # @api public
11
+ def root_path
12
+ @root_path ||= Pathname.pwd
13
+ end
14
+
15
+ # Execute command within root path
16
+ #
17
+ # @api public
18
+ def within_root_path(&block)
19
+ Dir.chdir(root_path, &block)
20
+ end
21
+
22
+ # Extract name from a path
23
+ #
24
+ # @api public
25
+ def name_from_path(path)
26
+ Pathname(path).basename.to_s
27
+ end
28
+
29
+ # Extract a relative path for the app
30
+ #
31
+ # @api private
32
+ def relative_path_from(root_path, path)
33
+ project_path = Pathname.new(path)
34
+ return project_path if project_path.relative?
35
+ project_path.relative_path_from(root_path)
36
+ end
37
+ end # PathHelpers
38
+ end # TTY
@@ -1,5 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require_relative 'plugins/plugin'
4
+
3
5
  module TTY
4
6
  # A class responsible for managing plugins installation
5
7
  class Plugins
@@ -39,28 +41,34 @@ module TTY
39
41
  @plugins << plugin
40
42
  end
41
43
 
42
- # Find all installed TTY plugins and store them
44
+ # Loads gemspec from a file and registers gems matching pattern.
43
45
  #
44
- # @param [String] :lib_name
46
+ # @param [String|Pathname] gemspec_path
47
+ # the path to gemspec
48
+ # @param [Regex] pattern
49
+ # the pattern to match gems by
45
50
  #
46
- # @return [self]
51
+ # @example
52
+ # plugins.load_from('foo.gemspec', /tty-(.*)/)
47
53
  #
48
- # @api private
49
- def find(lib_name = 'tty')
54
+ # @api public
55
+ def load_from(gemspec_path, pattern)
50
56
  Gem.refresh
51
- spec = Gem::Specification.find_by_name(lib_name)
52
- spec.runtime_dependencies.each do |gem|
53
- next unless gem.name =~ /^#{lib_name}/
54
- plugin_name = gem.name[/^#{lib_name}-(.*)/]
55
- register(plugin_name, Plugin.new(plugin_name, gem))
57
+ spec = Gem::Specification.load(gemspec_path)
58
+ dependencies = spec.runtime_dependencies.concat(spec.development_dependencies)
59
+ dependencies.each do |gem|
60
+ gem_name = gem.name[pattern]
61
+ next if gem_name.to_s.empty?
62
+ register(gem_name, Plugin.new(gem_name, gem))
56
63
  end
57
64
  self
58
65
  end
59
66
 
60
- # Load all plugins that are not enabled
67
+ # Activate all registered plugins that are not already enabled and
68
+ # add lib paths to $LOAD_PATH.
61
69
  #
62
70
  # @api public
63
- def load
71
+ def activate
64
72
  plugins.each do |plugin|
65
73
  plugin.load! unless plugin.enabled?
66
74
  end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'tty-file'
5
+
6
+ require_relative 'path_helpers'
7
+
8
+ module TTY
9
+ class Templater
10
+ include PathHelpers
11
+
12
+ attr_reader :templates
13
+
14
+ def initialize(source_path, target_path)
15
+ @source_path = templates_root_path.join(source_path)
16
+ @target_path = target_path
17
+ @templates = []
18
+ end
19
+
20
+ # The root path for all the templates
21
+ #
22
+ # @api public
23
+ def templates_root_path
24
+ Pathname(__dir__).join('templates')
25
+ end
26
+
27
+ # Add mapping to templates
28
+ #
29
+ # @param [String] source
30
+ # the source template location
31
+ # @param [String] target
32
+ # the target template location
33
+ #
34
+ # @api public
35
+ def add_mapping(source, target)
36
+ @templates << [source, target]
37
+ end
38
+
39
+ # Process templates by injecting vars and moving to location
40
+ #
41
+ # @api private
42
+ def generate(template_options, color_option)
43
+ templates.each do |src, dst|
44
+ source = @source_path.join(src)
45
+ destination = @target_path.join(dst).to_s
46
+ next unless ::File.exist?(source)
47
+ within_root_path do
48
+ TTY::File.copy_file(source, destination,
49
+ { context: template_options }.merge(color_option))
50
+ end
51
+ end
52
+ end
53
+ end # Templater
54
+ end # TTY