thor 1.3.2 → 1.5.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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +17 -0
- data/README.md +1 -2
- data/lib/thor/actions/file_manipulation.rb +42 -6
- data/lib/thor/actions/inject_into_file.rb +35 -2
- data/lib/thor/base.rb +2 -1
- data/lib/thor/parser/options.rb +1 -1
- data/lib/thor/runner.rb +1 -1
- data/lib/thor/shell/basic.rb +13 -9
- data/lib/thor/shell/color.rb +2 -0
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +34 -1
- data/thor.gemspec +3 -3
- metadata +7 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 83a74d97baca896e3f78dfdcb081c118c8599ecbcf79085cbff299f393ccdd7c
|
|
4
|
+
data.tar.gz: c96fd32b0d35ea099f176a8febae1eb8814bf81548c442eb3a43d05d1c58f407
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e81da702b50b15939c310e1f24b1410bfed9d29364aeaa1972e97c1faf34102853531ee55897358a43914b49351d7a1d213d888b302a24c3d1f779b26bf4d310
|
|
7
|
+
data.tar.gz: bf2139f49455edc3a076a4e2eefd7db7a2b8be46038638f1fc7203f378cffb49317044d0ad51af172b74c3fce163c5a8b8d85c5c99ee8227137475c0632b614a
|
data/CONTRIBUTING.md
CHANGED
|
@@ -13,3 +13,20 @@ Here are some reasons why a pull request may not be merged:
|
|
|
13
13
|
If you would like to help in this process, you can start by evaluating open pull requests against the criteria above. For example, if a pull request does not include specs for new functionality, you can add a comment like: “If you would like this feature to be added to Thor, please add specs to ensure that it does not break in the future.” This will help move a pull request closer to being merged.
|
|
14
14
|
|
|
15
15
|
Include this emoji in the top of your ticket to signal to us that you read this file: 🌈
|
|
16
|
+
|
|
17
|
+
Specs
|
|
18
|
+
-----
|
|
19
|
+
|
|
20
|
+
Ensure that all specs and code linting checks pass before submitting a pull request.
|
|
21
|
+
|
|
22
|
+
To execute the specs locally, run:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bundle exec rspec
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Linting checks are done with RuboCop. To run the linter, use:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle exec rubocop
|
|
32
|
+
```
|
data/README.md
CHANGED
|
@@ -27,10 +27,9 @@ Installation
|
|
|
27
27
|
|
|
28
28
|
Usage and documentation
|
|
29
29
|
-----------------------
|
|
30
|
-
Please see the [wiki][] for basic usage and other documentation on using Thor.
|
|
30
|
+
Please see the [wiki][] for basic usage and other documentation on using Thor.
|
|
31
31
|
|
|
32
32
|
[wiki]: https://github.com/rails/thor/wiki
|
|
33
|
-
[homepage]: http://whatisthor.com/
|
|
34
33
|
|
|
35
34
|
Contributing
|
|
36
35
|
------------
|
|
@@ -242,6 +242,35 @@ class Thor
|
|
|
242
242
|
insert_into_file(path, *(args << config), &block)
|
|
243
243
|
end
|
|
244
244
|
|
|
245
|
+
# Run a regular expression replacement on a file, raising an error if the
|
|
246
|
+
# contents of the file are not changed.
|
|
247
|
+
#
|
|
248
|
+
# ==== Parameters
|
|
249
|
+
# path<String>:: path of the file to be changed
|
|
250
|
+
# flag<Regexp|String>:: the regexp or string to be replaced
|
|
251
|
+
# replacement<String>:: the replacement, can be also given as a block
|
|
252
|
+
# config<Hash>:: give :verbose => false to not log the status, and
|
|
253
|
+
# :force => true, to force the replacement regardless of runner behavior.
|
|
254
|
+
#
|
|
255
|
+
# ==== Example
|
|
256
|
+
#
|
|
257
|
+
# gsub_file! 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
|
|
258
|
+
#
|
|
259
|
+
# gsub_file! 'README', /rake/, :green do |match|
|
|
260
|
+
# match << " no more. Use thor!"
|
|
261
|
+
# end
|
|
262
|
+
#
|
|
263
|
+
def gsub_file!(path, flag, *args, &block)
|
|
264
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
|
265
|
+
|
|
266
|
+
return unless behavior == :invoke || config.fetch(:force, false)
|
|
267
|
+
|
|
268
|
+
path = File.expand_path(path, destination_root)
|
|
269
|
+
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
|
270
|
+
|
|
271
|
+
actually_gsub_file(path, flag, args, true, &block) unless options[:pretend]
|
|
272
|
+
end
|
|
273
|
+
|
|
245
274
|
# Run a regular expression replacement on a file.
|
|
246
275
|
#
|
|
247
276
|
# ==== Parameters
|
|
@@ -267,11 +296,7 @@ class Thor
|
|
|
267
296
|
path = File.expand_path(path, destination_root)
|
|
268
297
|
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
|
269
298
|
|
|
270
|
-
unless options[:pretend]
|
|
271
|
-
content = File.binread(path)
|
|
272
|
-
content.gsub!(flag, *args, &block)
|
|
273
|
-
File.open(path, "wb") { |file| file.write(content) }
|
|
274
|
-
end
|
|
299
|
+
actually_gsub_file(path, flag, args, false, &block) unless options[:pretend]
|
|
275
300
|
end
|
|
276
301
|
|
|
277
302
|
# Uncomment all lines matching a given regex. Preserves indentation before
|
|
@@ -348,7 +373,7 @@ class Thor
|
|
|
348
373
|
end
|
|
349
374
|
|
|
350
375
|
def with_output_buffer(buf = "".dup) #:nodoc:
|
|
351
|
-
raise ArgumentError, "Buffer
|
|
376
|
+
raise ArgumentError, "Buffer cannot be a frozen object" if buf.frozen?
|
|
352
377
|
old_buffer = output_buffer
|
|
353
378
|
self.output_buffer = buf
|
|
354
379
|
yield
|
|
@@ -357,6 +382,17 @@ class Thor
|
|
|
357
382
|
self.output_buffer = old_buffer
|
|
358
383
|
end
|
|
359
384
|
|
|
385
|
+
def actually_gsub_file(path, flag, args, error_on_no_change, &block)
|
|
386
|
+
content = File.binread(path)
|
|
387
|
+
success = content.gsub!(flag, *args, &block)
|
|
388
|
+
|
|
389
|
+
if success.nil? && error_on_no_change
|
|
390
|
+
raise Thor::Error, "The content of #{path} did not change"
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
File.open(path, "wb") { |file| file.write(content) }
|
|
394
|
+
end
|
|
395
|
+
|
|
360
396
|
# Thor::Actions#capture depends on what kind of buffer is used in ERB.
|
|
361
397
|
# Thus CapturableERB fixes ERB to use String buffer.
|
|
362
398
|
class CapturableERB < ERB
|
|
@@ -2,6 +2,38 @@ require_relative "empty_directory"
|
|
|
2
2
|
|
|
3
3
|
class Thor
|
|
4
4
|
module Actions
|
|
5
|
+
WARNINGS = {unchanged_no_flag: "File unchanged! Either the supplied flag value not found or the content has already been inserted!"}
|
|
6
|
+
|
|
7
|
+
# Injects the given content into a file, raising an error if the contents of
|
|
8
|
+
# the file are not changed. Different from gsub_file, this method is reversible.
|
|
9
|
+
#
|
|
10
|
+
# ==== Parameters
|
|
11
|
+
# destination<String>:: Relative path to the destination root
|
|
12
|
+
# data<String>:: Data to add to the file. Can be given as a block.
|
|
13
|
+
# config<Hash>:: give :verbose => false to not log the status and the flag
|
|
14
|
+
# for injection (:after or :before) or :force => true for
|
|
15
|
+
# insert two or more times the same content.
|
|
16
|
+
#
|
|
17
|
+
# ==== Examples
|
|
18
|
+
#
|
|
19
|
+
# insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
|
|
20
|
+
#
|
|
21
|
+
# insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
|
|
22
|
+
# gems = ask "Which gems would you like to add?"
|
|
23
|
+
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
def insert_into_file!(destination, *args, &block)
|
|
27
|
+
data = block_given? ? block : args.shift
|
|
28
|
+
|
|
29
|
+
config = args.shift || {}
|
|
30
|
+
config[:after] = /\z/ unless config.key?(:before) || config.key?(:after)
|
|
31
|
+
config = config.merge({error_on_no_change: true})
|
|
32
|
+
|
|
33
|
+
action InjectIntoFile.new(self, destination, data, config)
|
|
34
|
+
end
|
|
35
|
+
alias_method :inject_into_file!, :insert_into_file!
|
|
36
|
+
|
|
5
37
|
# Injects the given content into a file. Different from gsub_file, this
|
|
6
38
|
# method is reversible.
|
|
7
39
|
#
|
|
@@ -21,8 +53,6 @@ class Thor
|
|
|
21
53
|
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
|
|
22
54
|
# end
|
|
23
55
|
#
|
|
24
|
-
WARNINGS = {unchanged_no_flag: "File unchanged! Either the supplied flag value not found or the content has already been inserted!"}
|
|
25
|
-
|
|
26
56
|
def insert_into_file(destination, *args, &block)
|
|
27
57
|
data = block_given? ? block : args.shift
|
|
28
58
|
|
|
@@ -47,6 +77,7 @@ class Thor
|
|
|
47
77
|
|
|
48
78
|
@replacement = data.is_a?(Proc) ? data.call : data
|
|
49
79
|
@flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
|
|
80
|
+
@error_on_no_change = @config.fetch(:error_on_no_change, false)
|
|
50
81
|
end
|
|
51
82
|
|
|
52
83
|
def invoke!
|
|
@@ -59,6 +90,8 @@ class Thor
|
|
|
59
90
|
if exists?
|
|
60
91
|
if replace!(/#{flag}/, content, config[:force])
|
|
61
92
|
say_status(:invoke)
|
|
93
|
+
elsif @error_on_no_change
|
|
94
|
+
raise Thor::Error, "The content of #{destination} did not change"
|
|
62
95
|
elsif replacement_present?
|
|
63
96
|
say_status(:unchanged, color: :blue)
|
|
64
97
|
else
|
data/lib/thor/base.rb
CHANGED
|
@@ -13,8 +13,9 @@ class Thor
|
|
|
13
13
|
autoload :RakeCompat, File.expand_path("rake_compat", __dir__)
|
|
14
14
|
autoload :Group, File.expand_path("group", __dir__)
|
|
15
15
|
|
|
16
|
-
# Shortcuts for help.
|
|
16
|
+
# Shortcuts for help and tree commands.
|
|
17
17
|
HELP_MAPPINGS = %w(-h -? --help -D)
|
|
18
|
+
TREE_MAPPINGS = %w(-t --tree)
|
|
18
19
|
|
|
19
20
|
# Thor methods that should not be overwritten by the user.
|
|
20
21
|
THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
|
data/lib/thor/parser/options.rb
CHANGED
|
@@ -144,7 +144,7 @@ class Thor
|
|
|
144
144
|
def check_exclusive!
|
|
145
145
|
opts = @assigns.keys
|
|
146
146
|
# When option A and B are exclusive, if A and B are given at the same time,
|
|
147
|
-
# the
|
|
147
|
+
# the difference of argument array size will decrease.
|
|
148
148
|
found = @exclusives.find{ |ex| (ex - opts).size < ex.size - 1 }
|
|
149
149
|
if found
|
|
150
150
|
names = names_to_switch_names(found & opts).map{|n| "'#{n}'"}
|
data/lib/thor/runner.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require_relative "../thor"
|
|
2
2
|
require_relative "group"
|
|
3
3
|
|
|
4
|
-
require "yaml"
|
|
5
4
|
require "digest/sha2"
|
|
6
5
|
require "pathname"
|
|
7
6
|
|
|
@@ -195,6 +194,7 @@ private
|
|
|
195
194
|
def thor_yaml
|
|
196
195
|
@thor_yaml ||= begin
|
|
197
196
|
yaml_file = File.join(thor_root, "thor.yml")
|
|
197
|
+
require "yaml"
|
|
198
198
|
yaml = YAML.load_file(yaml_file) if File.exist?(yaml_file)
|
|
199
199
|
yaml || {}
|
|
200
200
|
end
|
data/lib/thor/shell/basic.rb
CHANGED
|
@@ -311,13 +311,11 @@ class Thor
|
|
|
311
311
|
end
|
|
312
312
|
|
|
313
313
|
def show_diff(destination, content) #:nodoc:
|
|
314
|
-
diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u"
|
|
315
|
-
|
|
316
314
|
require "tempfile"
|
|
317
|
-
Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
|
|
315
|
+
Tempfile.open(File.basename(destination), File.dirname(destination), binmode: true) do |temp|
|
|
318
316
|
temp.write content
|
|
319
317
|
temp.rewind
|
|
320
|
-
system
|
|
318
|
+
system(*diff_tool, destination, temp.path)
|
|
321
319
|
end
|
|
322
320
|
end
|
|
323
321
|
|
|
@@ -369,19 +367,25 @@ class Thor
|
|
|
369
367
|
|
|
370
368
|
def merge(destination, content) #:nodoc:
|
|
371
369
|
require "tempfile"
|
|
372
|
-
Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
|
|
370
|
+
Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination), binmode: true) do |temp|
|
|
373
371
|
temp.write content
|
|
374
372
|
temp.rewind
|
|
375
|
-
system
|
|
373
|
+
system(*merge_tool, temp.path, destination)
|
|
376
374
|
end
|
|
377
375
|
end
|
|
378
376
|
|
|
379
377
|
def merge_tool #:nodoc:
|
|
380
|
-
@merge_tool ||=
|
|
378
|
+
@merge_tool ||= begin
|
|
379
|
+
require "shellwords"
|
|
380
|
+
Shellwords.split(ENV["THOR_MERGE"] || "git difftool --no-index")
|
|
381
|
+
end
|
|
381
382
|
end
|
|
382
383
|
|
|
383
|
-
def
|
|
384
|
-
|
|
384
|
+
def diff_tool #:nodoc:
|
|
385
|
+
@diff_cmd ||= begin
|
|
386
|
+
require "shellwords"
|
|
387
|
+
Shellwords.split(ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u")
|
|
388
|
+
end
|
|
385
389
|
end
|
|
386
390
|
end
|
|
387
391
|
end
|
data/lib/thor/shell/color.rb
CHANGED
data/lib/thor/version.rb
CHANGED
data/lib/thor.rb
CHANGED
|
@@ -625,7 +625,7 @@ class Thor
|
|
|
625
625
|
# alias name.
|
|
626
626
|
def find_command_possibilities(meth)
|
|
627
627
|
len = meth.to_s.length
|
|
628
|
-
possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort
|
|
628
|
+
possibilities = all_commands.reject {|k, v| v.is_a?(HiddenCommand) }.merge(map).keys.select { |n| meth == n[0, len] }.sort
|
|
629
629
|
unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
|
|
630
630
|
|
|
631
631
|
if possibilities.include?(meth)
|
|
@@ -671,4 +671,37 @@ class Thor
|
|
|
671
671
|
self.class.help(shell, subcommand)
|
|
672
672
|
end
|
|
673
673
|
end
|
|
674
|
+
|
|
675
|
+
map TREE_MAPPINGS => :tree
|
|
676
|
+
|
|
677
|
+
desc "tree", "Print a tree of all available commands"
|
|
678
|
+
def tree
|
|
679
|
+
build_command_tree(self.class, "")
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
private
|
|
683
|
+
|
|
684
|
+
def build_command_tree(klass, indent)
|
|
685
|
+
# Print current class name if it's not the root Thor class
|
|
686
|
+
unless klass == Thor
|
|
687
|
+
say "#{indent}#{klass.namespace || 'default'}", :blue
|
|
688
|
+
indent = "#{indent} "
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
# Print all commands for this class
|
|
692
|
+
visible_commands = klass.commands.reject { |_, cmd| cmd.hidden? || cmd.name == "help" }
|
|
693
|
+
commands_count = visible_commands.count
|
|
694
|
+
visible_commands.sort.each_with_index do |(command_name, command), i|
|
|
695
|
+
description = command.description.split("\n").first || ""
|
|
696
|
+
icon = i == (commands_count - 1) ? "└─" : "├─"
|
|
697
|
+
say "#{indent}#{icon} ", nil, false
|
|
698
|
+
say command_name, :green, false
|
|
699
|
+
say " (#{description})" unless description.empty?
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
# Print all subcommands (from registered Thor subclasses)
|
|
703
|
+
klass.subcommand_classes.each do |_, subclass|
|
|
704
|
+
build_command_tree(subclass, indent)
|
|
705
|
+
end
|
|
706
|
+
end
|
|
674
707
|
end
|
data/thor.gemspec
CHANGED
|
@@ -9,14 +9,14 @@ Gem::Specification.new do |spec|
|
|
|
9
9
|
spec.licenses = %w(MIT)
|
|
10
10
|
spec.authors = ["Yehuda Katz", "José Valim"]
|
|
11
11
|
spec.email = "ruby-thor@googlegroups.com"
|
|
12
|
-
spec.homepage = "
|
|
12
|
+
spec.homepage = "https://github.com/rails/thor"
|
|
13
13
|
spec.description = "Thor is a toolkit for building powerful command-line interfaces."
|
|
14
14
|
spec.summary = spec.description
|
|
15
15
|
|
|
16
16
|
spec.metadata = {
|
|
17
17
|
"bug_tracker_uri" => "https://github.com/rails/thor/issues",
|
|
18
18
|
"changelog_uri" => "https://github.com/rails/thor/releases/tag/v#{Thor::VERSION}",
|
|
19
|
-
"documentation_uri" => "
|
|
19
|
+
"documentation_uri" => "https://github.com/rails/thor/wiki",
|
|
20
20
|
"source_code_uri" => "https://github.com/rails/thor/tree/v#{Thor::VERSION}",
|
|
21
21
|
"wiki_uri" => "https://github.com/rails/thor/wiki",
|
|
22
22
|
"rubygems_mfa_required" => "true",
|
|
@@ -29,5 +29,5 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
spec.executables = %w(thor)
|
|
30
30
|
spec.require_paths = %w(lib)
|
|
31
31
|
|
|
32
|
-
spec.add_development_dependency "bundler", ">= 1.0"
|
|
32
|
+
spec.add_development_dependency "bundler", ">= 1.0"
|
|
33
33
|
end
|
metadata
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: thor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yehuda Katz
|
|
8
8
|
- José Valim
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: bundler
|
|
@@ -18,9 +17,6 @@ dependencies:
|
|
|
18
17
|
- - ">="
|
|
19
18
|
- !ruby/object:Gem::Version
|
|
20
19
|
version: '1.0'
|
|
21
|
-
- - "<"
|
|
22
|
-
- !ruby/object:Gem::Version
|
|
23
|
-
version: '3'
|
|
24
20
|
type: :development
|
|
25
21
|
prerelease: false
|
|
26
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -28,9 +24,6 @@ dependencies:
|
|
|
28
24
|
- - ">="
|
|
29
25
|
- !ruby/object:Gem::Version
|
|
30
26
|
version: '1.0'
|
|
31
|
-
- - "<"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '3'
|
|
34
27
|
description: Thor is a toolkit for building powerful command-line interfaces.
|
|
35
28
|
email: ruby-thor@googlegroups.com
|
|
36
29
|
executables:
|
|
@@ -80,17 +73,16 @@ files:
|
|
|
80
73
|
- lib/thor/util.rb
|
|
81
74
|
- lib/thor/version.rb
|
|
82
75
|
- thor.gemspec
|
|
83
|
-
homepage:
|
|
76
|
+
homepage: https://github.com/rails/thor
|
|
84
77
|
licenses:
|
|
85
78
|
- MIT
|
|
86
79
|
metadata:
|
|
87
80
|
bug_tracker_uri: https://github.com/rails/thor/issues
|
|
88
|
-
changelog_uri: https://github.com/rails/thor/releases/tag/v1.
|
|
89
|
-
documentation_uri:
|
|
90
|
-
source_code_uri: https://github.com/rails/thor/tree/v1.
|
|
81
|
+
changelog_uri: https://github.com/rails/thor/releases/tag/v1.5.0
|
|
82
|
+
documentation_uri: https://github.com/rails/thor/wiki
|
|
83
|
+
source_code_uri: https://github.com/rails/thor/tree/v1.5.0
|
|
91
84
|
wiki_uri: https://github.com/rails/thor/wiki
|
|
92
85
|
rubygems_mfa_required: 'true'
|
|
93
|
-
post_install_message:
|
|
94
86
|
rdoc_options: []
|
|
95
87
|
require_paths:
|
|
96
88
|
- lib
|
|
@@ -105,8 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
105
97
|
- !ruby/object:Gem::Version
|
|
106
98
|
version: 1.3.5
|
|
107
99
|
requirements: []
|
|
108
|
-
rubygems_version: 3.
|
|
109
|
-
signing_key:
|
|
100
|
+
rubygems_version: 3.6.7
|
|
110
101
|
specification_version: 4
|
|
111
102
|
summary: Thor is a toolkit for building powerful command-line interfaces.
|
|
112
103
|
test_files: []
|