作者:小白蒋,个人博客:www.nihao070.cn
答:Thor是构建强大命令行接口的工具箱。Bundler, Vagrant, Rails以及其他的一些项目使用了Thor构建其命令行工具。bundler用来处理Ruby项目的版本依赖,Vagrant用来管理虚拟机运行环境,Rails是web开发框架。
Thor is a toolkit for building powerful command-line interfaces. It is used in Bundler, Vagrant, Rails and others.
一个简单的Thor类公开带有许多子命令的可执行文件,比如git或bundler。在Thor类中,公共方法变成命令。
新建一个MyCLI.rb文件
1 require "thor"
2
3 class MyCLI < Thor
4 desc "hello NAME", "say hello to NAME"
5 def hello(name)
6 puts "hi #{
name}"
7 end
8 end
9
10 MyCLI.start(ARGV)
~
在这里,你想执行,上面定义的hello方法就得在命令中输入,然后hello 命令后面在跟参数name
ruby MyCLI hello mary
输出:
hi Mary
1 require "thor"
2
3 class MyCLI < Thor
4 desc "hello NAME", "say hello to NAME"
5 def hello(name, from=nil)
6 puts "from: #{
from}" if from
7 puts "hello #{
name}"
8 end
9 end
10
11 MyCLI.start(ARGV)
执行ruby MyCLI.rb hello mary bos
, from参数如果不传递就是nil(空),如果传递就打印出from:bos
from: bos
hello mary
①在许多情况下,您需要在较长的使用说明中提供较长的描述。在这种情况下,可以使用long_desc指定更长的使用指令。
②默认情况下,较长的描述以终端的大小包装行,并使用一个换行符将行分组在一起,就像Markdown一样。还可以在行首使用\x5转义序列强制行之间进行一次硬换行。**
1 require "thor"
2
3 class MyCLI < Thor
4 desc "hello NAME", "say hello to NAME"
5 long_desc <<-LONGDESC
6 `cli hello` will print out a message to a person of your
7 choosing.
8
9 You can optionally specify a second parameter, which will print
10 out a from message as well.
11
12 > $ cli hello "Yehuda Katz" "Carl Lerche"
13
14 \x5> from: Carl Lerche
15 LONGDESC
16 def hello(name, from=nil)
17 puts "from: #{
from}" if from
18 puts "Hello #{
name}"
19 end
20 end
21
22 MyCLI.start(ARGV)
执行跟上面命令一样
Thor makes it easy to specify options and flags as metadata about a Thor command:
1 require "thor"
2
3 class MyCLI < Thor
4 desc "hello NAME", "say hello to NAME"
5 option :from
6 def hello(name)
7 puts "from: #{
options[:from]}" if options[:from]
8 puts "Hello #{
name}"
9 end
10 end
11
12 MyCLI.start(ARGV)
执行:ruby My2.rb hello --from beijing mary
或者可以用ruby My2.rb hello beijing --from mary
或者ruby My2.rb hello beijing --from=mary
输出:
from: beijing
Hello mary
By default, options are Strings, but you can specify an alternate type for any options:
1 require 'thor'
2
3 class MyCLI < Thor
4 option :from
5 option :yell, :type => :boolean
6 desc "hello NAME", "say hello to NAME"
7 def hello(name)
8 output = []
9 output << "from: #{
options[:from]}" if options[:from]
10 output << "Hello #{
name}"
11 output = output.join("\n")
12 puts options[:yell] ? output.upcase : output
13 end
14 end
15
16 MyCLI.start(ARGV)
执行ruby My2.rb hello --from beijing maaa --yell
,因为这里yell传递了,所以执行了output.upcase,都输出了大写,如果这里不传递yell,输出全是小写
结果:
FROM: BEIJING
HELLO MAAA
`您还可以指定一个特定的选项是必需的。
1 require 'thor'
2
3 class MyCLI < Thor
4 option :from, :required => true
5 option :yell, :type => :boolean
6 desc "hello NAME", "say hello to NAME"
7 def hello(name)
8 output = []
9 output << "from: #{
options[:from]}" if options[:from]
10 output << "Hello #{
name}"
11 output = output.join("\n")
12 puts options[:yell] ? output.upcase : output
13 end
14 end
15
16 MyCLI.start(ARGV)
意思就是这里from参数必须要传递,否则报错,下面试一下
执行命令ruby My2.rb hello beijing --yell
结果:
No value provided for required options '--from'
执行ruby My2.rb hello mary --from beijing --yell
结果:
FROM: BEIJING
HELLO MARY
You can use a shorthand to specify a number of options at once if you just want to specify the type of the options. You could rewrite the previous example as:
1 require "thor"
2
3 class MyCLI < Thor
4 desc "hello NAME", "say hello to NAME"
5 options :from => :required, :yell => :boolean
6 def hello(name)
7 output = []
8 output << "from: #{
options[:from]}" if options[:from]
9 output << "Hello #{
name}"
10 output = output.join("\n")
11 puts options[:yell] ? output.upcase : output
12 end
13 end
14
15 MyCLI.start
输出ruby My3.rb hello --from beijing jack --yell
结果:
FROM: BEIJING
HELLO JACK
You can specify an option that should exist for the entire class by using class_option. Class options take exactly the same parameters as options for individual commands, but apply across all commands for a class.
1 require "thor"
2
3 class MyCLI < Thor
4 class_option :verbose, :type => :boolean
5
6 desc "hello NAME", "say hello to NAME"
7 options :from => :required, :yell => :boolean
8
9 def hello(name)
10 puts "> saying hello" if options[:verbose]
11 output = []
12 output << "from: #{
options[:from]}" if options[:from]
13 output << "Hello #{
name}"
14 output = output.join("\n")
15 puts options[:yell] ? output.upcase : output
16 puts "> done saying hello" if options[:verbose]
17 end
18
19 desc "goodbye", "say goodbye to the world"
20 def goodbye
21 puts "> saying goodbye" if options[:verbose]
22 puts "Goodbye World"
23 puts "> done saying goodbye" if options[:verbose]
24 end
25 end
26
27 MyCLI.start
执行: ruby My4.rb goodbye --verbose
结果:
> saying goodbye
Goodbye World
> done saying goodbye
As your CLI becomes more complex, you might want to be able to specify a command that points at its own set of subcommands. One example of this is the git remote command, which exposes add, rename, rm, prune, set-head, and so on.
在Thor中,您可以通过创建一个新的Thor类来表示子命令,并从父类指向它来轻松实现这一点。让我们看看如何实现git remote。这个示例是有意简化的。
In Thor, you can achieve this easily by creating a new Thor class to represent the subcommand, and point to it from the parent class. Let’s take a look at how you would implement git remote. The example is intentionally simplified.
1 require 'thor'
2
3 module GitCLI
4 class Remote < Thor
5 desc "add " , "Adds a remote named for the repository at "
6 long_desc <<-LONGDESC
7 Adds a remote named <name> for the repository at <url>. The command git fetch <name> can then be used to create and update
8 remote-tracking branches <name>/<branch>.
9
10 With -f option, git fetch <name> is run immediately after the remote information is set up.
11
12 With --tags option, git fetch <name> imports every tag from the remote repository.
13
14 With --no-tags option, git fetch <name> does not import tags from the remote repository.
15
16 With -t <branch> option, instead of the default glob refspec for the remote to track all branche s under $GIT_DIR/remotes// , a
17 refspec to track only <branch> is created. You can give more than one -t <branch> to track multi ple branches without grabbing all
18 branches.
19
20 With -m <master> option, $GIT_DIR/remotes/<name>/HEAD is set up to point at remote's <master> br anch. See also the set-head
21 command.
22
23 When a fetch mirror is created with --mirror=fetch, the refs will not be stored in the refs/remo tes/ namespace, but rather
24 everything in refs/ on the remote will be directly mirrored into refs/ in the local repository. This option only makes sense in
25 bare repositories, because a fetch would overwrite any local commits.
26
27 When a push mirror is created with --mirror=push, then git push will always behave as if --mirro r was passed.
28 LONGDESC
29 option :t, :banner => ""
30 option :m, :banner => ""
31 options :f => :boolean, :tags => :boolean, :mirror => :string
32 def add(name, url)
33 # implement git remote add
34 end
35
36 desc "rename " , "Rename the remote named to "
37 def rename(old, new)
38 end
39 end
40
41 class Git < Thor
42 desc "fetch [...]" , "Download objects and refs from another repository"
43 options :all => :boolean, :multiple => :boolean
44 option :append, :type => :boolean, :aliases => :a
45 def fetch(respository, *refspec)
46 # implement git fetch here
47 end
48
49 desc "remote SUBCOMMAND ...ARGS", "manage set of tracked repositories"
50 subcommand "remote", Remote
51 end
52
53
54
55 end