apical 0.1.4

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/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +24 -0
  4. data/Gemfile.lock +70 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +94 -0
  7. data/Rakefile +55 -0
  8. data/VERSION +1 -0
  9. data/apical.gemspec +122 -0
  10. data/bin/apical +7 -0
  11. data/examples/taco_truck/Gemfile +7 -0
  12. data/examples/taco_truck/Gemfile.lock +50 -0
  13. data/examples/taco_truck/app.rb +13 -0
  14. data/examples/taco_truck/taco_truck.apical +18 -0
  15. data/lib/apical.rb +30 -0
  16. data/lib/apical/adapter.rb +21 -0
  17. data/lib/apical/adapters/http_adapter.rb +30 -0
  18. data/lib/apical/adapters/rack_adapter.rb +16 -0
  19. data/lib/apical/cli.rb +36 -0
  20. data/lib/apical/content_types.rb +62 -0
  21. data/lib/apical/resource.rb +138 -0
  22. data/lib/apical/resource_types.rb +36 -0
  23. data/lib/apical/runner.rb +104 -0
  24. data/lib/apical/writers/console_writer.rb +26 -0
  25. data/lib/apical/writers/html_writer.rb +37 -0
  26. data/spec/apical/adapters/http_adapter_spec.rb +24 -0
  27. data/spec/apical/cli_spec.rb +81 -0
  28. data/spec/apical/content_types_spec.rb +9 -0
  29. data/spec/apical/rack_adapter_spec.rb +12 -0
  30. data/spec/apical_spec.rb +360 -0
  31. data/spec/before_and_after_spec.rb +211 -0
  32. data/spec/fixtures/cli_example_1.rb +7 -0
  33. data/spec/fixtures/cli_example_2.rb +8 -0
  34. data/spec/fixtures/example_require.rb +3 -0
  35. data/spec/fixtures/load_paths_example.apical +1 -0
  36. data/spec/html_writer_spec.rb +117 -0
  37. data/spec/http_apical_spec.rb +23 -0
  38. data/spec/load_paths_spec.rb +12 -0
  39. data/spec/spec_helper.rb +19 -0
  40. data/spec/support/test_apps.rb +34 -0
  41. data/templates/apical_helper.rb +11 -0
  42. data/templates/layout.mustache +180 -0
  43. data/templates/resource.mustache +14 -0
  44. metadata +248 -0
@@ -0,0 +1,13 @@
1
+ require 'sinatra'
2
+ require 'json'
3
+
4
+ get '/tacos.json' do
5
+ [
6
+ { meat: 'chicken', lettuce: true, tomatoes: true }
7
+ ]
8
+ end
9
+
10
+ post '/taco.json' do
11
+ JSON.generate(params)
12
+ end
13
+
@@ -0,0 +1,18 @@
1
+ require 'app'
2
+
3
+ adapter :rack, app: Sinatra::Application
4
+ name "Taco Truck"
5
+ desc "Get some tacos, post some tacos!"
6
+
7
+ accept :json
8
+ content_type :json
9
+
10
+ get "/tacos.json" do
11
+ desc "Get all your tacos"
12
+ end
13
+
14
+ post "/tacos.json" do
15
+ desc "Make a new taco!"
16
+ params { { meat: 'beef', lettuce: true } }
17
+ end
18
+
@@ -0,0 +1,30 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '.'))
2
+
3
+ require 'thor'
4
+ require 'mustache'
5
+ require 'json'
6
+ require 'kramdown'
7
+
8
+ require 'apical/content_types'
9
+ require 'apical/writers/console_writer'
10
+ require 'apical/writers/html_writer'
11
+ require 'apical/adapter'
12
+ require 'apical/adapters/rack_adapter'
13
+ require 'apical/adapters/http_adapter'
14
+ require 'apical/resource'
15
+ require 'apical/runner'
16
+ require 'apical/resource'
17
+ require 'apical/resource_types'
18
+ require 'apical/cli'
19
+
20
+ module Apical
21
+ VERSION = "0.1.0"
22
+
23
+ class << self
24
+ def new(&block)
25
+ Runner.new(&block)
26
+ end
27
+ end
28
+
29
+ end
30
+
@@ -0,0 +1,21 @@
1
+ module Apical
2
+
3
+ class Adapter
4
+
5
+ def self.[](identifier)
6
+ @@registry ||= {}
7
+ klass = @@registry[identifier]
8
+ raise "Adapter '#{identifier}' does not exist." unless klass
9
+
10
+ klass
11
+ end
12
+
13
+ def self.identifier(identifier)
14
+ @@registry ||= {}
15
+ @@registry[identifier] = self
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,30 @@
1
+ require 'httparty'
2
+
3
+ module Apical
4
+
5
+ class HttpAdapter < Adapter
6
+ identifier :http
7
+ include HTTParty
8
+
9
+ def initialize(options={})
10
+ self.class.base_uri options[:base_uri]
11
+ @headers = {}
12
+ end
13
+
14
+ def header(name, value)
15
+ @headers[name] = value
16
+ end
17
+
18
+ def clear_headers!
19
+ @headers = {}
20
+ end
21
+
22
+ def method_missing(name, *args)
23
+ self.class.headers(@headers) unless @headers.empty?
24
+ response = self.class.send(name, *args)
25
+ clear_headers!
26
+ return response
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,16 @@
1
+ require 'rack/test'
2
+
3
+ module Apical
4
+
5
+ class RackAdapter < Adapter
6
+ identifier :rack
7
+ include Rack::Test::Methods
8
+
9
+ def initialize(options={})
10
+ @app = options[:app]
11
+ end
12
+
13
+ attr_reader :app
14
+ end
15
+
16
+ end
@@ -0,0 +1,36 @@
1
+ module Apical
2
+ class CLI < Thor
3
+ map '-v' => :version
4
+
5
+ desc "version", "print version information"
6
+ def version
7
+ puts Apical::VERSION
8
+ end
9
+
10
+ desc "compile", "compile the given input files"
11
+ method_option :output_path, aliases: '-o', desc: "Path to output file"
12
+ method_option :format, aliases: '-f', desc: "Format (either 'html' or 'text')"
13
+ def compile(*files)
14
+ files.each do |file|
15
+ $:.unshift File.dirname(file)
16
+ block = eval("Proc.new { #{File.read(file)} }")
17
+ doc = Apical.new(&block)
18
+
19
+ output = if options[:output_path]
20
+ File.open(options[:output_path], 'w')
21
+ else
22
+ $stdout
23
+ end
24
+
25
+
26
+ case options[:format]
27
+ when 'html'
28
+ HtmlWriter.new(doc).write(output)
29
+ else
30
+ ConsoleWriter.new(doc).write(output)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,62 @@
1
+ module Apical
2
+
3
+ class ContentType
4
+
5
+ def self.[](format)
6
+ @@registry ||= {}
7
+ klass = @@registry[format]
8
+ raise "Content type '#{format}' does not exist." unless klass
9
+
10
+ klass.new
11
+ end
12
+
13
+ def self.format(format)
14
+ @format = format
15
+ @@registry ||= {}
16
+ @@registry[format] = self
17
+ end
18
+
19
+ end
20
+
21
+ class JsonContentType < ContentType
22
+ format :json
23
+
24
+ def header
25
+ 'application/json'
26
+ end
27
+
28
+ def generate(obj)
29
+ JSON.pretty_generate(obj)
30
+ end
31
+
32
+ def parse(str)
33
+ JSON.parse(str)
34
+ end
35
+
36
+ def to_s
37
+ 'json'
38
+ end
39
+ end
40
+
41
+ # TODO: Actually encode/decode urlencoded params
42
+ class FormContentType < ContentType
43
+ format :form
44
+
45
+ def header
46
+ 'application/x-www-form-urlencoded'
47
+ end
48
+
49
+ def generate(str)
50
+ str
51
+ end
52
+
53
+ def parse(obj)
54
+ obj
55
+ end
56
+
57
+ def to_s
58
+ 'form'
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,138 @@
1
+ module Apical
2
+ class Resource
3
+ extend Forwardable
4
+
5
+ def initialize(runner, path, &block)
6
+ @runner = runner
7
+ @path = path
8
+ @params = nil
9
+ @before_blocks = []
10
+ @after_blocks = []
11
+ @custom_headers = {}
12
+ Blueprint.new self, &block
13
+ end
14
+
15
+ attr_accessor :path, :method, :params, :desc, :before_blocks, :after_blocks, :custom_headers
16
+ attr_writer :accept, :content_type
17
+
18
+ def_delegator :@runner, :adapter
19
+ def_delegators :adapter, :get, :put, :post, :delete, :options, :header
20
+
21
+ class Blueprint
22
+ def initialize(resource, &block)
23
+ @resource = resource
24
+ instance_eval &block
25
+ end
26
+
27
+ def desc(val)
28
+ @resource.desc = val
29
+ end
30
+
31
+ def accept(format)
32
+ @resource.accept = ContentType[format]
33
+ end
34
+
35
+ def content_type(format)
36
+ @resource.content_type = ContentType[format]
37
+ end
38
+
39
+ def header(name, value)
40
+ @resource.custom_headers[name] = value
41
+ end
42
+
43
+ def auth_header(token)
44
+ header 'Authorization', token
45
+ end
46
+
47
+ def params(&block)
48
+ @resource.params = block
49
+ end
50
+
51
+ def before(&block)
52
+ @resource.before_blocks << block
53
+ end
54
+
55
+ def after(&block)
56
+ @resource.after_blocks << block
57
+ end
58
+ end
59
+
60
+ def content_type
61
+ @content_type || @runner.content_type
62
+ end
63
+
64
+ def accept
65
+ @accept || @runner.accept
66
+ end
67
+
68
+ def auth_header
69
+ @auth_header || @runner.auth_header
70
+ end
71
+
72
+ def content_type_header
73
+ content_type.header
74
+ end
75
+
76
+ def accept_header
77
+ accept.header
78
+ end
79
+
80
+ def run
81
+ @before_blocks.each {|block| instance_eval &block }
82
+ @params = instance_eval(&@params) if @params.is_a?(Proc)
83
+ @response = make_request
84
+ @after_blocks.each {|block| instance_eval &block }
85
+ end
86
+
87
+ def filtered_params
88
+ params.reject {|k, v| path_captures.include?(k) or path_captures.include?(k.to_s)}
89
+ end
90
+
91
+ def formatted_params
92
+ return {} unless params
93
+ return filtered_params unless accept
94
+ accept.generate(filtered_params)
95
+ end
96
+
97
+ def formatted_response
98
+ return response_body unless content_type
99
+ content_type.generate(content_type.parse(response_body))
100
+ end
101
+
102
+ def response_body
103
+ @response.body
104
+ end
105
+
106
+ PATH_CAPTURE_REGEXP = /:[^\.\/]+/
107
+
108
+ def path_captures
109
+ @path.match(PATH_CAPTURE_REGEXP).to_a.map {|m| m.gsub(':', '').intern }
110
+ end
111
+
112
+ def interpolated_path
113
+ @path.gsub(PATH_CAPTURE_REGEXP) do |match|
114
+ params[match.gsub(':', '').intern]
115
+ end
116
+ end
117
+
118
+ def call_if_proc(proc_or_obj)
119
+ proc_or_obj.is_a?(Proc) ? instance_eval(&proc_or_obj) : proc_or_obj
120
+ end
121
+
122
+ def custom_header(name)
123
+ call_if_proc @custom_headers[name]
124
+ end
125
+
126
+ def make_request
127
+ @custom_headers.each do |name, value|
128
+ adapter.header name, custom_header(name)
129
+ end
130
+
131
+ adapter.header "Accept", accept_header if accept_header
132
+ adapter.header "Content-Type", content_type_header if content_type_header
133
+
134
+ adapter.send(@method.downcase.intern, interpolated_path, formatted_params)
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,36 @@
1
+ module Apical
2
+ class PostResource < Resource
3
+ def initialize(runner, path, &block)
4
+ super runner, path, &block
5
+ @method = 'POST'
6
+ end
7
+ end
8
+
9
+ class GetResource < Resource
10
+ def initialize(runner, path, &block)
11
+ super runner, path, &block
12
+ @method = 'GET'
13
+ end
14
+ end
15
+
16
+ class PutResource < Resource
17
+ def initialize(runner, path, &block)
18
+ super runner, path, &block
19
+ @method = 'PUT'
20
+ end
21
+ end
22
+
23
+ class DeleteResource < Resource
24
+ def initialize(runner, path, &block)
25
+ super runner, path, &block
26
+ @method = 'DELETE'
27
+ end
28
+ end
29
+
30
+ class OptionsResource < Resource
31
+ def initialize(runner, path, &block)
32
+ super runner, path, &block
33
+ @method = 'OPTIONS'
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,104 @@
1
+ module Apical
2
+ class Runner
3
+ def initialize(&block)
4
+ @resources = []
5
+ @name = "apical"
6
+ @desc = ""
7
+ @accept = ContentType[:form]
8
+ @content_type = ContentType[:form]
9
+ @before_each_blocks = []
10
+ @before_all_blocks = []
11
+ @after_each_blocks = []
12
+ @after_all_blocks = []
13
+ Blueprint.new self, &block
14
+ end
15
+
16
+ include Rack::Test::Methods
17
+
18
+ attr_accessor :resources, :accept, :name, :desc, :app, :content_type, :auth_header, :before_each_blocks, :before_all_blocks, :after_each_blocks, :after_all_blocks, :adapter
19
+
20
+ class Blueprint
21
+ def initialize(runner, &block)
22
+ @runner = runner
23
+ instance_eval &block
24
+ end
25
+
26
+ def adapter(identifier, options={})
27
+ @runner.adapter = Adapter[identifier].new(options)
28
+ end
29
+
30
+ def accept(format)
31
+ @runner.accept = ContentType[format]
32
+ end
33
+
34
+ def content_type(format)
35
+ @runner.content_type = ContentType[format]
36
+ end
37
+
38
+ def auth_header(token)
39
+ @runner.auth_header = token
40
+ end
41
+
42
+ def name(name)
43
+ @runner.name = name
44
+ end
45
+
46
+ def desc(desc)
47
+ @runner.desc = desc
48
+ end
49
+
50
+ def app(app)
51
+ @runner.app = app
52
+ end
53
+
54
+ def before(type=:each, &block)
55
+ case type
56
+ when :each
57
+ @runner.before_each_blocks << block
58
+ when :all
59
+ @runner.before_all_blocks << block
60
+ end
61
+ end
62
+
63
+ def after(type=:each, &block)
64
+ case type
65
+ when :each
66
+ @runner.after_each_blocks << block
67
+ when :all
68
+ @runner.after_all_blocks << block
69
+ end
70
+ end
71
+
72
+ def get(path, &block)
73
+ @runner.resources << GetResource.new(@runner, path, &block)
74
+ end
75
+
76
+ def post(path, &block)
77
+ @runner.resources << PostResource.new(@runner, path, &block)
78
+ end
79
+
80
+ def put(path, &block)
81
+ @runner.resources << PutResource.new(@runner, path, &block)
82
+ end
83
+
84
+ def delete(path, &block)
85
+ @runner.resources << DeleteResource.new(@runner, path, &block)
86
+ end
87
+
88
+ def options(path, &block)
89
+ @runner.resources << OptionsResource.new(@runner, path, &block)
90
+ end
91
+ end
92
+
93
+ def run
94
+ @before_all_blocks.each {|block| instance_eval &block }
95
+ @resources.each do |resource|
96
+ @before_each_blocks.each {|block| resource.instance_eval &block }
97
+ resource.run
98
+ @after_each_blocks.each {|block| resource.instance_eval &block }
99
+ end
100
+ @after_all_blocks.each {|block| instance_eval &block }
101
+ self
102
+ end
103
+ end
104
+ end