chimps-cli 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|