restapi 0.0.4 → 0.0.5

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 (49) hide show
  1. data/Gemfile +0 -1
  2. data/Gemfile.lock +0 -10
  3. data/README.rdoc +18 -12
  4. data/app/controllers/restapi/restapis_controller.rb +28 -1
  5. data/app/views/layouts/restapi/restapi.html.erb +1 -0
  6. data/app/views/restapi/restapis/_params.html.erb +22 -0
  7. data/app/views/restapi/restapis/_params_plain.html.erb +16 -0
  8. data/app/views/restapi/restapis/index.html.erb +5 -5
  9. data/app/views/restapi/restapis/method.html.erb +8 -4
  10. data/app/views/restapi/restapis/plain.html.erb +70 -0
  11. data/app/views/restapi/restapis/resource.html.erb +16 -5
  12. data/app/views/restapi/restapis/static.html.erb +4 -6
  13. data/lib/restapi.rb +2 -1
  14. data/lib/restapi/application.rb +72 -22
  15. data/lib/restapi/client/generator.rb +104 -0
  16. data/lib/restapi/client/template/Gemfile.tt +5 -0
  17. data/lib/restapi/client/template/README.tt +3 -0
  18. data/lib/restapi/client/template/base.rb.tt +33 -0
  19. data/lib/restapi/client/template/bin.rb.tt +110 -0
  20. data/lib/restapi/client/template/cli.rb.tt +25 -0
  21. data/lib/restapi/client/template/cli_command.rb.tt +129 -0
  22. data/lib/restapi/client/template/client.rb.tt +10 -0
  23. data/lib/restapi/client/template/resource.rb.tt +17 -0
  24. data/lib/restapi/dsl_definition.rb +20 -2
  25. data/lib/restapi/error_description.rb +8 -2
  26. data/lib/restapi/extractor.rb +143 -0
  27. data/lib/restapi/extractor/collector.rb +113 -0
  28. data/lib/restapi/extractor/recorder.rb +122 -0
  29. data/lib/restapi/extractor/writer.rb +356 -0
  30. data/lib/restapi/helpers.rb +10 -5
  31. data/lib/restapi/markup.rb +12 -12
  32. data/lib/restapi/method_description.rb +52 -8
  33. data/lib/restapi/param_description.rb +6 -5
  34. data/lib/restapi/railtie.rb +1 -1
  35. data/lib/restapi/resource_description.rb +1 -1
  36. data/lib/restapi/restapi_module.rb +43 -0
  37. data/lib/restapi/validator.rb +70 -3
  38. data/lib/restapi/version.rb +1 -1
  39. data/lib/tasks/restapi.rake +120 -121
  40. data/restapi.gemspec +0 -2
  41. data/spec/controllers/restapis_controller_spec.rb +41 -6
  42. data/spec/controllers/users_controller_spec.rb +51 -12
  43. data/spec/dummy/app/controllers/application_controller.rb +0 -2
  44. data/spec/dummy/app/controllers/twitter_example_controller.rb +4 -9
  45. data/spec/dummy/app/controllers/users_controller.rb +13 -6
  46. data/spec/dummy/config/initializers/restapi.rb +7 -0
  47. data/spec/dummy/doc/restapi_examples.yml +28 -0
  48. metadata +49 -76
  49. data/app/helpers/restapi/restapis_helper.rb +0 -31
@@ -0,0 +1,104 @@
1
+ #!/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ require 'rubygems'
4
+ require 'thor'
5
+ require 'thor/group'
6
+ require 'fileutils'
7
+ require 'active_support/inflector'
8
+
9
+ module Restapi
10
+ module Client
11
+
12
+ class Generator < Thor::Group
13
+ include Thor::Actions
14
+
15
+ # Define arguments and options
16
+ argument :name
17
+
18
+ attr_reader :doc, :resource
19
+
20
+ def initialize(*args)
21
+ super
22
+ @doc = Restapi.to_json()[:docs]
23
+ end
24
+
25
+ def self.source_root
26
+ File.expand_path("../template", __FILE__)
27
+ end
28
+
29
+ def self.destination_root
30
+ File.join(FileUtils.pwd, "client")
31
+ end
32
+
33
+ def self.start(client_name)
34
+ super([client_name.parameterize.underscore], :destination_root => destination_root)
35
+ end
36
+
37
+ def generate_cli
38
+ template("README.tt", "README")
39
+ template("Gemfile.tt", "Gemfile")
40
+ template("bin.rb.tt", "bin/#{name}-client")
41
+ chmod("bin/#{name}-client", 0755)
42
+ template("client.rb.tt", "lib/#{name}_client.rb")
43
+ template("base.rb.tt", "lib/#{name}_client/base.rb")
44
+ template("cli_command.rb.tt", "lib/#{name}_client/cli_command.rb")
45
+ doc[:resources].each do |key, resource|
46
+ @resource = resource
47
+ template("cli.rb.tt", "lib/#{name}_client/commands/#{resource_name}.thor")
48
+ template("resource.rb.tt", "lib/#{name}_client/resources/#{resource_name}.rb")
49
+ end
50
+ end
51
+
52
+ protected
53
+
54
+ def class_base
55
+ name.camelize
56
+ end
57
+
58
+ def plaintext(text)
59
+ text.gsub(/<.*?>/, '').gsub("\n",' ').strip
60
+ end
61
+
62
+ # Resource related helper methods:
63
+
64
+ def resource_name
65
+ resource[:name].gsub(/\s/,"_").downcase.singularize
66
+ end
67
+
68
+ def api(method)
69
+ method[:apis].first
70
+ end
71
+
72
+ def params_in_path(method)
73
+ api(method)[:api_url].scan(/:([^\/]*)/).map(&:first)
74
+ end
75
+
76
+ def client_args(method)
77
+ client_args = params_in_path(method).dup
78
+ client_args << "params = {}" if method[:params].any?
79
+ client_args
80
+ end
81
+
82
+ def validation_hash(method)
83
+ if method[:params].any? { |p| p[:params] }
84
+ method[:params].reduce({}) do |h, p|
85
+ h.update(p[:name] => (p[:params] ? p[:params].map { |pp| pp[:name] } : nil))
86
+ end
87
+ else
88
+ method[:params].map { |p| p[:name] }
89
+ end
90
+ end
91
+
92
+ def substituted_url(method)
93
+ params_in_path(method).reduce(api(method)[:api_url]) { |u, p| u.sub(":#{p}","\#{#{p}}")}
94
+ end
95
+
96
+ def transformation_hash(method)
97
+ method[:params].find_all { |p| p[:expected_type] == "hash" && !p[:params].nil? }.reduce({}) do |h, p|
98
+ h.update(p[:name] => p[:params].map { |pp| pp[:name] })
99
+ end
100
+ end
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ gem 'thor', '0.15.4'
4
+ gem 'json'
5
+ gem 'rest-client', '1.6.1'
@@ -0,0 +1,3 @@
1
+ This is a readme file for this gem.
2
+
3
+ Let the code be with you!
@@ -0,0 +1,33 @@
1
+ require 'rest_client'
2
+ require 'json'
3
+
4
+ module <%= class_base %>Client
5
+ class Base
6
+ attr_reader :client
7
+
8
+ def initialize(config)
9
+ @client = RestClient::Resource.new(config[:base_url], :user => config[:username], :password => config[:password])
10
+ end
11
+
12
+ def call(method, path, params = {})
13
+ ret = client[path].send(method, params)
14
+ data = JSON.parse(ret.body) rescue ret.body
15
+ return data, ret
16
+ end
17
+
18
+ def validate_params!(options, valid_keys)
19
+ return unless options.is_a?(Hash)
20
+ invalid_keys = options.keys - (valid_keys.is_a?(Hash) ? valid_keys.keys : valid_keys)
21
+ raise ArgumentError, "Invalid keys: #{invalid_keys.join(", ")}" unless invalid_keys.empty?
22
+
23
+ if valid_keys.is_a? Hash
24
+ valid_keys.each do |key, keys|
25
+ if options[key]
26
+ validate_params!(options[key], keys)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems" # ruby1.9 doesn't "require" it though
3
+ require "pathname"
4
+ require "thor"
5
+ require 'thor/core_ext/file_binary_read'
6
+
7
+ $: << File.expand_path("../../lib", __FILE__)
8
+ require "<%= name %>_client"
9
+ require "<%= name %>_client/cli_command"
10
+
11
+ module <%= class_base %>Cli
12
+ class Main < Thor
13
+
14
+ def help(meth = nil)
15
+ if meth && !self.respond_to?(meth)
16
+ initialize_thorfiles(meth)
17
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
18
+ self.class.handle_no_task_error(task, false) if klass.nil?
19
+ klass.start(["-h", task].compact, :shell => self.shell)
20
+ else
21
+ say "<%= name.capitalize %> CLI"
22
+ say
23
+ invoke :commands
24
+ end
25
+ end
26
+
27
+ desc "commands [SEARCH]", "List the available commands"
28
+ def commands(search="")
29
+ initialize_thorfiles
30
+ klasses = Thor::Base.subclasses
31
+ display_klasses(false, false, klasses)
32
+ end
33
+
34
+ class << self
35
+ private
36
+ def dispatch(task, given_args, given_options, config)
37
+ parser = Thor::Options.new({:username => Thor::Option.parse(%w[username -u], :string),
38
+ :password => Thor::Option.parse(%w[password -p], :string)})
39
+ opts = parser.parse(given_args)
40
+ <%= class_base %>Client.client_config[:username] = opts["username"]
41
+ <%= class_base %>Client.client_config[:password] = opts["password"]
42
+ #remaining = parser.instance_variable_get("@unknown") # TODO: this is an ugly hack :(
43
+ remaining = parser.remaining
44
+ super(task, remaining, given_options, config)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def method_missing(meth, *args)
51
+ meth = meth.to_s
52
+ initialize_thorfiles(meth)
53
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
54
+ args.unshift(task) if task
55
+ klass.start(args, :shell => self.shell)
56
+ end
57
+
58
+ # Load the thorfiles. If relevant_to is supplied, looks for specific files
59
+ # in the thor_root instead of loading them all.
60
+ #
61
+ # By default, it also traverses the current path until find Thor files, as
62
+ # described in thorfiles. This look up can be skipped by suppliying
63
+ # skip_lookup true.
64
+ #
65
+ def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
66
+ thorfiles.each do |f|
67
+ Thor::Util.load_thorfile(f, nil, options[:debug])
68
+ end
69
+ end
70
+
71
+ def thorfiles
72
+ Dir[File.expand_path("../../lib/<%= name %>_client/commands/*.thor", __FILE__)]
73
+ end
74
+
75
+ # Display information about the given klasses. If with_module is given,
76
+ # it shows a table with information extracted from the yaml file.
77
+ #
78
+ def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
79
+ klasses -= [Thor, Main, ::<%= class_base %>Client::CliCommand] unless show_internal
80
+
81
+ show_modules if with_modules && !thor_yaml.empty?
82
+
83
+ list = Hash.new { |h,k| h[k] = [] }
84
+ groups = []
85
+
86
+ # Get classes which inherit from Thor
87
+ (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_tasks(false) }
88
+
89
+ # Get classes which inherit from Thor::Base
90
+ groups.map! { |k| k.printable_tasks(false).first }
91
+ list["root"] = groups
92
+
93
+ # Order namespaces with default coming first
94
+ list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
95
+ list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
96
+ end
97
+
98
+ def display_tasks(namespace, list) #:nodoc:
99
+ say namespace
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ begin
106
+ <%= class_base %>Cli::Main.start
107
+ rescue RestClient::Exception => e
108
+ $stderr.puts e.message
109
+ exit 1
110
+ end
@@ -0,0 +1,25 @@
1
+ class <%= resource_name.camelize %> < <%= class_base %>Client::CliCommand
2
+
3
+ <% resource[:methods].each do |method| -%>
4
+ desc '<%= method[:name] %>', '<%= api(method)[:short_description] %>'
5
+ <% params_in_path(method).each do |param| -%>
6
+ method_option :<%= param %>, :required => 'true'
7
+ <% end
8
+ method[:params].map {|p| p[:expected_type] == "hash" ? (p[:params] || p) : p}.flatten.each do |param| -%>
9
+ method_option :<%= param[:name] %>, :required => <%= param[:required] ? 'true' : 'false' %>, :desc => '<%= plaintext(param[:description]) %>', :type => :<%= param[:expected_type] %>
10
+ <% end -%>
11
+ def <%= method[:name] %>
12
+ <% if params_in_path(method).any? || transformation_hash(method).any?
13
+ transform_options_params = [params_in_path(method).inspect]
14
+ transform_options_params << transformation_hash(method).inspect if transformation_hash(method).any? -%>
15
+ <%= (params_in_path(method) + ["options"]).join(", ") %>, *_ = transform_options(<%= transform_options_params.join(", ").html_safe %>)
16
+ <% end
17
+
18
+ client_args = params_in_path(method).dup
19
+ client_args << "options" if method[:params].any? -%>
20
+ data, resp = client.<%= method[:name] %><%= "(#{client_args.join(", ")})" if client_args.any? %>
21
+ print_data(data)
22
+ end
23
+
24
+ <% end -%>
25
+ end
@@ -0,0 +1,129 @@
1
+ module <%= class_base %>Client
2
+ class CliCommand < Thor
3
+ no_tasks do
4
+ def client
5
+ resource_class = <%= class_base %>Client::Resources.const_get(self.class.name[/[^:]*$/])
6
+ @client ||= resource_class.new(<%= class_base %>Client.client_config)
7
+ end
8
+
9
+ def transform_options(inline_params, transform_hash = {})
10
+ ret = inline_params.map { |p| options[p] }
11
+
12
+ # we use not mentioned params without change
13
+ transformed_options = (options.keys - transform_hash.values.flatten - inline_params).reduce({}) { |h, k| h.update(k => options[k]) }
14
+
15
+ transform_hash.each do |sub_key, params|
16
+ transformed_options[sub_key] = {}
17
+ params.each { |p| transformed_options[sub_key][p] = options[p] if options.has_key?(p) }
18
+ end
19
+
20
+ ret << transformed_options
21
+ return *ret
22
+ end
23
+
24
+ def print_data(data)
25
+ case data
26
+ when Array
27
+ print_big_table(table_from_array(data))
28
+ when Hash
29
+ print_table(table_from_hash(data))
30
+ else
31
+ print_unknown(data)
32
+ end
33
+ end
34
+
35
+ # unifies the data for further processing. e.g.
36
+ #
37
+ # { "user" => {"username" => "test", "password" => "changeme" }
38
+ #
39
+ # becomes:
40
+ #
41
+ # { "username" => "test", "password" => "changeme" }
42
+ def normalize_item_data(item)
43
+ if item.size == 1 && item.values.first.is_a?(Hash)
44
+ item.values.first
45
+ else
46
+ item
47
+ end
48
+ end
49
+
50
+ def table_from_array(data)
51
+ return [] if data.empty?
52
+ table = []
53
+ items = data.map { |item| normalize_item_data(item) }
54
+ columns = items.first.keys
55
+ table << columns
56
+ items.each do |item|
57
+ row = columns.map { |c| item[c] }
58
+ table << row.map(&:to_s)
59
+ end
60
+ return table
61
+ end
62
+
63
+ def table_from_hash(data)
64
+ return [] if data.empty?
65
+ table = []
66
+ normalize_item_data(data).each do |k, v|
67
+ table << ["#{k}:",v].map(&:to_s)
68
+ end
69
+ table
70
+ end
71
+
72
+ def print_unknown(data)
73
+ say data
74
+ end
75
+
76
+ def print_big_table(table, options={})
77
+ return if table.empty?
78
+
79
+ formats, ident, colwidth = [], options[:ident].to_i, options[:colwidth]
80
+ options[:truncate] = terminal_width if options[:truncate] == true
81
+
82
+ formats << "%-#{colwidth + 2}s" if colwidth
83
+ start = colwidth ? 1 : 0
84
+
85
+ start.upto(table.first.length - 2) do |i|
86
+ maxima ||= table.max{|a,b| a[i].size <=> b[i].size }[i].size
87
+ formats << "%-#{maxima + 2}s"
88
+ end
89
+
90
+ formats << "%s"
91
+ formats[0] = formats[0].insert(0, " " * ident)
92
+
93
+ header_printed = false
94
+ table.each do |row|
95
+ sentence = ""
96
+
97
+ row.each_with_index do |column, i|
98
+ sentence << formats[i] % column.to_s
99
+ end
100
+
101
+ sentence = truncate(sentence, options[:truncate]) if options[:truncate]
102
+ $stdout.puts sentence
103
+ say(set_color("-" * sentence.size, :green)) unless header_printed
104
+ header_printed = true
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ class << self
111
+ def help(shell, subcommand = true)
112
+ list = self.printable_tasks(true, subcommand)
113
+ Thor::Util.thor_classes_in(self).each do |klass|
114
+ list += printable_tasks(false)
115
+ end
116
+ list.sort!{ |a,b| a[0] <=> b[0] }
117
+
118
+ shell.say
119
+ shell.print_table(list, :indent => 2, :truncate => true)
120
+ shell.say
121
+ Thor.send(:class_options_help, shell)
122
+ end
123
+
124
+ def banner(task, namespace = nil, subcommand = false)
125
+ task.name
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,10 @@
1
+ require '<%= name %>_client/base'
2
+
3
+ resource_files = Dir[File.expand_path('../<%= name %>_client/resources/*.rb', __FILE__)]
4
+ resource_files.each { |f| require f }
5
+
6
+ module <%= class_base %>Client
7
+ def self.client_config
8
+ @client_config ||= {:base_url => "http://localhost:3000"}
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ module <%= class_base %>Client
2
+ module Resources
3
+ class <%= resource_name.camelize %> < <%= class_base %>Client::Base
4
+ <% resource[:methods].each do |method| -%>
5
+
6
+ def <%= method[:name] %><%= "(#{ client_args(method).join(", ") })" if client_args(method).any? %>
7
+ <% if method[:params].any? -%>
8
+ validate_params!(params, <%= validation_hash(method).inspect.html_safe %>)
9
+ <% end -%>
10
+ call(:<%= api(method)[:http_method].downcase %>, "<%= substituted_url(method) %>"<%=
11
+ (api(method)[:http_method].downcase == 'get' ? ", :params => params" : ", params") if method[:params].any? %>)
12
+ end
13
+ <% end -%>
14
+
15
+ end
16
+ end
17
+ end