一、创建gem模板项目
假设我的gem命名为mytool
执行gem指令:
bundler gem mytool
二、项目开发
1、结构
将在当前目录下获取一个模板项目,结构如下:
mytool
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
│ ├── console
│ └── setup
├── lib
│ ├── mytool
│ │ └── version.rb
│ └── mytool.rb
└── mytool.gemspec
我们查看lib文件夹下面的mytool.rb
require "mytool/version"
module Mytool
class Error < StandardError; end
# Your code goes here...
end
这是入口文件,我们可以在这里添加我们的代码。
如果需要新建其他rb文件,添加在lib文件目录下,并在mytool.rb里引入
require "yournewfile"
2、使gem支持CIL调用
一般我们使用gem安装后,执行通过CIL命令的方式进行调用,这里我们使用thor支持CIL交互接口。
具体可参考 http://whatisthor.com/
下面我们在我们的mytool.rb文件中添加代码:
require "mytool/version"
require "thor"
module Mytool
class Error < StandardError; end
class CLI < Thor
desc "hello_test", "hello_test to test your gem"
long_desc <<-LONGDESC
hello_test to test your gem
action : str, test your gem
LONGDESC
def hello_test(action)
puts "It is do #{action}"
end
end
end
我们新增个CLI的class,继承于Thor。
方法前的desc和long_desc为方法说明,后面使用指令调用时,在控制输入mytool --help hello_test可打印该说明。
完成以上代码,这样,我们就可以通过
Mytool::CLI.start(ARGV)
来接受CIL交互的调用了,
我们把bin/console文件复制一份,重命名mytool,删除console文件
我们把代码加到bin/mytool文件中
#!/usr/bin/env ruby
require "bundler/setup"
require "mytool"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
# require "irb"
# IRB.start(__FILE__)
Mytool::CLI.start(ARGV)
3、修改gemspec文件
该文件描述了该gem包的基本信息,以及指定安装gem包需要的文件,这里主要修改几个地方:
spec.summary = %q{TODO: Write a short summary, because RubyGems requires one.}
spec.description = %q{TODO: Write a longer description or delete this line.}
spec.homepage = "TODO: Put your gem's website or public repo URL here."
spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
summary和description:介绍和描述你这个gem包的用途
homepage:可以指定你上传git后的地址
allowed_push_host:非必须,可注释
source_code_uri:你gem代码所在git地址,这里直接指定为homepage
changelog_uri:日志记录的地址,随便写
bindir:可执行脚本在gem中的路径,这里指定为bin
executables:gem中包含的可执行文件,这些文件在bindr指定的路径下,我们这里指定console文件
更多详细信息参阅:https://guides.rubygems.org/specification-reference/
另外,因为使用了thor,也期望用户可以通过gemfile使用bundler命令安装gem,我们也要加上gem的依赖(如果gem里使用了xcodeproj也要相应加上):
spec.add_runtime_dependency "bundler", "~> 2.0", '>= 2.0.2'
spec.add_runtime_dependency "xcodeproj", "~> 1.0", '>= 1.9.0'
spec.add_runtime_dependency "thor", "~> 1.0", '>= 1.0.1'
修改后文件如下:
require_relative 'lib/mytool/version'
Gem::Specification.new do |spec|
spec.name = "mytool"
spec.version = Mytool::VERSION
spec.authors = ["lph"]
spec.email = ["luph@qq.com"]
spec.summary = "mytool to test my gem"
spec.description = "mytool to test my gem. prinf someting"
spec.homepage = "https://github.com/opensource/mytool"
spec.license = "MIT"
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
# 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('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = "bin"
spec.executables = spec.name
spec.require_paths = ["lib"]
spec.add_runtime_dependency "bundler", "~> 2.0", '>= 2.0.2'
spec.add_runtime_dependency "xcodeproj", "~> 1.0", '>= 1.9.0'
spec.add_runtime_dependency "thor", "~> 1.0", '>= 1.0.1'
end
三、Gem发布
1、本地安装gem包测试
除了把gem发布,我们可以先在本地安装gem包进行测试
在gem工程目录下执行
$ gem build mytool.gemspec
Successfully built RubyGem
Name: mytool
Version: 0.1.0
File: mytool-0.1.0.gem
将在当前目录下生成mytool-0.1.0.gem,安装该gem包
$ gem install mytool-0.1.0.gem
mytool-0.1.0.gem
Successfully installed mytool-0.1.0
Parsing documentation for mytool-0.1.0
Installing ri documentation for mytool-0.1.0
Done installing documentation for mytool after 0 seconds
1 gem installed
这样gem包就直接安装在了本地,下面我们可执行:
$ mytool --help hello_test
Usage:
mytool hello_test
Description:
hello_test to test your gem action : str, test your gem
输出方法说明。
执行:
$ mytool hello_test daydayup
It is do daydayup
成功调用方法。
使用以下指令移除gem包:
gem uninstall mytool
PS:这里的指令mytool本质就是调用了我们bin/mytool的可执行文件,所有刚才才重新命名了文件。
若执行命令报错:
$ mytool hello_test daydayup
Could not find gem 'rake (~> 12.0)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.
我们可使用gem list --local
查看本地已安装的命令有没有rake包和版本
再查看项目Gemfile的配置:
source "https://rubygems.org"
# Specify your gem's dependencies in mytool.gemspec
gemspec
gem "rake", "~> 12.0"
若版本不对说明没有对应版本,可以选择:
1、修改gemfile rake版本号
2、将rake加到mytool.gemspec的开发依赖spec.add_development_dependency 'rake'
重新build gem包安装
3、在工程目录下,bundle install
2、将代码上传git
将工程上传git仓库,作为私有tool提供给有访问权限的人使用。
使用者只需在自己的Gemfile中添加:
source "https://gems.ruby-china.com/"
# gems for xxx
gem 'mytool', git: 'https://github.com/opensource/mytool.git',tag: '0.6.3'
在所在目录中,bundle install
便可安装指定git tag版本的gem包
3、Gemfile发布rubygems网站
$ curl -u tom https://rubygems.org/api/v1/api_key.yaml >
~/.gem/credentials
Enter host password for user 'tom':
设定完之后发布
% gem push mytool-0.1.0.gem
Pushing gem to RubyGems.org...
Successfully registered gem: mytool (0.1.0)
这样任何一个人都可以使用你写的gem了。
三、CocoaPods 插件
CocoaPods 插件本质就是个gem包,或者说cocopod本身就是一个Gem
1、创建插件模板工程
制作pod插件跟gem基本一样
安装pod gem 工具:
gem install cocoapods-plugins
执行:
pod plugins create myplugin
创建插件工程如下:
.
└── cocoapods-myplugin
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── cocoapods-myplugin.gemspec
├── lib
│ ├── cocoapods-myplugin
│ │ ├── command
│ │ │ └── myplugin.rb #Command类,设置命令
│ │ ├── command.rb
│ │ └── gem_version.rb
│ ├── cocoapods-myplugin.rb
│ └── cocoapods_plugin.rb #编写插件逻辑
└── spec
├── command
│ └── myplugin_spec.rb
└── spec_helper.rb
目录结构和gem包模板基本相同。
cocospod插件可支持两种使用方式:
plugin:在podfile中设置plugin 'cocoapods-yyplugin'
pod命令:在终端输入pod自定义命令
2、编写插件
我们这里实现pod hook,在pod install 或者update的时候,打印文本
在lib/cocoapods-myplugin/command下新建cocoapods_test_handler.rb文件,输入:
module CocoapodsMyplugin
class PrinterTest
def printHello
puts "this is myplugin action"
end
end
end
在lib/cocoapods_plugin.rb中增加代码:
require 'cocoapods-myplugin/command'
require 'cocoapods'
require_relative 'cocoapods-myplugin/command/cocoapods_test_handler'
module CocoapodsMyplugin
Pod::HooksManager.register('cocoapods-myplugin', :post_install) do |context|
PrinterTest.new.printHello()
end
Pod::HooksManager.register('cocoapods-myplugin', :post_update) do |context|
PrinterTest.new.printHello()
end
end
以上,我们注册hook钩子,执行内容为printHello打印文本。
这里的context为pod的上下文,你可以使用context.methods打印看看相关方法这里就不展开了。
接着,我们执行一下脚本,重新安装插件
gem uninstall cocoapods-myplugin && gem build cocoapods-myplugin.gemspec && gem install cocoapods-myplugin-0.0.1.gem
PS: 我们可以使用pod plugins installed
查看已安装的pod插件:
Installed CocoaPods Plugins:
- cocoapods-deintegrate : 1.0.5
- cocoapods-disable-podfile-validations : 0.1.1
- cocoapods-generate : 2.1.0
- cocoapods-myplugin : 0.0.1 (post_install and
post_update hooks)
- cocoapods-open : 0.0.8
- cocoapods-plugins : 1.0.0
- cocoapods-repo-update : 0.0.4 (pre_install hook)
- cocoapods-search : 1.0.1
- cocoapods-stats : 1.1.0 (post_install hook)
- cocoapods-trunk : 1.5.0
- cocoapods-try : 1.2.0
然后在Demo工程中修改podfile,新增代码plugin 'cocoapods-yyplugin'
:
platform :ios, '10.0'
plugin 'cocoapods-yyplugin'
source 'https://github.com/CocoaPods/Specs.git'
target 'HeifSDKDemo' do
pod 'heif', '1.0.16'
end
然后我们pod install
,出现如下输出:
Analyzing dependencies
Downloading dependencies
Generating Pods project
Integrating client project
this is myplugin action
说明插件已正常运行
3、自定义pod命令
我们打开lib/cocoapods-myplugin/command/myplugin.rb,看注释,模板已经举例告诉你如何创建一个pod list 的子命令了。这里我们先编写自己的插件命令。
主要修改的地方:
self.summary :简述
self.description:详细模式
def initialize(argv):命令的的初始化方法,在此接收参数
def validate!:用于检查输入实参的有效性,如果校验失败,会通过调用 help! 方法来输出帮助信息
self.arguments:设置接收参数的内容
修改后文件内容如下:
require_relative 'cocoapods_test_handler'
module Pod
class Command
# This is an example of a cocoapods plugin adding a top-level subcommand
# to the 'pod' command.
#
# You can also create subcommands of existing or new commands. Say you
# wanted to add a subcommand to `list` to show newly deprecated pods,
# (e.g. `pod list deprecated`), there are a few things that would need
# to change.
#
# - move this file to `lib/pod/command/list/deprecated.rb` and update
# the class to exist in the the Pod::Command::List namespace
# - change this class to extend from `List` instead of `Command`. This
# tells the plugin system that it is a subcommand of `list`.
# - edit `lib/cocoapods_plugins.rb` to require this file
#
# @todo Create a PR to add your plugin to CocoaPods/cocoapods.org
# in the `plugins.json` file, once your plugin is released.
#
class Myplugin < Command
self.summary = 'Short description of cocoapods-myplugin.'
self.description = <<-DESC
Longer description of cocoapods-myplugin.
DESC
#用于返回该命令的可选项及对应的描述,concat(super)把父类的option拼上
def self.options
[
['--no-milk', 'Don’t add milk to the beverage'], #milk=false
['--sweetener=[sugar|honey]', 'Use one of the available sweeteners'], #sweetener
].concat(super)
end
self.arguments = [
CLAide::Argument.new('ACTION', false, true ),
]
def initialize(argv)
@name = argv.shift_argument # 取第一个参数
@actions = argv.arguments! # 取第一个参数之外剩下的参数(不包括flag、option)
@add_milk = argv.flag?('milk', true) #取bool类型的值,--no-代表false,默认true
@sweetener = argv.option('sweetener') #取option的值
super
end
#用于检查输入实参的有效性,如果校验失败,会通过调用 help! 方法来输出帮助信息
def validate!
super
if @sweetener && !%w(sugar honey).include?(@sweetener)
help! "`#{@sweetener}' is not a valid sweetener."
end
end
def run
if @add_milk
puts '* Adding milk…'
end
if @sweetener
puts "* Adding #{@sweetener}…"
end
puts "action to #{@actions} #{@name}"
CocoapodsMyplugin::PrinterTest.new.printHello()
end
end
end
end
a、option选项
这里我们增加了option选项,通过self.options可设置选项说明。
option:这里增加了–sweetener选项,通过@sweetener = argv.option('sweetener')
获取值。
flag:是一个特殊的option,表示只有bool值。这里 --no-milk是一个特殊的选项,其中–no-表示milk=false。bool选项通过argv.flag?('milk', true)
获取,设置了默认true
b、参数列表
参数说明
self.arguments = [
CLAide::Argument.new('ACTION', false, true ),
]
声明了命令所接受的参数值的Usage输出(就是–help后命令的说明)
我们查看CLAided的构造方法:
module CLAide
class Argument
def initialize(names, required, repeatable = false)
@names = Array(names)
@required = required
@repeatable = repeatable
end
end
- names 就是在 Usage 中输出的 [ACTION …]
- require 表示该 Argument 是否为必传参数,可选参数Usage会用 [ ] 将其包裹起来。也就是命令默认是不需要传 ACTION
- repeatable 表示该 Argument 是否可以重复多次出现,类似可变参数的意思。如果设置为true,那么会在names 的Usage输出信息后面会添加 … 表示该参数为复数参数
参数获取
通过@name = argv.shift_argument
我们可获取参数列表的第一个值
通过@actions = argv.arguments!
获取除第一个参数的之外的其他参数list
注意:这里获取的arguments是不包括option的
例如:我们输入
pod myplugin a b c --sweetener=sugar
@name -> a
@actions -> [b,c]
b、数据验证
我们通过def validate!
方法编写参数校验逻辑,例如这里我们
if @sweetener && !%w(sugar honey).include?(@sweetener)
help! "`#{@sweetener}' is not a valid sweetener."
end
判断option固定的两个可选值,否则就报help!的内容。
完成以上代码后,我们执行一下脚本重新安装插件
gem uninstall cocoapods-myplugin && gem build cocoapods-myplugin.gemspec && gem install cocoapods-myplugin-0.0.1.gem
执行pod自定义命令:
$ pod myplugin asa --sweetener=sugar aasa dss
* Adding milk…
* Adding sugar…
action to ["aasa", "dss"] asa
this is myplugin actio
参考博文:
https://www.jianshu.com/p/5889b25a85dd
https://juejin.cn/post/6871919379391447047