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.
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