Cocoapods 命令解析器 CLAide

1,892 阅读3分钟

pod 命令解析是用 CLAide 来实现的,在之前的文章Ruby和Cocoapods文章合集中,我们介绍了使用VSCode来调试Cocoapods源码。

CLAide命令解析

我们在调试工程中,通过pod install --clean-install --project-directory=${workspaceRoot}/TestLibrary来感受下,pod 命令是如何解析的。launch.json如下所示

{
    "configurations": [
        {
            "name": "Debug CocoaPods Plugin",
            "showDebuggerOutput": true,
            "type": "Ruby",
            "request": "launch",
            "useBundler": true,
            "cwd": "${workspaceRoot}/TestLibrary", // pod 命令执行的路径
            "program": "${workspaceRoot}/CocoaPods/bin/pod",
            "args": ["install", "--clean-install", "--project-directory=${workspaceRoot}/TestLibrary"], // `pod` 命令的参数
      }
  ]
}

我们摁下F5进入调试环境,解析函数的函数调用栈如图所示:

截屏2021-09-09 下午10.55.11.png

ARGV

我们传入的是一个数组 ["install", "--clean-install", "--project-directory=${workspaceRoot}/TestLibrary"]

首先会将其包装为 ARGV对象。 在其初始化方法里面,来解析参数数组。

# @param [Array<#to_s>] argv
# A list of parameters.
#
def initialize(argv)
    @entries = Parser.parse(argv)
end

截屏2021-09-09 下午11.00.00.png

参数的三种类型

CALide定义了三种参数类型:

截屏2021-09-09 下午11.05.03.png

option:可选参数, 以 --开头,且包含=的参数。

flag: 限定为 bool,类型的option参数 以 --开头,且不包含=的参数。

arg: 普通的实参。 所谓的实参就是直接跟在命令后面,且不带任何 -- 修饰字符。

参数解析后,格式如下

[    [:arg, "install"],
    [:flag, "clean-install"],
    [:option, "project-directory","${workspaceRoot}/TestLibrary"]
]

CLAide:ARGV参数处理

arguments

新建一个调试工程,在Gemfile中引入 CLAide

source 'https://rubygems.org'
gem 'ruby-debug-ide'
gem 'debase'
gem 'claide'

我们自定义一个CLAide::ARGV对象:

require 'claide'
argv = CLAide::ARGV.new(['tea','green','--no-milk', '--sweetener=honey'])
puts argv.arguments 

输出
tea 
green

上面参数的含义我们定义为:来一杯 绿茶不加牛奶甜度选择为蜂蜜等级

arguements:获取所有的普通参数。

shift_argument

oneARGU = argv.shift_argument
puts "oneARGU: #{oneARGU}" # tea
twoARGU = argv.shift_argument
puts "twoARGU: #{twoARGU}" # green
threeARGU = argv.shift_argument # nil
puts "threeARGU: #{threeARGU}"

输出结果:
oneARGU: tea 
twoARGU: green
threeARGU:

shift_argument: 获取第一个普通参数,并在entries元组中移除。

flag?

flagMilk1 = argv.flag?('milk')  # false
puts "flagMilk1: #{flagMilk1}"

flagMilk2 = argv.flag?('milk') # nil
puts "flagMilk2: #{flagMilk2}"


输出结果:
flagMilk1: false 
flagMilk2:

flag?(): 返回一个bool, 如果 以 --no-开头,返回false, 并在entries元组中移除。

option

option1 = argv.option('sweetener') # honey
puts "option1 #{option1}"

option2 = argv.option('sweetener') # nil
puts "option2 #{option2}"

输出结果:
option1 honey 
option2

option():获取可选参数,以keyvalue的形式获取可选参数。并在entries元组中移除。

自制饮料售卖机

需求:提供两种饮料teacoffee:

选择tea时,需选择 红茶,绿茶,黑茶,其中的一种,可选择是否加冰。

选择tea或者coffe,都可以选择是否添加牛奶,可以选择甜度(加糖或者加蜂蜜)

创建Gem工程

1, 我们创建一个bundle gem BeverageMaker。我们将gemspec文件修改成如下所示

require_relative "lib/BeverageMaker/version"

\


Gem::Specification.new do |spec|

    spec.name = "BeverageMaker"
    spec.version = BeverageMaker::VERSION
    spec.authors = ["LYC"]
    spec.email = ["1260197127@qq.com"]
    spec.summary = "BeverageMaker"
    spec.description = "BeverageMaker"
    spec.homepage = "http://www.baidu.com"
    spec.license = "MIT"
    spec.required_ruby_version = ">= 2.4.0"
    # Specify which files should be added to the gem when it is released.
    # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
    spec.files = Dir.chdir(File.expand_path(__dir__)) do
    `git ls-files -z`.split("\x0").reject do |f|
        (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})

       end
    end
    spec.bindir = "bin"
    spec.executables = "beverage-maker"
    spec.require_paths = ["lib"]
    spec.add_runtime_dependency 'claide'
    spec.add_runtime_dependency 'colored2'
end

2, 创建 launch.json

{"configurations": [
{
    "name": "Debug CocoaPods Plugin",
    "showDebuggerOutput": true,
    "type": "Ruby",
    "request": "launch",
    "useBundler": true,
    "cwd": "${workspaceRoot}", //
    "program": "${workspaceRoot}/bin/beverage-maker", // 入口
    "args": [
        "tea", "green", "--no-milk", "--sweetner=honey" // 参数
    ]
    }]
}

3,核心代码 Tea.rb:

module BeverageMaker
    class Tea < Command
        self.summary = 'Drink based on cured leaves'
        self.description = <<-DESC
             An aromatic beverage commonly prepared by pouring boiling hot water over cured leaves of the Camellia sinensis plant.The following flavors are available: black, green, oolong, and white.

        DESC
        
        # tea 后面 要跟一个 FLAVOR 参数
        self.arguments = [
            # represent individual arguments to present to
            # the command help banner
            # def initialize(names, required, repeatable = false)
            CLAide::Argument.new('FLAVOR',true)
        ]
        # 可选参数 help banner
        def self.options
            [['--iced','the ice-tea version']].concat(super)
        end
        # 初始化方法
        def initialize(argv)
            # 给 实例变量 flavor 和 iced 赋值
            @flavor = argv.shift_argument
            @iced = argv.flag?('iced')
            super
        end
        # 检查参数是否合理
        def validate!
            super
            # 如果 flavor 变量为空,输出 help banner
            if @flavor.nil?
                help! 'A flavor argument is Required.'
            end
            # @flavor 值: 应该为 black green red 里面的一种
            # %w(black red green) == ["black", "red", "green"]
            unless %w(black red green).include?(@flavor)
            help! "#{@flavor} is not a valid favor"
            end
        end
        
        def run
            super
            puts "* Infuse #{@flavor} tea..."
            sleep 1
            if @iced
                puts '* Cool off...'
            end
            sleep 1
            puts '* Enjoy!'
        end
     end
 end

F5进行命令调试,运行结果如下

截屏2021-09-10 上午12.58.52.png

总结

Cocoapods 使用 CLAide 来解析命令的,它定义了三种参数: :arg,:flag:option。分别通过 shift_argument()flag?()option("key")来读取相对应的值。并且 CLAide还提供了 参数说明模版help banner,来展示 参数规则。

本文Demo地址Git

参考文章:

CocoaPods 命令解析 -CLAide

CLAide