chimps-cli 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +32 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +322 -0
  5. data/Rakefile +24 -0
  6. data/VERSION +1 -0
  7. data/bin/chimps +4 -0
  8. data/lib/chimps-cli.rb +46 -0
  9. data/lib/chimps-cli/commands.rb +179 -0
  10. data/lib/chimps-cli/commands/base.rb +65 -0
  11. data/lib/chimps-cli/commands/create.rb +38 -0
  12. data/lib/chimps-cli/commands/delete.rb +29 -0
  13. data/lib/chimps-cli/commands/destroy.rb +36 -0
  14. data/lib/chimps-cli/commands/download.rb +40 -0
  15. data/lib/chimps-cli/commands/get.rb +30 -0
  16. data/lib/chimps-cli/commands/help.rb +100 -0
  17. data/lib/chimps-cli/commands/list.rb +48 -0
  18. data/lib/chimps-cli/commands/me.rb +30 -0
  19. data/lib/chimps-cli/commands/post.rb +33 -0
  20. data/lib/chimps-cli/commands/put.rb +33 -0
  21. data/lib/chimps-cli/commands/query.rb +58 -0
  22. data/lib/chimps-cli/commands/search.rb +54 -0
  23. data/lib/chimps-cli/commands/show.rb +40 -0
  24. data/lib/chimps-cli/commands/test.rb +37 -0
  25. data/lib/chimps-cli/commands/update.rb +38 -0
  26. data/lib/chimps-cli/commands/upload.rb +86 -0
  27. data/lib/chimps-cli/utils.rb +13 -0
  28. data/lib/chimps-cli/utils/acts_on_resource.rb +93 -0
  29. data/lib/chimps-cli/utils/explicit_path.rb +30 -0
  30. data/lib/chimps-cli/utils/http_format.rb +51 -0
  31. data/lib/chimps-cli/utils/uses_param_value_data.rb +90 -0
  32. data/spec/chimps-cli/commands/delete_spec.rb +9 -0
  33. data/spec/chimps-cli/commands/get_spec.rb +8 -0
  34. data/spec/chimps-cli/commands/help_spec.rb +18 -0
  35. data/spec/chimps-cli/commands/list_spec.rb +7 -0
  36. data/spec/chimps-cli/commands/post_spec.rb +10 -0
  37. data/spec/chimps-cli/commands/put_spec.rb +10 -0
  38. data/spec/chimps-cli/commands/show_spec.rb +7 -0
  39. data/spec/spec_helper.rb +52 -0
  40. data/spec/support/acts_on_resource.rb +22 -0
  41. data/spec/support/explicit_path.rb +42 -0
  42. data/spec/support/http_format.rb +23 -0
  43. data/spec/support/uses_param_value_data.rb +88 -0
  44. 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,9 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Chimps::Commands::Delete do
4
+
5
+ it_behaves_like "a request to an explicit path"
6
+ it_behaves_like "it can set its response format"
7
+
8
+ end
9
+
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Chimps::Commands::Get do
4
+ it_behaves_like "a request to an explicit path"
5
+ it_behaves_like "it can set its response format"
6
+
7
+ end
8
+
@@ -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
+
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Chimps::Commands::List do
4
+ it_behaves_like "it can set its response format"
5
+ it_behaves_like "it acts on a resource", nil
6
+ end
7
+
@@ -0,0 +1,10 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Chimps::Commands::Post do
4
+
5
+ it_behaves_like "a request to an explicit path"
6
+ it_behaves_like "it can set its response format"
7
+ it_behaves_like "it uses data", nil
8
+
9
+ end
10
+
@@ -0,0 +1,10 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Chimps::Commands::Put do
4
+
5
+ it_behaves_like "a request to an explicit path"
6
+ it_behaves_like "it can set its response format"
7
+ it_behaves_like "it uses data", nil
8
+
9
+ end
10
+
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Chimps::Commands::Show do
4
+ it_behaves_like "it can set its response format"
5
+ it_behaves_like "it acts on a resource", nil
6
+ end
7
+
@@ -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