acter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 169bc9fba66570e92ebb5ee873a2abc1f750311bda5dc87c68d07ce88c695636
4
+ data.tar.gz: 200513d33f26c77d6bdbcb7551a242a700844ce058698df245f3383c7144eaa5
5
+ SHA512:
6
+ metadata.gz: 7182c74161fec3f76f79f867fe81460dd1e952f702b8a58899724f34f199bbec8bc21c6b11f3dd393ab85e6277cebfddd22e55472564d2c48d48d3e3a904c3ef
7
+ data.tar.gz: 13dc24ce9f691c5b2cca7ebaa94ff1a6b3579ca456965a9c3c8e2db97ce949d852a5a352d992da60b4dfc223dcb4578581ed7f35fa70657c938f66ee6d07aeb6
@@ -0,0 +1,12 @@
1
+ Gemfile.lock
2
+ /.bundle/
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.3
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in acter.gemspec
6
+ gemspec
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2019 Smartling, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,63 @@
1
+ # Acter
2
+
3
+ Acter is a command line client for HTTP APIs described by a JSON schema (cf. https://json-schema.org).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'acter'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install acter
20
+
21
+ ## Usage
22
+
23
+ $ acter <subject> <action> <args...>
24
+
25
+ Arguments may take the form `<param>=<value>` to set a request parameter, or `<Header>:<value>` to set an HTTP header for the request. As a special case, if the first argument does not contain `=` or `:`, it will be interpreted as `<subject>=<arg>`, i.e. the argument will be passed as a parameter named after the subject.
26
+
27
+ Ensure that the JSON schema describing your API is in a file named `schema.json` or `schema.yml` in the current directory. If not, you will have to specify a path or URL using the `-s` option.
28
+
29
+ To make acter your own, simply copy the executable or create a symlink with a new name.
30
+
31
+ ### Advanced Usage
32
+
33
+ ```ruby
34
+ schema_data = Acter.load_schema_data(SCHEMA_PATH_OR_URL)
35
+ begin
36
+ action = Acter::Action.new(ARGV, schema_data)
37
+ rescue Acter::InvalidCommand => e
38
+ Acter.handle_invalid_command(e)
39
+ exit 1
40
+ end
41
+ result = action.send_request do |faraday_connection|
42
+ ##
43
+ ## apply middleware to connection or whatever
44
+ ##
45
+ end
46
+ puts result.render(render_options) do |faraday_response|
47
+ ##
48
+ ## return hash of conditional rendering options depending on the response,
49
+ ## action, phase of the moon, etc.
50
+ ##
51
+ end
52
+ exit 1 unless result.success?
53
+ ```
54
+
55
+ ## Development
56
+
57
+ 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.
58
+
59
+ 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).
60
+
61
+ ## Contributing
62
+
63
+ Bug reports and pull requests are welcome on GitHub at https://github.com/syskill/acter.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,33 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "acter/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "acter"
7
+ spec.version = Acter::VERSION
8
+ spec.authors = ["Ben Slusky"]
9
+ spec.email = ["bslusky@smartling.com"]
10
+
11
+ spec.summary = "Command line client for APIs described by JSON Schema"
12
+ spec.homepage = "https://github.com/syskill/acter"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "faraday", "~> 0.14"
23
+ spec.add_dependency "faraday_middleware", "~> 0.12"
24
+ spec.add_dependency "multi_json", "~> 1.10"
25
+ spec.add_dependency "json_schema", "~> 0.16"
26
+ spec.add_dependency "rouge", "~> 1.11"
27
+ spec.add_dependency "term-ansicolor", "~> 1.6"
28
+ spec.add_dependency "activesupport"
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.16"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec", "~> 3.0"
33
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "acter"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "acter"
4
+ require "optparse"
5
+ require "optparse/uri"
6
+ require "pathname"
7
+
8
+ schema_path = nil
9
+ render_options = {}
10
+
11
+ opt_parser = OptionParser.new do |opts|
12
+ opts.version = Acter::VERSION
13
+ opts.banner += "\n\nPerform HTTP requests defined by JSON schema\n\n"
14
+
15
+ opts.on("-sPATH", "--schema=PATH", URI, "Path to JSON schema") do |v|
16
+ schema_path = v.scheme ? v : Pathname.new(v.to_s)
17
+ end
18
+ opts.on("-H", "--[no-]show-headers", :OPTIONAL, TrueClass, "Output response headers (default: no)") do |v|
19
+ render_options[:show_headers] = v
20
+ end
21
+ opts.on("-b", "--[no-]show-body", :OPTIONAL, TrueClass, "Output response body (default: yes)") do |v|
22
+ render_options[:show_body] = v
23
+ end
24
+ opts.on("-c", "--[no-]color", :OPTIONAL, TrueClass, "Colorize output (default: only if output is a TTY)") do |v|
25
+ render_options[:color] = v
26
+ end
27
+ opts.on("-h", "--help", "Help for subject or action") do
28
+ Acter.help_wanted = true
29
+ end
30
+ opts.on("-V", "--version", "Version info") do
31
+ puts opts.ver
32
+ exit
33
+ end
34
+ end
35
+
36
+ begin
37
+ opt_parser.parse!
38
+ rescue OptionParser::ParseError => e
39
+ puts e
40
+ puts opt_parser
41
+ exit 1
42
+ end
43
+
44
+ Acter.options_text = opt_parser.summarize
45
+ Acter.run(ARGV, schema_path, render_options) or exit 1
@@ -0,0 +1,77 @@
1
+ require "acter/version"
2
+ require "json_schema-cromulent_links"
3
+ require "json_schema-example_parsing"
4
+ require "multi_json"
5
+ require "open-uri"
6
+ require "pathname"
7
+ require "yaml"
8
+
9
+ module Acter
10
+ autoload :Action, "acter/action"
11
+ autoload :Error, "acter/error"
12
+ autoload :Help, "acter/help"
13
+ autoload :Request, "acter/request"
14
+ autoload :Response, "acter/response"
15
+ autoload :Result, "acter/result"
16
+
17
+ autoload :NoSchema, "acter/error"
18
+ autoload :InvalidSchema, "acter/error"
19
+ autoload :InvalidCommand, "acter/error"
20
+ autoload :InvalidSubject, "acter/error"
21
+ autoload :InvalidAction, "acter/error"
22
+ autoload :MissingParameters, "acter/error"
23
+ autoload :HelpWanted, "acter/error"
24
+
25
+ class << self
26
+ def load_schema_data(path = nil)
27
+ path ||= Pathname.glob("schema.{json,yml}").first or raise NoSchema
28
+ if path.is_a?(String)
29
+ uri = URI(path)
30
+ source = uri.scheme ? uri : Pathname.new(path)
31
+ elsif path.respond_to?(:read) && path.respond_to?(:to_s)
32
+ source = path
33
+ else
34
+ raise ArgumentError, "Argument to load_schema must be a String or a Pathname-like object"
35
+ end
36
+ if source.to_s =~ /\.ya?ml$/
37
+ YAML.load(source.read)
38
+ else
39
+ MultiJson.load(source.read)
40
+ end
41
+ end
42
+
43
+ def handle_invalid_command(exn)
44
+ puts exn
45
+ puts
46
+ help = Help.new(exn.schema)
47
+ case exn
48
+ when HelpWanted, MissingParameters
49
+ puts help.help_for_action(exn.action, exn.subject)
50
+ when InvalidAction
51
+ puts help.help_for_subject(exn.subject)
52
+ else
53
+ puts help.general_help
54
+ end
55
+ end
56
+
57
+ def run(args, schema_path = nil, render_options = nil)
58
+ schema_data = load_schema_data(schema_path)
59
+ action = Action.new(args, schema_data)
60
+ result = action.send_request
61
+ puts result.render(render_options)
62
+ result.success?
63
+ rescue InvalidCommand => e
64
+ handle_invalid_command(e)
65
+ rescue NoSchema
66
+ raise unless args.empty? || args == %w"help"
67
+ handle_invalid_command(InvalidCommand.new(nil))
68
+ end
69
+
70
+ def program_name
71
+ @program_name ||= File.basename($0, ".rb")
72
+ end
73
+
74
+ attr_accessor :help_wanted, :options_text
75
+ alias help_wanted? help_wanted
76
+ end
77
+ end
@@ -0,0 +1,104 @@
1
+ require "acter/result"
2
+ require "active_support/core_ext/object/try"
3
+ require "active_support/core_ext/string/inflections"
4
+ require "cgi"
5
+ require "json_schema"
6
+ require "multi_json"
7
+
8
+ module Acter
9
+ class Action
10
+ def initialize(args, schema_data)
11
+ @subject, @name = args.shift(2)
12
+
13
+ @params = {}
14
+ @headers = {}
15
+ args.each_with_index do |arg, idx|
16
+ case
17
+ when /^(?<key>[^:=]+):(?<value>.*)/ =~ arg
18
+ warn "Value of header #{key.inspect} is empty" if value.empty?
19
+ @headers[key] = value
20
+ when /^(?<key>[^:=]+)=(?<value>.*)/ =~ arg
21
+ @params[key] = try_json_value(value)
22
+ when idx.zero?
23
+ @params[@subject] = try_json_value(arg)
24
+ else
25
+ raise ArgumentError, arg.inspect
26
+ end
27
+ end
28
+
29
+ @schema, errors = JsonSchema.parse(schema_data)
30
+ @schema or raise InvalidSchema.new("JSON schema parsing failed", errors)
31
+ result, errors = @schema.expand_references
32
+ result or raise InvalidSchema.new("JSON schema reference expansion failed", errors)
33
+
34
+ @base_url = @schema.links.find do |li|
35
+ li.href && li.rel == "self"
36
+ end.try(:href)
37
+ @base_url or raise InvalidSchema, "Schema has no valid link to self"
38
+
39
+ validate_link!
40
+ if Acter.help_wanted?
41
+ raise HelpWanted.new(@name, @subject, @schema)
42
+ end
43
+ validate_params!
44
+ end
45
+
46
+ attr_reader :name, :subject, :params, :headers, :schema,
47
+ :base_url, :link, :path
48
+
49
+ def send_request(&block)
50
+ req = Request.new(link.method, base_url, path, params, headers)
51
+ req.client(&block)
52
+ Result.new(req.send)
53
+ end
54
+
55
+ private
56
+
57
+ def try_json_value(str)
58
+ begin
59
+ MultiJson.load(%<{"_":#{str}}>)['_']
60
+ rescue MultiJson::ParseError
61
+ str
62
+ end
63
+ end
64
+
65
+ def validate_link!
66
+ schema.properties.key?(subject) or raise InvalidSubject.new(subject, schema)
67
+ @link = schema.properties[subject].cromulent_links.find {|li| li.title.underscore == name }
68
+ @link or raise InvalidAction.new(name, subject, schema)
69
+ @link
70
+ end
71
+
72
+ def validate_params!
73
+ missing_params = []
74
+ path_keys = link.href.scan(/\{\(([^)]+)\)\}/).map do |m|
75
+ path_param_base_name(m.first)
76
+ end
77
+ path_keys.uniq! and raise InvalidSchema, "Link #{link.pointer.inspect} has multiple path parameters with same base name"
78
+ path_params = path_keys.each_with_object({}) do |k, hsh|
79
+ if params.key?(k)
80
+ hsh[k.to_sym] = params.delete(k)
81
+ else
82
+ missing_params << k
83
+ end
84
+ end
85
+ if link.schema && link.schema.properties
86
+ unless (path_keys & link.schema.properties.keys).empty?
87
+ raise InvalidSchema, "Path parameter base names and property names of link #{link.pointer.inspect} are not unique"
88
+ end
89
+ if link.schema.required
90
+ missing_params.concat(link.schema.required - params.keys)
91
+ end
92
+ end
93
+ missing_params.empty? or raise MissingParameters.new(missing_params, name, subject, schema)
94
+ @path = link.href.gsub(/\{\(([^)]+)\)\}/) do
95
+ "%{#{path_param_base_name($1)}}"
96
+ end % path_params
97
+ @params
98
+ end
99
+
100
+ def path_param_base_name(str)
101
+ CGI.unescape(str).split("/").last
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,91 @@
1
+ module Acter
2
+ class Error < StandardError
3
+ end
4
+
5
+ class NoSchema < Error
6
+ def to_s
7
+ "Schema not found"
8
+ end
9
+ end
10
+
11
+ class InvalidSchema < Error
12
+ def initialize(message, errors = nil)
13
+ @message = message
14
+ @errors = Array(errors)
15
+ end
16
+ attr_reader :message
17
+ def to_s
18
+ @errors.dup.unshift(@message).join("\n\t")
19
+ end
20
+ end
21
+
22
+ class InvalidCommand < Error
23
+ def initialize(schema, subject = nil, action = nil, params = nil)
24
+ @schema = schema
25
+ @subject = subject
26
+ @action = action
27
+ @params = params
28
+ end
29
+ attr_reader :schema, :subject, :action, :params
30
+ def to_s
31
+ if @schema.nil?
32
+ "Command-line help"
33
+ else
34
+ "Invalid command"
35
+ end
36
+ end
37
+ end
38
+
39
+ class InvalidSubject < InvalidCommand
40
+ def initialize(subject, schema)
41
+ super(schema, subject)
42
+ end
43
+ def message
44
+ "Invalid subject"
45
+ end
46
+ def to_s
47
+ if @subject.nil? || @subject == "help"
48
+ "Command-line help"
49
+ else
50
+ "#{message}: #{@subject.inspect}"
51
+ end
52
+ end
53
+ end
54
+
55
+ class InvalidAction < InvalidCommand
56
+ def initialize(action, subject, schema)
57
+ super(schema, subject, action)
58
+ end
59
+ def message
60
+ "No valid link for action"
61
+ end
62
+ def to_s
63
+ if @action.nil? || @action == "help"
64
+ "Command-line help"
65
+ else
66
+ "#{message}: #{@subject.inspect} -> #{@action.inspect}"
67
+ end
68
+ end
69
+ end
70
+
71
+ class MissingParameters < InvalidCommand
72
+ def initialize(params, action, subject, schema)
73
+ super(schema, subject, action, params)
74
+ end
75
+ def message
76
+ "Missing required parameters"
77
+ end
78
+ def to_s
79
+ "#{message}: #{@params.map(&:inspect).join(", ")}"
80
+ end
81
+ end
82
+
83
+ class HelpWanted < InvalidCommand
84
+ def initialize(action, subject, schema)
85
+ super(schema, subject, action)
86
+ end
87
+ def to_s
88
+ "Command-line help"
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,113 @@
1
+ require "active_support/core_ext/string/inflections"
2
+ require "cgi"
3
+ require "json_schema"
4
+ require "multi_json"
5
+ require "stringio"
6
+
7
+ module Acter
8
+ class Help
9
+ def initialize(schema)
10
+ @schema = schema
11
+ end
12
+
13
+ def general_help
14
+ StringIO.open do |s|
15
+ s.puts "USAGE: #{Acter.program_name}#{Acter.options_text ? " [options]" : ""} <subject> <action> <params...>"
16
+ s.puts Acter.options_text if Acter.options_text
17
+ if @schema
18
+ s.puts
19
+ s.puts "Perform #{@schema.description} requests defined by JSON schema"
20
+ s.puts
21
+ s.puts "Valid subjects:"
22
+ @schema.properties.keys.sort.each do |subj|
23
+ s.puts " #{subj}"
24
+ end
25
+ end
26
+ s.string
27
+ end
28
+ end
29
+
30
+ def help_for_subject(subject)
31
+ prop_schema = @schema.properties[subject]
32
+ StringIO.open do |s|
33
+ s.puts "USAGE: #{Acter.program_name}#{Acter.options_text ? " [options]" : ""} #{subject} <action> <params...>"
34
+ s.puts Acter.options_text if Acter.options_text
35
+ s.puts
36
+ s.puts "Perform #{@schema.description} #{prop_schema.description} requests defined by JSON schema"
37
+ s.puts
38
+ s.puts "Valid actions:"
39
+ prop_schema.cromulent_links.each do |li|
40
+ s.puts " #{example_command(li)}"
41
+ s.puts " #{li.description}"
42
+ end
43
+ s.string
44
+ end
45
+ end
46
+
47
+ def help_for_action(action, subject)
48
+ link = @schema.properties[subject].cromulent_links.find {|li| li.title.underscore == action }
49
+ StringIO.open do |s|
50
+ s.puts "USAGE: #{Acter.program_name}#{Acter.options_text ? " [options]" : ""} #{subject} #{example_command(link)}"
51
+ s.puts Acter.options_text if Acter.options_text
52
+ s.puts
53
+ s.puts link.description
54
+
55
+ if link.schema && link.schema.properties
56
+ s.puts
57
+ s.puts "Parameters:"
58
+ link.schema.properties.each do |name, prop_schema|
59
+ next if prop_schema.read_only?
60
+ descr = nil
61
+ if !prop_schema.default.nil?
62
+ descr = "default #{prop_schema.default.inspect}"
63
+ elsif !(ex = prop_schema.make_example).nil?
64
+ descr = "e.g. #{ex.inspect}"
65
+ elsif !prop_schema.enum.nil?
66
+ descr = prop_schema.enum.map(&:inspect).join(", ")
67
+ elsif !prop_schema.type.empty?
68
+ descr = prop_schema.type.map(&:capitalize).join("|")
69
+ end
70
+ descr = [descr, prop_schema.description].compact.join(" - ")
71
+ required = link.schema.required && link.schema.required.include?(name) ? " (REQUIRED)" : ""
72
+ s.puts " #{name}#{required} : #{descr}"
73
+ end
74
+ end
75
+ s.string
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def metasyntactic(property, subject = nil)
82
+ unless property.any_of.empty?
83
+ property.any_of.map(&method(:metasyntactic)).join("_or_")
84
+ else
85
+ path = property.reference ? property.reference.pointer : property.pointer
86
+ ###pc = path.split("/")
87
+ ###"#{pc[-3]}_#{pc[-1]}"
88
+ path.split("/").last
89
+ end
90
+ end
91
+
92
+ def example_command(link)
93
+ subject = link.pointer.split("/")[-3]
94
+ args = []
95
+ link.href.scan(/\{\(([^)]+)\)\}/).each do |m|
96
+ path = CGI.unescape(m.first)
97
+ basename = path.split("/").last
98
+ property = JsonPointer.evaluate(@schema, path) or
99
+ raise InvalidSchema, "Link #{link.pointer.inspect} has invalid path parameter #{path.inspect}"
100
+ meta = metasyntactic(property)
101
+ if basename == subject
102
+ args.unshift("[#{basename}=]<#{meta}>")
103
+ else
104
+ args << "#{basename}=<#{meta}>"
105
+ end
106
+ end
107
+ if link.schema && link.schema.properties
108
+ args << "<params...>"
109
+ end
110
+ args.unshift(link.title.underscore).join(" ")
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,34 @@
1
+ require "acter/response"
2
+ require "faraday"
3
+ require "faraday_middleware"
4
+
5
+ module Acter
6
+ class Request
7
+ def initialize(method, base_url, path, params = nil, headers = nil)
8
+ @method = method.is_a?(Symbol) ? method : method.to_s.downcase
9
+ @base_url = base_url
10
+ @path = path
11
+ @params = Hash(params)
12
+ @headers = Hash(headers)
13
+ end
14
+
15
+ def client
16
+ unless @client && !block_given?
17
+ @client = Faraday.new(
18
+ url: @base_url,
19
+ headers: {'Accept' => "application/json"},
20
+ ) do |faraday|
21
+ faraday.request :json
22
+ yield faraday if block_given?
23
+ faraday.response :json, content_type: /\bjson(?:;|$)/
24
+ faraday.adapter Faraday.default_adapter
25
+ end
26
+ end
27
+ @client
28
+ end
29
+
30
+ def send
31
+ Response.new_from_faraday(client.send(@method, @path, @params, @headers))
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ require "multi_json"
2
+
3
+ module Acter
4
+ class Response
5
+ def initialize(status, headers, body)
6
+ @status = status
7
+ @success = (200..299).include?(status[/\d+/])
8
+ @headers = headers.sort.map {|a| a.join(": ") }
9
+ @body = case body
10
+ when String
11
+ @body_is_json = false
12
+ body
13
+ else
14
+ @body_is_json = true
15
+ MultiJson.dump(body, pretty: true)
16
+ end
17
+ end
18
+
19
+ def self.new_from_faraday(faraday_response)
20
+ status_string = "#{faraday_response.status} #{faraday_response.reason_phrase}"
21
+ new(status_string, faraday_response.headers, faraday_response.body)
22
+ end
23
+
24
+ attr_reader :status, :success, :headers, :body, :body_is_json
25
+ alias_method :success?, :success
26
+ alias_method :body_is_json?, :body_is_json
27
+ remove_method :success
28
+ remove_method :body_is_json
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ require "forwardable"
2
+ require "rouge"
3
+ require "stringio"
4
+ require "term/ansicolor"
5
+
6
+ module Acter
7
+ class Result
8
+ extend Forwardable
9
+
10
+ DEFAULT_RENDER_OPTIONS = {
11
+ show_body: true,
12
+ show_headers: false,
13
+ color: :tty?,
14
+ theme: "monokai",
15
+ }
16
+
17
+ def initialize(response)
18
+ @response = response
19
+ end
20
+
21
+ attr_reader :response
22
+ def_delegator :@response, :success?
23
+
24
+ def render(options = nil)
25
+ options = DEFAULT_RENDER_OPTIONS.merge(Hash(options))
26
+ if block_given?
27
+ more_options = yield response
28
+ options.merge!(Hash(more_options))
29
+ end
30
+
31
+ colorize = options[:color] && (options[:color] != :tty? || $>.tty?)
32
+
33
+ StringIO.open do |s|
34
+ if colorize
35
+ s.puts Term::ANSIColor.bold(response.status)
36
+ else
37
+ s.puts response.status
38
+ end
39
+ if options[:show_headers]
40
+ response.headers.each(&s.method(:puts))
41
+ end
42
+ if options[:show_body]
43
+ s.puts
44
+ if colorize
45
+ lexer = response.body_is_json? ? Rouge::Lexers::JSON : Rouge::Lexers::HTML
46
+ s.puts Rouge::Formatters::Terminal256.format(lexer.new.lex(response.body), theme: options[:theme])
47
+ else
48
+ s.puts response.body
49
+ end
50
+ end
51
+ s.string
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ module Acter
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,16 @@
1
+ require 'json_schema'
2
+ require 'json_schema/schema'
3
+
4
+ module JsonSchema
5
+ class Schema
6
+ def cromulent_links
7
+ links.select(&:cromulent?)
8
+ end
9
+
10
+ class Link
11
+ def cromulent?
12
+ href && method && title
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ require 'json_schema'
2
+ require 'json_schema/parser'
3
+ require 'json_schema/schema'
4
+
5
+ module JsonSchema
6
+ module ExampleParsing
7
+ def parse_schema(data, parent, fragment)
8
+ schema = super(data, parent, fragment)
9
+ schema.example = schema.data['example']
10
+ schema
11
+ end
12
+ end
13
+
14
+ class Parser
15
+ prepend ExampleParsing
16
+ end
17
+
18
+ class Schema
19
+ attr_schema :example
20
+
21
+ def make_example
22
+ if example
23
+ return example
24
+ end
25
+ if items
26
+ ex = items.make_example and return Array(ex)
27
+ end
28
+ unless any_of.empty?
29
+ any_of.each {|s| ex = s.make_example and return ex }
30
+ end
31
+ unless all_of.empty?
32
+ any_of.each {|s| ex = s.make_example and return ex }
33
+ end
34
+ nil
35
+ end
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,206 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Slusky
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-05-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.14'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday_middleware
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.12'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: multi_json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json_schema
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.16'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.16'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rouge
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.11'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.11'
83
+ - !ruby/object:Gem::Dependency
84
+ name: term-ansicolor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.6'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.6'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activesupport
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: bundler
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.16'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.16'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '10.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '10.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ description:
154
+ email:
155
+ - bslusky@smartling.com
156
+ executables:
157
+ - acter
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - ".gitignore"
162
+ - ".rspec"
163
+ - ".travis.yml"
164
+ - Gemfile
165
+ - LICENSE.txt
166
+ - README.md
167
+ - Rakefile
168
+ - acter.gemspec
169
+ - bin/console
170
+ - bin/setup
171
+ - exe/acter
172
+ - lib/acter.rb
173
+ - lib/acter/action.rb
174
+ - lib/acter/error.rb
175
+ - lib/acter/help.rb
176
+ - lib/acter/request.rb
177
+ - lib/acter/response.rb
178
+ - lib/acter/result.rb
179
+ - lib/acter/version.rb
180
+ - lib/json_schema-cromulent_links.rb
181
+ - lib/json_schema-example_parsing.rb
182
+ homepage: https://github.com/syskill/acter
183
+ licenses:
184
+ - MIT
185
+ metadata: {}
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubyforge_project:
202
+ rubygems_version: 2.7.6
203
+ signing_key:
204
+ specification_version: 4
205
+ summary: Command line client for APIs described by JSON Schema
206
+ test_files: []