chimps-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +8 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +20 -0
- data/README.rdoc +322 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/bin/chimps +4 -0
- data/lib/chimps-cli.rb +46 -0
- data/lib/chimps-cli/commands.rb +179 -0
- data/lib/chimps-cli/commands/base.rb +65 -0
- data/lib/chimps-cli/commands/create.rb +38 -0
- data/lib/chimps-cli/commands/delete.rb +29 -0
- data/lib/chimps-cli/commands/destroy.rb +36 -0
- data/lib/chimps-cli/commands/download.rb +40 -0
- data/lib/chimps-cli/commands/get.rb +30 -0
- data/lib/chimps-cli/commands/help.rb +100 -0
- data/lib/chimps-cli/commands/list.rb +48 -0
- data/lib/chimps-cli/commands/me.rb +30 -0
- data/lib/chimps-cli/commands/post.rb +33 -0
- data/lib/chimps-cli/commands/put.rb +33 -0
- data/lib/chimps-cli/commands/query.rb +58 -0
- data/lib/chimps-cli/commands/search.rb +54 -0
- data/lib/chimps-cli/commands/show.rb +40 -0
- data/lib/chimps-cli/commands/test.rb +37 -0
- data/lib/chimps-cli/commands/update.rb +38 -0
- data/lib/chimps-cli/commands/upload.rb +86 -0
- data/lib/chimps-cli/utils.rb +13 -0
- data/lib/chimps-cli/utils/acts_on_resource.rb +93 -0
- data/lib/chimps-cli/utils/explicit_path.rb +30 -0
- data/lib/chimps-cli/utils/http_format.rb +51 -0
- data/lib/chimps-cli/utils/uses_param_value_data.rb +90 -0
- data/spec/chimps-cli/commands/delete_spec.rb +9 -0
- data/spec/chimps-cli/commands/get_spec.rb +8 -0
- data/spec/chimps-cli/commands/help_spec.rb +18 -0
- data/spec/chimps-cli/commands/list_spec.rb +7 -0
- data/spec/chimps-cli/commands/post_spec.rb +10 -0
- data/spec/chimps-cli/commands/put_spec.rb +10 -0
- data/spec/chimps-cli/commands/show_spec.rb +7 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/support/acts_on_resource.rb +22 -0
- data/spec/support/explicit_path.rb +42 -0
- data/spec/support/http_format.rb +23 -0
- data/spec/support/uses_param_value_data.rb +88 -0
- metadata +166 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
module Chimps
|
2
|
+
|
3
|
+
# Raised when the user provides bad input on the command line.
|
4
|
+
CLIError = Class.new(Error)
|
5
|
+
|
6
|
+
module Utils
|
7
|
+
autoload :ActsOnResource, 'chimps-cli/utils/acts_on_resource'
|
8
|
+
autoload :UsesParamValueData, 'chimps-cli/utils/uses_param_value_data'
|
9
|
+
autoload :HttpFormat, 'chimps-cli/utils/http_format'
|
10
|
+
autoload :ExplicitPath, 'chimps-cli/utils/explicit_path'
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Chimps
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
# Defines methods which can be included by a Chimps::Command to
|
5
|
+
# let it interpret an optional first argument as the name of a
|
6
|
+
# resource to act on.
|
7
|
+
module ActsOnResource
|
8
|
+
|
9
|
+
def self.included klass
|
10
|
+
klass.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def default_resources_type
|
15
|
+
'datasets'
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_resource_type
|
19
|
+
normalize_resource_name(default_resources_type)
|
20
|
+
end
|
21
|
+
|
22
|
+
def allowed_resources
|
23
|
+
%w[dataset collection source license]
|
24
|
+
end
|
25
|
+
|
26
|
+
def resources_listing
|
27
|
+
doc = <<DOC
|
28
|
+
In addition to #{default_resources_type}, this command can operate on
|
29
|
+
other resources at Infochimps as well. If the first
|
30
|
+
argument passed is one of
|
31
|
+
|
32
|
+
DOC
|
33
|
+
allowed_resources.each do |resource|
|
34
|
+
doc << " #{resource}\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
doc << "\nthen this command will act on that resource instead."
|
38
|
+
end
|
39
|
+
|
40
|
+
def normalize_resource_name string
|
41
|
+
string.to_s.downcase.gsub(/s$/,'')
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def first_arg_is_resource_type?
|
47
|
+
return false if config.argv.size < 2
|
48
|
+
self.class.allowed_resources.include?(self.class.normalize_resource_name(config.argv[1]))
|
49
|
+
end
|
50
|
+
|
51
|
+
def resource_type
|
52
|
+
(first_arg_is_resource_type? && self.class.normalize_resource_name(config.argv[1])) || self.class.default_resource_type
|
53
|
+
end
|
54
|
+
|
55
|
+
def plural_resource
|
56
|
+
# i miss you DHH
|
57
|
+
if resource_type[-1].chr == 'y'
|
58
|
+
resource_type[1..-1] + 'ies'
|
59
|
+
else
|
60
|
+
resource_type + 's'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def has_resource_identifier?
|
65
|
+
if first_arg_is_resource_type?
|
66
|
+
config.argv.size >= 2 && (! config.argv[1].empty?) && config.argv[2]
|
67
|
+
else
|
68
|
+
config.argv.size >= 1 && (! config.argv.first.empty?) && config.argv[1]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def resource_identifier
|
73
|
+
raise CLIError.new(self.class::USAGE) unless has_resource_identifier?
|
74
|
+
first_arg_is_resource_type? ? config.argv[2] : config.argv[1]
|
75
|
+
end
|
76
|
+
|
77
|
+
def resources_path
|
78
|
+
if config[:my]
|
79
|
+
"/my/#{plural_resource}"
|
80
|
+
else
|
81
|
+
"/#{plural_resource}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def resource_path
|
86
|
+
"#{plural_resource}/#{resource_identifier}"
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Chimps
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
module ExplicitPath
|
5
|
+
|
6
|
+
def raw_path
|
7
|
+
raise CLIError.new(self.class::USAGE) unless config.argv.size > 1
|
8
|
+
config.argv[1]
|
9
|
+
end
|
10
|
+
|
11
|
+
def query_params
|
12
|
+
return {} unless raw_path.include?('?')
|
13
|
+
{}.tap do |h|
|
14
|
+
raw_path.split('?')[1].split('&').each do |pair|
|
15
|
+
name, value = pair.split('=').map { |string| CGI::unescape(string) }
|
16
|
+
h[name] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def path
|
22
|
+
p = raw_path.split('?').first
|
23
|
+
p =~ /\.(\w+)$/i ? p : "#{p}.#{response_fmt}"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Chimps
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
module HttpFormat
|
5
|
+
|
6
|
+
def self.included klass
|
7
|
+
klass.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
[:request, :response].each do |type|
|
12
|
+
define_method("default_#{type}_fmt") { 'json' }
|
13
|
+
define_method("allowed_#{type}_fmts") { %w[json xml yaml] }
|
14
|
+
end
|
15
|
+
def mime_type_for fmt
|
16
|
+
case fmt.to_s.tr('.','').downcase.to_sym
|
17
|
+
when :json then 'application/json'
|
18
|
+
when :yaml then 'application/x-yaml'
|
19
|
+
when :tsv then 'text/tab-separated-values'
|
20
|
+
else 'application/json' # default
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def normalize_fmt string
|
26
|
+
string.to_s.downcase.tr('.','')
|
27
|
+
end
|
28
|
+
|
29
|
+
def headers
|
30
|
+
{
|
31
|
+
:content_type => self.class.mime_type_for(request_fmt),
|
32
|
+
:accept => self.class.mime_type_for(response_fmt)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
[:request, :response].each do |type|
|
37
|
+
define_method "#{type}_fmt" do
|
38
|
+
param_name = "#{type}_format".to_sym
|
39
|
+
if config[param_name] && self.class.send("allowed_#{type}_fmts").include?(normalize_fmt(config[param_name]))
|
40
|
+
normalize_fmt(config[param_name])
|
41
|
+
else
|
42
|
+
self.class.send("default_#{type}_fmt")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Chimps
|
5
|
+
module Utils
|
6
|
+
module UsesParamValueData
|
7
|
+
|
8
|
+
def self.included klass
|
9
|
+
klass.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def how_to_input_data
|
14
|
+
<<DOC
|
15
|
+
Properties and values can be supplied directly on the command line or
|
16
|
+
from an input file with the --data option.
|
17
|
+
DOC
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
@data ||= merge_all(*(data_from_stdin + data_from_file + data_from_command_line)) || {}
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def merge_all *objs
|
28
|
+
objs.compact!
|
29
|
+
return if objs.blank? # raising an error here is left to the caller
|
30
|
+
klasses = objs.map(&:class).uniq
|
31
|
+
raise CLIError.new("Mismatched YAML data types -- Hashes can only be combined with Hashes, Arrays with Arrays") if klasses.size > 1
|
32
|
+
data_type = klasses.first.new
|
33
|
+
case data_type
|
34
|
+
when Array
|
35
|
+
# greater precedence at the end so iterate in order
|
36
|
+
[].tap do |d|
|
37
|
+
objs.each do |obj|
|
38
|
+
d.concat(obj)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
when Hash
|
42
|
+
# greater precedence at the end so iterate in order
|
43
|
+
{}.tap do |d|
|
44
|
+
objs.each do |obj|
|
45
|
+
d.merge!(obj)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
else raise CLIError.new("Incompatible YAML data type #{data_type} -- can only combine Hashes and Arrays")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def data_from_command_line
|
53
|
+
[].tap do |d|
|
54
|
+
config.argv.each_with_index do |arg, index|
|
55
|
+
next unless arg =~ /^(\w+) *=(.*)$/
|
56
|
+
name, value = $1.downcase, $2.strip
|
57
|
+
d << { name => value } # always a hash
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def data_from_file
|
63
|
+
return [] unless config[:data]
|
64
|
+
[].tap do |d|
|
65
|
+
config[:data].split(',').map { |p| File.expand_path(p) }.each do |path|
|
66
|
+
d << (path =~ /\.ya?ml$/ ? YAML.load_file(path) : JSON.parse(File.read(path)))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def data_from_stdin
|
72
|
+
return [] unless $stdin.stat.size > 0
|
73
|
+
[].tap do |d|
|
74
|
+
begin
|
75
|
+
YAML.load_stream($stdin).each do |document|
|
76
|
+
d << document
|
77
|
+
end
|
78
|
+
rescue
|
79
|
+
d << JSON.parse($stdin.read)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def ensure_data_is_present!
|
85
|
+
raise CLIError.new(self.class::USAGE) if data.empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Chimps::Commands::Help do
|
4
|
+
|
5
|
+
it "should display a default help message when called without arguments" do
|
6
|
+
run_argv("help").stderr.should include("usage: chimps help")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should display a default help message when called with arguments it doesn't recognize" do
|
10
|
+
run_argv("help", "foobar").stderr.should include("usage: chimps help")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should display help on a specific command when called with that command name" do
|
14
|
+
run_argv("help", "show").stderr.should include("usage: chimps show")
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
CHIMPS_CLI_ROOT_DIR = File.join(File.expand_path(File.dirname(__FILE__)), '..') unless defined? CHIMPS_CLI_ROOT_DIR
|
2
|
+
CHIMPS_CLI_SPEC_DIR = File.join(CHIMPS_CLI_ROOT_DIR, 'spec') unless defined? CHIMPS_CLI_SPEC_DIR
|
3
|
+
CHIMPS_CLI_LIB_DIR = File.join(CHIMPS_CLI_ROOT_DIR, 'lib') unless defined? CHIMPS_CLI_LIB_DIR
|
4
|
+
$: << CHIMPS_CLI_LIB_DIR
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rspec'
|
8
|
+
require 'chimps-cli'
|
9
|
+
|
10
|
+
Dir[File.dirname(__FILE__) + "/support/*.rb"].each { |path| require path }
|
11
|
+
|
12
|
+
TMP_DIR = "/tmp/chimps_cli_test" unless defined?(TMP_DIR)
|
13
|
+
|
14
|
+
def in_tmp_dir &block
|
15
|
+
FileUtils.mkdir_p TMP_DIR
|
16
|
+
FileUtils.cd(TMP_DIR, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def clean_tmp_dir!
|
20
|
+
FileUtils.rm_rf TMP_DIR
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_argv *args
|
24
|
+
$0 = 'chimps'
|
25
|
+
::ARGV.replace args
|
26
|
+
Chimps.boot!
|
27
|
+
Chimps.config.command_settings.resolve! if Chimps::CLI.command
|
28
|
+
end
|
29
|
+
|
30
|
+
def command
|
31
|
+
Chimps::CLI.command
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
old_stdout = $stdout
|
36
|
+
old_stderr = $stderr
|
37
|
+
new_stdout = StringIO.new
|
38
|
+
new_stderr = StringIO.new
|
39
|
+
$stdout = new_stdout
|
40
|
+
$stderr = new_stderr
|
41
|
+
exit_code = Chimps::CLI.execute!
|
42
|
+
$stdout.flush
|
43
|
+
$stderr.flush
|
44
|
+
$stdout = old_stdout
|
45
|
+
$stderr = old_stderr
|
46
|
+
OpenStruct.new(:stdout => new_stdout.string, :stderr => new_stderr.string, :exit_code => exit_code)
|
47
|
+
end
|
48
|
+
|
49
|
+
def run_argv *args
|
50
|
+
set_argv *args
|
51
|
+
run
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
shared_examples_for 'it acts on a resource' do |required_arg|
|
2
|
+
|
3
|
+
def described_class_command_name
|
4
|
+
described_class.to_s.split('::').last.downcase
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should use the default resource type when not passed any arguments" do
|
8
|
+
set_argv(described_class_command_name, required_arg)
|
9
|
+
command.resource_type.should == described_class.default_resource_type
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should not accept a resource type that isn't allowed" do
|
13
|
+
set_argv(described_class_command_name, required_arg, 'foobar')
|
14
|
+
command.resource_type.should == described_class.default_resource_type
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should accept a resource type that is allowed" do
|
18
|
+
set_argv(described_class_command_name, required_arg, described_class.allowed_resources.first)
|
19
|
+
command.resource_type.should == described_class.allowed_resources.first
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
shared_examples_for 'a request to an explicit path' do
|
2
|
+
|
3
|
+
def described_class_command_name
|
4
|
+
described_class.to_s.split('::').last.downcase
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should display usage when called without a path" do
|
8
|
+
run_argv(described_class_command_name).stderr.should include("usage: chimps #{described_class_command_name}")
|
9
|
+
run_argv(described_class_command_name).exit_code.should == 1
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "constructing the path to send a request to" do
|
13
|
+
|
14
|
+
it "should append a .json extension by default" do
|
15
|
+
set_argv(described_class_command_name, "/datasets")
|
16
|
+
command.path.should == '/datasets.json'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should an extension if given one" do
|
20
|
+
set_argv(described_class_command_name, "/datasets.xml")
|
21
|
+
command.path.should == '/datasets.xml'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be able to extract the path when given a path with query string" do
|
25
|
+
set_argv(described_class_command_name, "/datasets.xml?some=query&string=true")
|
26
|
+
command.path.should == '/datasets.xml'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "constructing the query params to send with the request" do
|
31
|
+
it "should not set any query params by default" do
|
32
|
+
set_argv(described_class_command_name, "/datasets.xml")
|
33
|
+
command.query_params.should == {}
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should detect and unescape query params when given" do
|
37
|
+
set_argv(described_class_command_name, "/datasets.xml?withSpaces=foo+bar")
|
38
|
+
command.query_params.should == {'withSpaces' => 'foo bar'}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|