利用 Thor 来写 CLI



```
explicitly |ɪkˈsplɪsɪtli|
adverb
①(clearly, precisely)明确地 míngquè de ‹warn, forbid, tell, deny›
②(openly)坦率地 tǎnshuài de ‹talk, admit›
③(graphically)生动地 shēngdòng de ‹depict, display›
```


# 入门

简单说,所有继承自`Thor`类的类都是可以执行的, 而且可以带很多子命令和参数.
在这样的类里, 所有的公开方法都是可以作为第一参数的形式来执行各种子命令的.
最后的效果就是能够像 git 或者 bundler 那样灵活使用.

看简单的例子:
我们创建一个文件: `touch cli`, 然后编辑这个文件, 创建一个类`MyCLI`, 然后继续了`Thor`.

```ruby
class MyCLI < Thor
  desc "hello NAME", "say hello to NAME"
  def hello(name)
    puts "Hello #{name}"
  end
end

MyCLI.start(ARGV)
```

这个时候如果我们使用`ruby`来执行这个文件的话, 我们会发现这样的结果:

```
─> ruby cli                                                                                                                                                               
Commands:
  cli hello NAME      # say hello to NAME
  cli help [COMMAND]  # Describe available commands or one specific command
```

这里需要注意的就是在定义完这个类之后, 它会通过`MyCLI.start(ARGV)`里的一个`start`方法来触发当前文件中所有可以当作子命令来执行的类. 而且它还可以帮助解析从命令行传递的参数作为方法参数使用, 如果当前没有传递任何参数,就会默认输出帮助信息.

如上面的输出:

```
 cli hello NAME      # say hello to NAME
```

我们来把玩一下:

```
─> ruby cli hello "xiang"                                                                                                                                                     
Hello xiang
```

有点酷? 我们继续

# 关于命令的描述
默认 Thor 会使用`desc`来作为短描述使用, 如果想要使用长描述, 可以通过使用`long_desc`来完成.
在 长描述的场合,有时候需要强制换行, 可以通过如 markdown 一样的方式 用`\x5` 来实现.

```
class MyCLI < Thor
  desc "hello NAME", "say hello to NAME"
  long_desc <<-LONGDESC
    `cli hello` will print out a message to a person of your
    choosing.
 
    You can optionally specify a second parameter, which will print
    out a from message as well.
 
    > $ cli hello "Yehuda Katz" "Carl Lerche"
    \x5> from: Carl Lerche
  LONGDESC
  def hello(name, from=nil)
    puts "from: #{from}" if from
    puts "Hello #{name}"
  end
end
```

#命令选修和标识

Thor 已经实现了轻松使用命令的选项和标识:

```
class MyCLI < Thor
  desc "hello NAME", "say hello to NAME"
  option :from
  def hello(name)
    puts "from: #{options[:from]}" if options[:from]
    puts "Hello #{name}"
  end
end
```

这个时候,可以这样玩:

```

$ ruby ./cli hello --from "Carl Lerche" Yehuda
from: Carl Lerche
Hello Yehuda
 
$ ruby ./cli hello Yehuda --from "Carl Lerche"
from: Carl Lerche
Hello Yehuda
 
$ ruby ./cli hello Yehuda --from="Carl Lerche"
from: Carl Lerche
Hello Yehuda
```

但是一般来说,所有的参数都是作为字符串来使用的, 不过也可以这样:

```
class MyCLI < Thor
  option :from
  option :yell, :type => :boolean
  desc "hello NAME", "say hello to NAME"
  def hello(name)
    output = []
    output << "from: #{options[:from]}" if options[:from]
    output << "Hello #{name}"
    output = output.join("\n")
    puts options[:yell] ? output.upcase : output
  end
end
```

这样玩:

```
$ ./cli hello --yell Yehuda --from "Carl Lerche"
FROM: CARL LERCHE
HELLO YEHUDA
 
$ ./cli hello Yehuda --from "Carl Lerche" --yell
FROM: CARL LERCHE
HELLO YEHUDA
```

同样,还有必选参数选修哦

```
class MyCLI < Thor
  option :from, :required => true
  option :yell, :type => :boolean
  desc "hello NAME", "say hello to NAME"
  def hello(name)
    output = []
    output << "from: #{options[:from]}" if options[:from]
    output << "Hello #{name}"
    output = output.join("\n")
    puts options[:yell] ? output.upcase : output
  end
end

```
这样的话,如果没有`from`选修:

```
$ ./cli hello Yehuda
No value provided for required options '--from'
```
下面是对于选修的解释:

```
:desc: A description for the option. When printing out full usage for a command using cli help hello, this description will appear next to the option.
:banner: The short description of the option, printed out in the usage description. By default, this is the upcase version of the flag (from=FROM).
:required: Indicates that an option is required
:default: The default value of this option if it is not provided. An option cannot be both :required and have a :default.
:type: :string, :hash, :array, :numeric, or :boolean
:aliases: A list of aliases for this option. Typically, you would use aliases to provide short versions of the option.

```

另外, 可以通过`options`这个参数来一次性搞定所有选修参数

```
class MyCLI < Thor
  desc "hello NAME", "say hello to NAME"
  options :from => :required, :yell => :boolean
  def hello(name)
    output = []
    output << "from: #{options[:from]}" if options[:from]
    output << "Hello #{name}"
    output = output.join("\n")
    puts options[:yell] ? output.upcase : output
  end
end
```