Gem 分享如何创建一个新 Ruby Gem

xiajian · 2016年11月24日 · 最后由 helapu 回复于 2017年01月25日 · 6563 次阅读
本帖已被管理员设置为精华贴

原发于: http://bbs.yundianjia.net/d/111-2-ruby-gem

写的不好,希望大家吐槽拉。这里贴出的代码,是最初一个版本的,后续的修改,可以参考 https://github.com/xiajian/bbs_uploader,代码没写测试。确实是个硬伤。更多关于 gem 编写的介绍: http://guides.rubygems.org/make-your-own-gem/

如何创建一个 gem,并将其发布到 rubygems 上,让世界感受你的代码中蕴含的洪荒的力量,从此展开征服世界旅程。。。。。

从一个小的功能开始,比如将 论坛中图片上传 七牛 上,这个小的功能,名字就取得通俗一点: bbs_uploader

1 创建一个新的 gem 目录:

bundle gem bbs_uploader

生成如下的目录:

bbs_uploader
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bbs_uploader.gemspec
├── bin
│   ├── console
│   └── setup
└── lib
    ├── bbs_uploader
    │   └── version.rb
    └── bbs_uploader.rb
  1. 编写并完善相关的代码,参考了 http://bbs.yundianjia.net/d/110-- 以及 https://github.com/xiajian/ruby-docx-templater 中的相关实现。

bbs_uploader.gemspec:

# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'bbs_uploader/version'

Gem::Specification.new do |spec|
  spec.name          = "bbs_uploader"
  spec.version       = BbsUploader::VERSION
  spec.authors       = ["xiajian"]
  spec.email         = ["[email protected]"]

  spec.summary       = %q{简单分装七牛上传的图片类}
  spec.description   = %q{简单封装七牛的图片上传,并演示如何创建 gem 的功能}
  spec.homepage      = "https://github.com/xiajian/bbs_uploader"
  spec.license       = "MIT"

  # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
  # delete this section to allow pushing this gem to any host.
  if spec.respond_to?(:metadata)
    spec.metadata['allowed_push_host'] = "https://rubygems.org"
  else
    raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
  end

  spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  spec.bindir        = "bin"
  spec.executables   = ['bbs_uploader']
  spec.require_paths = ["lib"]

  spec.add_dependency "qiniu", "~> 6.8.1"

  spec.add_development_dependency "bundler", "~> 1.10"
  spec.add_development_dependency "rake", "~> 10.0"
end

lib/bbs_uploader.rb

# 参考: http://bbs.yundianjia.net/d/110--
require "qiniu"
require "bbs_uploader/version"

module BbsUploader
  # Your code goes here...
  IMAGE_URL_REGEXP = /http:\/\/.*[jpg|png|jpeg]$/

  class << self
    @@qiniu = {}

    # 初始化全局变量 @@qiniu
    def qiniu=(options)
      @@qiniu = default_options(options)
    end

    def qiniu
      @@qiniu != {} ? @@qiniu : default_options
    end

    def default_options(options = {})
      {
          access_key: "79WvzAxvV-nOEX7m0PERzwR0Lhm-FDHriz2-QdAN",
          secret_key: "4wwnWNU16n9uVK8DHRW6qO61b2gls3aSduHswkvc",
          bucket: "bss-image",
          bucket_domain: "ognvcf5x6.bkt.clouddn.com"
      }.merge(options)
    end

    def upload_image(file_path)
      # 构建鉴权对象
      Qiniu.establish_connection! access_key: self.qiniu[:access_key],
                                  secret_key: self.qiniu[:secret_key]

      key = "bbs_image/#{File.basename(file_path)}"
      put_policy = Qiniu::Auth::PutPolicy.new(
          self.qiniu[:bucket], # 存储空间
          key, # 指定上传的资源名,如果传入 nil,就表示不指定资源名,将使用默认的资源名
          3600 # token 过期时间,默认为 3600 秒,即 1 小时
      )

      uptoken = Qiniu::Auth.generate_uptoken(put_policy)

      # 调用 upload_with_token_2 方法上传
      code, result = Qiniu::Storage.upload_with_token_2(
          uptoken,
          file_path,
          key,
          nil, # 可以接受一个 Hash 作为自定义变量,请参照 http://developer.qiniu.com/article/kodo/kodo-developer/up/vars.html#xvar
          bucket: self.qiniu[:bucket]
      )

      if code == 200
        image_url = "http://#{self.qiniu[:bucket_domain]}/#{result['key']}"

        puts "图片上传成功! \n链接为: #{image_url} \nmarkdown 链接: #{markdown_image_link(image_url)}"

        image_url
      else
        puts '上传图片失败'

        nil
      end
    rescue => e
      puts "上传图片发生异常: #{e}"

      nil
    end

    def markdown_image_link(image_url)
      if image_url && image_url.is_a?(String) && IMAGE_URL_REGEXP.match(image_url)
        "![](#{image_url})"
      else
        ''
      end
    end

    # 根据目录下指定目录下的文件,将文件上传到七牛上
    #
    # @note 注意文件需要按照文件目录的自然顺序
    def generate_markdown_format(dir)
      content = ''
      files = []

      `ls #{dir}`.split('\n').map { |filename| files << "#{dir}/#{filename}" }

      files.each do |file|
        content <<  markdown_image_link(upload_image(file)) + "\n"
      end

      puts content

      content
    end
  end
end

bin/bbs_uploader

#!/usr/bin/env ruby

require 'json'
require 'psych'
require 'optparse'
require 'bundler/setup'
require 'bbs_uploader'

options = {}

OptionParser.new do |opts|
  opts.banner = "Usage: bbs_uploader [options]"

  opts.on_tail('-h', '--help', 'Show this message') do
    puts opts
    exit
  end

  opts.on_tail('-v', '--version', 'Print version') do
    puts "bbs_uploader #{BbsUploader::VERSION}"
    exit
  end

  opts.on('-i', '--input-file [file]', 'Default: xxx.jpg') do |file|
    unless File.exist? file
      puts "输入的文件 #{file} 不存在,请检查后重试"
      exit
    end

    options[:input_file] = File.absolute_path file
  end
end.parse!

if options[:input_file].nil?
  file = ARGV[0]

  if !file.nil? && File.exists?(file)
    options[:input_file] = File.absolute_path file
  elsif !file.nil?
    puts "输入的文件 #{ARGV[0]} 不存在,请检查后重试"
    exit
  elsif file.nil?
    puts "没有选择上传的图片"
    exit
  end
end

BbsUploader.upload_image options[:input_file]

至此,代码算是也好了,可以在本地测试一下,是否正常:

bin/bbs_uploader /Users/xiajian/Downloads/test.jpg
D, [2016-11-15T13:09:03.811865 #23391] DEBUG -- : Query bss-image hosts Success: {"ttl"=>86400, "http"=>{"io"=>["http://iovip.qbox.me"], "up"=>["http://up.qiniu.com", "http://upload.qiniu.com", "-H up.qiniu.com http://183.136.139.16"]}, "https"=>{"io"=>["https://iovip.qbox.me"], "up"=>["https://up.qbox.me"]}}
图片上传成功!
链接为: http://ognvcf5x6.bkt.clouddn.com/bbs_image/test.jpg
markdown 链接: ![](http://ognvcf5x6.bkt.clouddn.com/bbs_image/test.jpg)
  1. 在 github 上新建 一个 同名的版本库,并将代码推送上去。

在项目中执行,将代码推送到远程:

git init
git add -A
git commit -m"first comment"
git remote add origin [email protected]:xiajian/bbs_uploader.git
git push -u origin master
  1. 配置 Rubygems 相关的配置,详情可以参考 http://bbs.yundianjia.net/d/95-rubygem-gem

执行 rake release 发布 gem 包。

$ rake release
bbs_uploader 0.1.0 built to pkg/bbs_uploader-0.1.0.gem.
Tagged v0.1.0.
Pushed git commits and tags.
Pushed bbs_uploader 0.1.0 to rubygems.org.

rubygems 上项目的地址: https://rubygems.org/gems/bbs_uploader

此刻,过个 30 分钟(taobao 同步 Rubygems 的时间间隔的 2 倍),就可以 gem install bbs_uploader

使用指南:

命令行使用:

使用指南:

Usage: bbs_uploader [options]
    -i, --input-file [file]          Default: xxx.jpg
    -h, --help                       Show this message
    -v, --version                    Print version

调用实例:

bbs_uploader ~/Downloads/test.jpg
D, [2016-11-15T16:31:52.906672 #30130] DEBUG -- : Query bss-image hosts Success: {"ttl"=>86400, "http"=>{"io"=>["http://iovip.qbox.me"], "up"=>["http://up.qiniu.com", "http://upload.qiniu.com", "-H up.qiniu.com http://183.136.139.16"]}, "https"=>{"io"=>["https://iovip.qbox.me"], "up"=>["https://up.qbox.me"]}}
图片上传成功!
链接为: http://ognvcf5x6.bkt.clouddn.com/bbs_image/test.jpg
markdown 链接: ![](http://ognvcf5x6.bkt.clouddn.com/bbs_image/test.jpg)

编程使用:

BbsUploader.qiniu = {
  access_key: 'your access key',
  secret_key: 'your secret key',
  bucket: "your bucket",
  bucket_domain: "your application domain"
}

BbsUploader.upload_image '~/Downloads/test.jpg'

至此,全文完!!!!

我比较关心的是,LZ 的 ID 是真名么。。。

#1 楼 @gyorou 真是的关注点特别啊。是的,真实姓名啊。

huacnlee 将本帖设为了精华贴。 11月25日 10:06

#3 楼 @gyorou 所谓,取个贱名好养活

惊了,我才知道搭建 gem 原来也是有脚手架工具了,每次都是手动一个个文件建出来的 0.0

吐槽几点和 gem 本身相关的内容:

  • 软件包的可执行目录,现在推荐使用 bundle gem 默认生成时的 exe,而不是 bin,理由可以参见这篇博文:https://bundler.io/blog/2015/03/20/moving-bins-to-exe.html
  • allowed_push_host 这个字段目的是支持私有仓库,要么直接删除这几行代码,要么将 rubygems.org 加进去,何必弄一半,强迫症看着心慌
  • add_dependency 中的依赖最好限定版本,毕竟 gem 没有 Gemfile.lock 文件保障

PS:个人比较推荐看 rubygems 官方的 guides,内容还是比较齐全的

#7 楼 @0x005a

  1. 可执行文件的目录,如果是 exe,不是还要创建一个新的目录吗,我觉得,我上面的那个比较的简单。
  2. allowed_push_host 的我改一下。
  3. 版本依赖,我也加一下

谢谢。此外,我写这个,也就是简单的介绍的一下。你说的官方的 guides,我加一下。

这个 ID 厉害了

#10 楼 @easonlovewan 我后来,也接受了,这个 id 好处在于,简单好记,朗朗上口。

自己碰到一个小问题 分享给大家

Gem::Specification.new do |spec|
  spec.files    = spec.files         = `git ls-files -z`.split("\x0").reject do |f|
    f.match(%r{^(test|spec|features)/})
  end

这是默认的 gemspec 设置 所有的源代码要通过 git add 才可以

git ls-files -z

可以在项目下运行它便知道 这是列出项目下加入 git 管理的文件

需要 登录 后方可回复, 如果你还没有账号请 注册新账号