From fd0ec642b1d77a3e71e1b544cf41f910cbe044bd Mon Sep 17 00:00:00 2001 From: Tatsuki Sugiura Date: Sat, 12 Mar 2016 05:52:51 +0900 Subject: [PATCH] First version. --- .gitignore | 16 +++ .rspec | 2 + .travis.yml | 4 + Gemfile | 5 + README.md | 36 ++++++ Rakefile | 14 +++ bin/console | 14 +++ bin/setup | 8 ++ exe/osdn | 5 + lib/osdn-cli.rb | 1 + lib/osdn/cli.rb | 219 +++++++++++++++++++++++++++++++++++++ lib/osdn/cli/command/frs_mkdirs.rb | 70 ++++++++++++ lib/osdn/cli/command/frs_upload.rb | 128 ++++++++++++++++++++++ lib/osdn/cli/command/login.rb | 63 +++++++++++ lib/osdn/cli/command/package.rb | 118 ++++++++++++++++++++ lib/osdn/cli/common.rb | 6 + lib/osdn/cli/runner.rb | 121 ++++++++++++++++++++ lib/osdn/cli/version.rb | 5 + osdn-cli.gemspec | 27 +++++ spec/osdn/cli_spec.rb | 11 ++ spec/spec_helper.rb | 2 + 21 files changed, 875 insertions(+) create mode 100644 .gitignore create mode 100644 .rspec create mode 100644 .travis.yml create mode 100644 Gemfile create mode 100644 README.md create mode 100644 Rakefile create mode 100755 bin/console create mode 100755 bin/setup create mode 100755 exe/osdn create mode 100644 lib/osdn-cli.rb create mode 100644 lib/osdn/cli.rb create mode 100644 lib/osdn/cli/command/frs_mkdirs.rb create mode 100644 lib/osdn/cli/command/frs_upload.rb create mode 100644 lib/osdn/cli/command/login.rb create mode 100644 lib/osdn/cli/command/package.rb create mode 100644 lib/osdn/cli/common.rb create mode 100644 lib/osdn/cli/runner.rb create mode 100644 lib/osdn/cli/version.rb create mode 100644 osdn-cli.gemspec create mode 100644 spec/osdn/cli_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98d8eb8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +*~ +*.bak +*.orig +*.rej +.*.sw[po] +.osdn.vars diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..8c18f1a --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--format documentation +--color diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..42960bc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: ruby +rvm: + - 2.2.4 +before_install: gem install bundler -v 1.11.2 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..24ae444 --- /dev/null +++ b/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in osdn-cli.gemspec +gemspec +gem 'rake-hooks' diff --git a/README.md b/README.md new file mode 100644 index 0000000..721daa0 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Osdn::Cli + +Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/osdn/cli`. To experiment with that code, run `bin/console` for an interactive prompt. + +TODO: Delete this and the text above, and describe your gem + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'osdn-cli' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install osdn-cli + +## Usage + +TODO: Write usage instructions here + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/osdn-cli. + diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..43a2314 --- /dev/null +++ b/Rakefile @@ -0,0 +1,14 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" +require 'rake/hooks' +require 'fileutils' + +RSpec::Core::RakeTask.new(:spec) + +before :build do + spec = Bundler.load_gemspec(File.join(File.dirname(__FILE__), 'osdn-cli.gemspec')) + FileUtils.chmod 0644, spec.files + FileUtils.chmod 0755, spec.executables +end + +task :default => :spec diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..0820680 --- /dev/null +++ b/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "osdn/cli" + +# 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 diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/exe/osdn b/exe/osdn new file mode 100755 index 0000000..320c342 --- /dev/null +++ b/exe/osdn @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +require 'osdn/cli' + +cli = OSDN::CLI::Runner.new +cli.run diff --git a/lib/osdn-cli.rb b/lib/osdn-cli.rb new file mode 100644 index 0000000..498c72f --- /dev/null +++ b/lib/osdn-cli.rb @@ -0,0 +1 @@ +require 'osdn/cli' diff --git a/lib/osdn/cli.rb b/lib/osdn/cli.rb new file mode 100644 index 0000000..1b4d2fd --- /dev/null +++ b/lib/osdn/cli.rb @@ -0,0 +1,219 @@ +require "osdn/cli/version" +require "osdn/cli/runner" +require 'getoptlong' +require 'logger' +require 'pathname' +require 'yaml' +require 'fileutils' +require 'osdn-client' +require 'hashie' +require 'json' + +module OSDN + module CLI + @@client_id = "fd573bffcd132857b32a438d3f4b6f575580f286afc94a45e220db244919163e" + @@client_secret = "92b4792000e091668ccf2ac95981c35fd3ef347450a4fde1a9a3344e99100fa1" + def client_id + @@client_id + end + def client_secret + @@client_secret + end + module_function :client_id, :client_secret + + module Command + autoload :Login, 'osdn/cli/command/login' + autoload :Package, 'osdn/cli/command/package' + #autoload :Release, 'osdn/cli/command/release' + #autoload :FrsFile, 'osdn/cli/command/frs_file' + autoload :FrsMkdirs, 'osdn/cli/command/frs_mkdirs' + autoload :FrsUpload, 'osdn/cli/command/frs_upload' + + class Base + def initialize(logger) + @logger = logger + @credential = Hashie::Mash.new + @format = 'pretty' + end + attr_reader :logger + attr_accessor :credential, :format + + def credential_path + Pathname.new(ENV['HOME']) + '.config/osdn/credential.yml' + end + + def load_credential + begin + stat = credential_path.stat() + unless credential_path.owned? + logger.error "Invalid ownership of credential file #{credential_path}, skip loading." + return + end + unless (stat.mode & 0777).to_s(8) == "600" + logger.error "Invalid permission #{(stat.mode & 0777).to_s(8)} of credential file #{credential_path}, skip loading." + return + end + rescue Errno::ENOENT + return + end + logger.debug "Loading credentials from #{credential_path}" + @credential = Hashie::Mash.new(YAML.load_file(credential_path)) + set_client_token + end + + def write_credential + FileUtils.mkdir_p credential_path.dirname, verbose: false + cio = credential_path.open('w', 0600) + YAML.dump(credential.to_hash, cio) + cio.close + end + + def update_token + logger.debug "Checking token expires..." + load_credential + if credential.expires_at > Time.now + 30 + logger.debug "You have valid access token, skip to refresh." + return + end + + logger.debug "Access token has been expired. Refresh access token..." + api = OSDNClient::DefaultApi.new + begin + set_credential api.token(CLI.client_id, CLI.client_secret, grant_type: 'refresh_token', refresh_token: credential.refresh_token) + rescue OSDNClient::ApiError => e + begin + err = JSON.parse(e.response_body) + logger.fatal err["error_description"] + rescue + logger.fatal "Failed to refresh access token." + end + logger.fatal "Please login again." + return + end + logger.debug "Access token refreshed successfully." + end + + def set_credential(token, update_expires = true) + token = Hashie::Mash.new(token.to_hash) + if update_expires + token.expires_at = Time.now + token.expires_in.to_i + end + token.scope = [*token.scope].join(' ').split(' ') + + credential.update token + write_credential + set_client_token + end + + def set_client_token + if credential.access_token && !credential.access_token.empty? + OSDNClient.configure do |config| + config.access_token = credential.access_token + end + end + end + + def load_variables(path = '.', recursive_merge = true) + vars = {} + path = Pathname.new(Dir.getwd) + path + cur_dir = Pathname.new('/') + if recursive_merge + path.each_filename do |d| + cur_dir = cur_dir + d + vf = cur_dir + '.osdn.vars' + vf.exist? or next + begin + logger.debug "Load and merge variables from #{vf}" + vars.update YAML.load_file(vf.to_s) + rescue => e + logger.warn "Failed to load variables from #{vf}; #{e.message}" + end + end + else + begin + path = path+'.osdn.vars' + if path.exist? + logger.debug "Load and merge variables from #{path}" + vars.update YAML.load_file(path) + end + rescue => e + logger.warn "Failed to load variables from #{path}; #{e.message}" + end + end + logger.debug "Variables: #{vars.inspect}" + Hashie::Mash.new(vars) + end + + def write_variables(vars, dir = nil) + path = Pathname.new(dir || '.') + '.osdn.vars' + logger.info "Save variables to #{path}" + vio = path.open('w') + YAML.dump(vars.to_hash, vio) + vio.close + end + + def update_variables(dir, vars) + write_variables(load_variables(dir, false).merge(vars), dir) + end + end + + class Ping < Base + def run + update_token + api = OSDNClient::DefaultApi.new + pp api.ping + end + + def help + puts "#{$0} ping" + end + + def self.description + "Test API request." + end + end + + class Vars < Base + def run + subcommand = ARGV.shift ||'show' + self.send subcommand + end + + def show + name = ARGV.shift + if name + puts load_variables[name] + else + pp load_variables + end + end + + def set + name, value = ARGV.shift, ARGV.shift + if !name || name.empty? + logger.fatal "Missing variable name" + help + exit + end + if !value || value.empty? + logger.fatal "Missing variable value" + help + exit + end + vars = load_variables('.', false) + vars[name] = value + write_variables vars + end + + def help + puts "#{$0} vars show [name] -- Show current variable" + puts "#{$0} vars set -- Save variable to .osdn.vars" + end + + def self.description + "Get/set request environment variable." + end + end + end + end +end diff --git a/lib/osdn/cli/command/frs_mkdirs.rb b/lib/osdn/cli/command/frs_mkdirs.rb new file mode 100644 index 0000000..3da46b8 --- /dev/null +++ b/lib/osdn/cli/command/frs_mkdirs.rb @@ -0,0 +1,70 @@ +require 'pathname' +require 'fileutils' + +module OSDN; module CLI; module Command + class FrsMkdirs < Base + def help + puts "#{$0} frs_mkdirs [opts] [target_dir]" + puts "Options:" + puts " -p --project= Target project (numeric id or name)" + #puts " --package= Target package (numeric id)" + #puts " --release= Target release (numeric id)" + end + + def run + update_token + opts = GetoptLong.new( + [ '--dry-run', '-n', GetoptLong::NO_ARGUMENT ], + [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ], + ) + opts.each do |opt, arg| + case opt + when '--project' + arg.empty? or + @target_proj = arg + end + end + + proj_info = api.get_project target_proj # check project existance + + target_dir = Pathname.new(ARGV.shift || '.') + FileUtils.mkdir_p target_dir + update_variables target_dir, project: target_proj + + logger.debug "Getting Package list..." + packages = api.list_packages target_proj + logger.debug "Making each package directry" + + packages.each do |package| + logger.info "Making directory for package #{package.name}" + pdir = target_dir + package.name + FileUtils.mkdir_p(pdir) + update_variables pdir, package_id: package.id + package.releases.each do |release| + logger.info "Making directory for release #{release.name}" + rdir = pdir + release.name + FileUtils.mkdir_p(rdir) + update_variables rdir, release_id: release.id + end + end + end + + def self.description + "Make directory tree for current project release" + end + + private + def target_proj + @target_proj and return @target_proj + vars = load_variables + vars.project && !vars.project.empty? and + return vars.project + logger.fatal "No target project is specified." + exit + end + + def api + OSDNClient::ProjectApi.new + end + end +end; end; end diff --git a/lib/osdn/cli/command/frs_upload.rb b/lib/osdn/cli/command/frs_upload.rb new file mode 100644 index 0000000..a40ace3 --- /dev/null +++ b/lib/osdn/cli/command/frs_upload.rb @@ -0,0 +1,128 @@ +module OSDN; module CLI; module Command + class FrsUpload < Base + def help + puts "#{$0} frs_upload [opts] [target_dir]" + puts "Options:" + puts " -n --dry-run Do noting (use with global -v to inspect)" + puts " -p --project= Target project (numeric id or name)" + #puts " --package= Target package (numeric id)" + #puts " --release= Target release (numeric id)" + puts " -v --visibility=" + puts " Default visibility for newly created items" + end + + def run + update_token + opts = GetoptLong.new( + [ '--dry-run', '-n', GetoptLong::NO_ARGUMENT ], + [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ], + [ '--release', '-r', GetoptLong::REQUIRED_ARGUMENT ], + [ '--visibility', '-v', GetoptLong::REQUIRED_ARGUMENT ], + ) + opts.each do |opt, arg| + case opt + when '--project' + arg.empty? or + @target_proj = arg + #when '--release' + # arg.empty? or + # @target_release = arg + #when '--package' + # arg.empty? or + # @target_package = arg + when '--visibility' + unless %w(public private hidden).member?(arg) + logger.fatal "Invalid visibility status: #{arg}" + exit + end + @visibility = arg + when '--dry-run' + @dry_run = true + end + end + + @target_dir = Pathname.new(ARGV.shift || '.') + proj_info = api.get_project target_proj # check project existance + + Pathname.glob(@target_dir+'*').each do |pdir| + unless load_variables(pdir).package_id + logger.info "Createing new package '#{pdir.basename}'" + pinfo = api.create_package target_proj, pdir.basename, visibility: @visibility + update_variables pdir, package_id: pinfo.id + $stdout.puts "New package '#{pinfo.name}' has been created; #{pinfo.url}" + end + + Pathname.glob(pdir + '*').each do |rdir| + vars = load_variables(rdir) + rinfo = nil + if vars.release_id + rinfo = api.get_release target_proj, target_package(rdir), target_release(rdir) + else vars.release_id + logger.info "Createing new release '#{rdir.basename}'" + rinfo = nil + if api.respond_to? :create_reelase # TODO: remove, just typo... + rinfo = api.create_reelase target_proj, target_package(rdir), rdir.basename, visibility: @visibility + else + rinfo = api.create_release target_proj, target_package(rdir), rdir.basename, visibility: @visibility + end + update_variables rdir, release_id: rinfo.id + $stdout.puts "New release '#{rinfo.name}' has been created; #{rinfo.url}" + end + + Pathname.glob(rdir + '*').each do |file| + if file.directory? + logger.error "Skip direcotry #{file}" + next + end + + if rinfo.files.find { |f| f.name == file.basename.to_s } + logger.warn "Skip already uploaded file '#{file}'" + else + logger.info "Uploading file #{file} (#{file.size} bytes)" + # TODO: show progress bar! + finfo = api.create_release_file target_proj, target_package(rdir), target_release(rdir), file.open, visibility: @visibility + logger.info "Upload completed." + $stdout.puts "New file '#{file}' has been uploaded; #{finfo.url}" + end + end + end + end + end + + def self.description + "Upload local file tree and create package/release implicitly." + end + + private + def target_proj + @target_proj and return @target_proj + vars = load_variables(@target_dir) + vars.project && !vars.project.empty? and + return vars.project + logger.fatal "No target project is specified." + exit + end + + def target_package(dir) + @target_package and return @target_package + vars = load_variables(dir) + vars.package_id && !vars.package_id.to_s.empty? and + return vars.package_id + logger.fatal "No target package is specified." + exit + end + + def target_release(dir) + @target_release and return @target_release + vars = load_variables(dir) + vars.release_id && !vars.release_id.to_s.empty? and + return vars.release_id + logger.fatal "No target release is specified." + exit + end + + def api + OSDNClient::ProjectApi.new + end + end +end; end; end diff --git a/lib/osdn/cli/command/login.rb b/lib/osdn/cli/command/login.rb new file mode 100644 index 0000000..9d68b06 --- /dev/null +++ b/lib/osdn/cli/command/login.rb @@ -0,0 +1,63 @@ +module OSDN; module CLI; module Command + class Login < Base + def run + logger.debug "Trying login" + scope = %w(profile group group_write) + + auth_url = "https://sf-test-sugi.office.osdn.jp/account/oauth2ui/authorize?client_id=#{CLI.client_id}&state=cli#{Time.now.to_i}&response_type=code&scope=#{scope.join('%20')}" + + launch_brwoser auth_url + puts + authcode = prompt("Type your auth code: ") + puts + if authcode.empty? + logger.error "Empty auth code, login has been canceled." + return + end + + api = OSDNClient::DefaultApi.new + begin + set_credential api.token(CLI.client_id, CLI.client_secret, code: authcode) + rescue OSDNClient::ApiError => e + begin + err = JSON.parse(e.response_body) + logger.fatal err["error_description"] + rescue + logger.fatal "Failed to get access token" + end + return + end + end + + def launch_brwoser(url) + puts "Access follwoing URL to get auth code;\n#{url}" + %w(/usr/bin/xdg-open /usr/bin/X11/xdg-open /usr/local/bin/xdg-open + /usr/bin/x-www-browser /usr/bin/firefox /usr/local/bin/firefox + ).each do |bin| + File.executable?(bin) or next + exec(bin, url) if fork.nil? + return + end + case RUBY_PLATFORM + when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ + exec("start #{url}") if fork.nil? + when /darwin|mac os/ + exec("/usr/bin/open", url) if fork.nil? + end + end + + def prompt(msg = "", newline = false) + require 'readline' + msg += "\n" if newline + Readline.readline(msg, true).to_s.squeeze(" ").strip + end + + def help + puts "#{$0} login" + end + + def self.description + "Login and save access token." + end + end +end; end; end diff --git a/lib/osdn/cli/command/package.rb b/lib/osdn/cli/command/package.rb new file mode 100644 index 0000000..64838bf --- /dev/null +++ b/lib/osdn/cli/command/package.rb @@ -0,0 +1,118 @@ +module OSDN; module CLI; module Command + class Package < Base + def self.help + puts "#{$0} package [opts] [list]" + puts "#{$0} package [opts] create " + puts "#{$0} package [opts] update [name]" + puts "#{$0} package [opts] delete " + puts "Options:" + puts " -f --format= Set output format" + puts " -p --project= Target project (numeric id or name)" + puts " -v --visibility=" + end + + def self.description + "Manipulate release package of project" + end + + def run + update_token + opts = GetoptLong.new( + [ '--format', '-f', GetoptLong::REQUIRED_ARGUMENT ], + [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ], + [ '--visibility', '-v', GetoptLong::REQUIRED_ARGUMENT ], + ) + opts.each do |opt, arg| + case opt + when '--format' + arg == 'json' and + self.format = arg + when '--project' + arg.empty? or + @target_proj = arg + when '--visibility' + unless %w(public private hidden).member?(arg) + logger.fatal "Invalid visibility status: #{arg}" + exit + end + @visibility = arg + end + end + command = ARGV.shift || 'list' + if !command || command.empty? + logger.fatal "subcommand is missing." + help + return + end + if self.respond_to? command + self.send command + else + logger.fatal "Invalid subcommand: #{command}" + help + return + end + end + + def list + list = api.list_packages target_proj + if format == 'json' + puts list.to_json + else + list.each do |p| + puts format_package(p) + end + end + end + + def create + name = ARGV.shift + if !name + logger.fatal "Package name is missing." + help + return + end + p = api.create_package target_proj, name, visibility: @visibility + logger.info "New package has been created." + puts format_package(p) + end + + def update + target_id = ARGV.shift + args = {name: ARGV.shift} + if @visibility + args[:visibility] = @visibility + end + p = api.update_package target_proj, target_id, args + logger.info "Package #{target_id} has been updated." + puts format_package(p) + end + + def delete + target_id = ARGV.shift + p = api.delete_package target_proj, target_id + logger.info "Package #{target_id} has been deleted." + end + + private + def target_proj + @target_proj and return @target_proj + vars = load_variables + vars.project && !vars.project.empty? and + return vars.project + logger.fatal "No target project is specified." + exit + end + + def api + OSDNClient::ProjectApi.new + end + + def format_package(p) + if format == 'json' + p.to_json + else + "##{p.id} #{p.name} (#{p.visibility}, #{[*p.releases].count} releases)\n #{p.url}" + end + end + end +end; end; end diff --git a/lib/osdn/cli/common.rb b/lib/osdn/cli/common.rb new file mode 100644 index 0000000..975341a --- /dev/null +++ b/lib/osdn/cli/common.rb @@ -0,0 +1,6 @@ +module OSDN + module CLI + module Common + end + end +end diff --git a/lib/osdn/cli/runner.rb b/lib/osdn/cli/runner.rb new file mode 100644 index 0000000..3b7934f --- /dev/null +++ b/lib/osdn/cli/runner.rb @@ -0,0 +1,121 @@ +require 'getoptlong' +require 'logger' +require 'pp' + +module OSDN + module CLI + class Runner + def initialize + @logger = Logger.new(STDERR) + @logger.level = Logger::WARN + @logger.formatter = proc { |severity, time, progname, msg| + "[%s] %s\n" % [severity, msg] + } + end + attr_reader :logger + + def parse_opt + opts = GetoptLong.new( + [ '--help', '-h', GetoptLong::NO_ARGUMENT ], + [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], + [ '--quiet', '-q', GetoptLong::NO_ARGUMENT ], + ) + opts.ordering = GetoptLong::REQUIRE_ORDER + opts.each do |opt, arg| + case opt + when '--help' + help + exit 0 + when '--verbose' + if logger.level == Logger::DEBUG + OSDNClient.configure do |config| + config.debugging = true + end + end + logger.level > Logger::DEBUG and + logger.level -= 1 + when '--quiet' + logger.level < Logger::UNKNOWN and + logger.level += 1 + when '--help' + help + exit + end + end + logger.debug "Loglevel is #{logger.level}" + end + + def get_command_class(command_name) + class_name = command_name.to_s.split('_').map(&:capitalize).join + begin + return self.class.const_get("OSDN::CLI::Command::#{class_name}") + rescue NameError => e + logger.fatal "Invalid command name '#{command_name}'. Use 'help' to list commands." + exit + end + false + end + + def run + parse_opt + + command_name = ARGV.shift + unless command_name + help + exit 1 + end + + OSDNClient.configure do |config| + # TODO: remove + config.verify_ssl = false + config.logger.level = logger.level + end + + if command_name == 'help' + help + exit + end + + command = get_command_class(command_name).new(logger) + logger.debug "Run command #{command_name}" + begin + command.run + rescue OSDNClient::ApiError => e + begin + err = JSON.parse(e.response_body) + if err["message"] + logger.fatal "#{err["status"]}: #{err["message"]}" + elsif err["error_description"] + logger.fatal err["error_description"] + else + logger.fatal "Command failed: #{e.inspect}" + end + rescue + logger.fatal "Command failed: #{e.inspect}" + end + end + end + + def help + command_name = ARGV.shift + if command_name + get_command_class(command_name).new(logger).help + else + puts "#{$0} [global-options] [command-options] [args]" + puts "#{$0} help " + puts "Global Options:" + puts " -h --help Show help message. use 'help ' for specific command. " + puts " -v --verbose Increase log level (multiple)" + puts " -q --quiet Decrease log level (multiple)" + puts "Avaiable Commands:" + puts " help" + OSDN::CLI::Command.constants.each do |c| + c = c.to_s.split(/(?=[A-Z])/).join('_').downcase + c == 'base' and next + puts " %-14s %s" % [c, get_command_class(c).description] + end + end + end + end + end +end diff --git a/lib/osdn/cli/version.rb b/lib/osdn/cli/version.rb new file mode 100644 index 0000000..9d0e616 --- /dev/null +++ b/lib/osdn/cli/version.rb @@ -0,0 +1,5 @@ +module OSDN + module CLI + VERSION = "0.1.0" + end +end diff --git a/osdn-cli.gemspec b/osdn-cli.gemspec new file mode 100644 index 0000000..fa4bc08 --- /dev/null +++ b/osdn-cli.gemspec @@ -0,0 +1,27 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'osdn/cli/version' + +Gem::Specification.new do |spec| + spec.name = "osdn-cli" + spec.version = OSDN::CLI::VERSION + spec.authors = ["OSDN"] + spec.email = ["admin@osdn.jp"] + + spec.summary = %q{OSDN Command Line Interface} + spec.description = %q{Non-intaractive manipulation tool for OSDN} + spec.homepage = "https://osdn.jp/projects/osdn-codes/wiki/CommandLineInterface" + + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler", "~> 1.11" + spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rspec", "~> 3.0" + + spec.add_dependency "osdn-client", "~> 0.0.1" + spec.add_dependency "hashie" +end diff --git a/spec/osdn/cli_spec.rb b/spec/osdn/cli_spec.rb new file mode 100644 index 0000000..03b7e56 --- /dev/null +++ b/spec/osdn/cli_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Osdn::Cli do + it 'has a version number' do + expect(Osdn::Cli::VERSION).not_to be nil + end + + it 'does something useful' do + expect(false).to eq(true) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..485be38 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,2 @@ +$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) +require 'osdn/cli' -- 2.11.0