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
@@ -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