cabbage_doc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cabbage_doc/action.rb +86 -0
  3. data/lib/cabbage_doc/authentication.rb +54 -0
  4. data/lib/cabbage_doc/client.rb +27 -0
  5. data/lib/cabbage_doc/cloneable.rb +9 -0
  6. data/lib/cabbage_doc/collection.rb +67 -0
  7. data/lib/cabbage_doc/configuration.rb +52 -0
  8. data/lib/cabbage_doc/controller.rb +117 -0
  9. data/lib/cabbage_doc/customizer.rb +9 -0
  10. data/lib/cabbage_doc/example.rb +25 -0
  11. data/lib/cabbage_doc/pacto_helper.rb +27 -0
  12. data/lib/cabbage_doc/parameter.rb +72 -0
  13. data/lib/cabbage_doc/params.rb +59 -0
  14. data/lib/cabbage_doc/parser.rb +47 -0
  15. data/lib/cabbage_doc/path.rb +9 -0
  16. data/lib/cabbage_doc/processor.rb +41 -0
  17. data/lib/cabbage_doc/processors/contracts.rb +12 -0
  18. data/lib/cabbage_doc/processors/documentation.rb +15 -0
  19. data/lib/cabbage_doc/processors/rspec.rb +11 -0
  20. data/lib/cabbage_doc/request.rb +70 -0
  21. data/lib/cabbage_doc/response.rb +37 -0
  22. data/lib/cabbage_doc/singleton.rb +15 -0
  23. data/lib/cabbage_doc/task.rb +72 -0
  24. data/lib/cabbage_doc/version.rb +3 -0
  25. data/lib/cabbage_doc/web.rb +39 -0
  26. data/lib/cabbage_doc/web_helper.rb +25 -0
  27. data/lib/cabbage_doc.rb +34 -0
  28. data/web/public/css/application.css +184 -0
  29. data/web/public/js/application.js +113 -0
  30. data/web/public/js/jquery-1.12.3.min.js +5 -0
  31. data/web/views/action.haml +20 -0
  32. data/web/views/array.haml +13 -0
  33. data/web/views/authentication.haml +25 -0
  34. data/web/views/controller.haml +7 -0
  35. data/web/views/documentation.haml +3 -0
  36. data/web/views/enumeration.haml +9 -0
  37. data/web/views/index.haml +8 -0
  38. data/web/views/layout.haml +10 -0
  39. data/web/views/parameter.haml +12 -0
  40. data/web/views/response.haml +14 -0
  41. data/web/views/text.haml +1 -0
  42. data/web/views/welcome.haml +1 -0
  43. metadata +196 -0
@@ -0,0 +1,41 @@
1
+ module CabbageDoc
2
+ class Processor
3
+ class << self
4
+ def inherited(klass)
5
+ all[klass.to_s.split('::').last.downcase.to_sym] = klass
6
+ end
7
+
8
+ def all
9
+ @_all ||= {}
10
+ end
11
+
12
+ def load!
13
+ Dir.glob(File.join(File.dirname(__FILE__), 'processors', '*.rb')).sort.each do |processor|
14
+ require(processor)
15
+ end
16
+ end
17
+ end
18
+
19
+ def perform
20
+ raise NotImplementedError
21
+ end
22
+
23
+ protected
24
+
25
+ def client
26
+ @_client ||= Client.new(auth)
27
+ end
28
+
29
+ def auth
30
+ @_auth ||= Authentication.new
31
+ end
32
+
33
+ def collection
34
+ @_collection ||= Collection.instance.tap do |collection|
35
+ collection.load!
36
+ end
37
+ end
38
+ end
39
+
40
+ Processor.load!
41
+ end
@@ -0,0 +1,12 @@
1
+ module CabbageDoc
2
+ module Processors
3
+ class Contracts < Processor
4
+ include PactoHelper
5
+
6
+ def perform
7
+ # TODO: use "examples" to generate Pacto Contracts
8
+ # nop
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module CabbageDoc
2
+ module Processors
3
+ class Documentation < Processor
4
+ def perform
5
+ collection.clear!
6
+
7
+ Configuration.instance.controllers.call.each do |filename|
8
+ collection.parse!(filename)
9
+ end
10
+
11
+ collection.save!
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module CabbageDoc
2
+ module Processors
3
+ class Rspec < Processor
4
+ def perform
5
+ # TODO: use "Examples" and generate a "controllers_spec.rb" that validates
6
+ # itself against the generated contracts via Pacto ...
7
+ # nop
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ module CabbageDoc
2
+ class Request
3
+ METHODS = %i[get post put delete].freeze
4
+
5
+ attr_reader :raw_request, :collection
6
+
7
+ def initialize(raw_request, collection)
8
+ @raw_request = raw_request
9
+ @collection = collection
10
+ end
11
+
12
+ def perform
13
+ Response.new(url, params, perform_request) if valid?
14
+ end
15
+
16
+ def valid?
17
+ action && method && METHODS.include?(method)
18
+ end
19
+
20
+ private
21
+
22
+ def perform_request
23
+ key = (method == :get) ? :query : :body
24
+ client.send(method, action, key => params.to_hash)
25
+ end
26
+
27
+ def url
28
+ @_url ||= [client.base_uri, action]
29
+ end
30
+
31
+ def action
32
+ @_action ||= compose_action(raw_request.params['action'])
33
+ end
34
+
35
+ def method
36
+ @_method ||= compose_method(raw_request.params['method'])
37
+ end
38
+
39
+ def compose_method(method)
40
+ method.downcase.to_sym if method
41
+ end
42
+
43
+ def compose_action(action)
44
+ return action unless action && action.include?(':')
45
+
46
+ action = action.dup
47
+
48
+ action.dup.scan(/\/:([^\/]+)/) do
49
+ value = params.delete($1)
50
+ return unless value
51
+
52
+ action.sub!(":#{$1}", value)
53
+ end
54
+
55
+ action
56
+ end
57
+
58
+ def params
59
+ @_params ||= Params.new(raw_request.params, collection)
60
+ end
61
+
62
+ def auth
63
+ @_auth ||= Authentication.new(raw_request)
64
+ end
65
+
66
+ def client
67
+ @_client ||= Client.new(auth)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,37 @@
1
+ module CabbageDoc
2
+ class Response
3
+ attr_reader :url, :headers, :params, :code, :body
4
+
5
+ def initialize(url, params, response)
6
+ @url = url
7
+ @params = params
8
+ @headers = convert_headers(response)
9
+ @code = response.code
10
+ @body = response.parsed_response
11
+ end
12
+
13
+ def to_json
14
+ {
15
+ url: url,
16
+ query: params.to_query,
17
+ code: code,
18
+ headers: prettify(headers),
19
+ body: prettify(body)
20
+ }.to_json
21
+ end
22
+
23
+ private
24
+
25
+ def prettify(hash)
26
+ JSON.pretty_generate(hash)
27
+ end
28
+
29
+ def convert_headers(response)
30
+ {}.tap do |hash|
31
+ response.headers.each do |k, v|
32
+ hash[k] = v
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ module CabbageDoc
2
+ module Singleton
3
+ class << self
4
+ def included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+ end
8
+
9
+ module ClassMethods
10
+ def instance
11
+ @_instance ||= new
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,72 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ module CabbageDoc
5
+ class Task < Rake::TaskLib
6
+ attr_accessor :processors, :name
7
+
8
+ def self.define
9
+ new.tap do |instance|
10
+ yield instance if block_given?
11
+ instance.validate!
12
+ instance.define!
13
+ end
14
+ end
15
+
16
+ def initialize
17
+ @processors = [:documentation]
18
+ @name = :cabbagedoc
19
+ end
20
+
21
+ def contracts=(value)
22
+ if value
23
+ @processors.delete(:rspec) if @processors.include?(:rspec)
24
+ @processors << :contracts
25
+ @processors << :rspec
26
+ else
27
+ @processors.delete(:contracts)
28
+ end
29
+ end
30
+
31
+ def rspec=(value)
32
+ if value
33
+ @processors << :contracts unless @processors.include?(:contracts)
34
+ @processors << :rspec
35
+ else
36
+ @processors.delete(:rspec)
37
+ end
38
+ end
39
+
40
+ def define!
41
+ namespace name do
42
+ processors.each do |processor|
43
+ desc "Process #{processor}"
44
+ task processor.to_s => :environment do
45
+ Processor.all[processor].new.perform
46
+ end
47
+ end
48
+
49
+ desc "Customize Web"
50
+ task :customize => :environment do
51
+ Customizer.new.perform
52
+ end
53
+ end
54
+
55
+ desc "Run all processors"
56
+ task name => :environment do
57
+ processors.each do |name|
58
+ Processor.all[name].new.perform
59
+ end
60
+ end
61
+ end
62
+
63
+ def validate!
64
+ fail "Invalid 'name'" unless name.is_a?(Symbol)
65
+ fail "No 'processors' configured" unless processors.any?
66
+
67
+ processors.each do |processor|
68
+ fail "Invalid 'processor' #{processor}" unless Processor.all.has_key?(processor)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ module CabbageDoc
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -0,0 +1,39 @@
1
+ require 'sinatra/base'
2
+ require 'haml'
3
+ require 'redcarpet'
4
+ require 'json'
5
+ require 'cgi'
6
+
7
+ module CabbageDoc
8
+ class Web < Sinatra::Base
9
+ ROOT = File.expand_path("../../../web", __FILE__).freeze
10
+
11
+ set :root, proc {
12
+ dir = File.join(Configuration.instance.root, 'web')
13
+ if Dir.exists?(dir)
14
+ dir
15
+ else
16
+ ROOT
17
+ end
18
+ }
19
+ set :public_folder, proc { "#{root}/public" }
20
+ set :views, proc { "#{root}/views" }
21
+
22
+ helpers WebHelper
23
+
24
+ get '/' do
25
+ haml :index
26
+ end
27
+
28
+ post '/' do
29
+ response = Request.new(request, collection).perform
30
+
31
+ if response
32
+ content_type :json
33
+ response.to_json
34
+ else
35
+ halt 500
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ module CabbageDoc
2
+ module WebHelper
3
+ def config
4
+ Configuration.instance
5
+ end
6
+
7
+ def collection
8
+ @_collection ||= Collection.instance.tap do |collection|
9
+ collection.load!
10
+ end
11
+ end
12
+
13
+ def markdown
14
+ @_markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML.new)
15
+ end
16
+
17
+ def title
18
+ @_title ||= config.title
19
+ end
20
+
21
+ def auth
22
+ @_auth ||= Authentication.new(request)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module CabbageDoc
2
+ MARKER = ':cabbagedoc:'.freeze
3
+
4
+ autoload :Path, 'cabbage_doc/path'
5
+ autoload :Singleton, 'cabbage_doc/singleton'
6
+ autoload :Cloneable, 'cabbage_doc/cloneable'
7
+ autoload :PactoHelper, 'cabbage_doc/pacto_helper'
8
+ autoload :Parser, 'cabbage_doc/parser'
9
+ autoload :Client, 'cabbage_doc/client'
10
+ autoload :Request, 'cabbage_doc/request'
11
+ autoload :Response, 'cabbage_doc/response'
12
+ autoload :Params, 'cabbage_doc/params'
13
+ autoload :Example, 'cabbage_doc/example'
14
+ autoload :Configuration, 'cabbage_doc/configuration'
15
+ autoload :Authentication, 'cabbage_doc/authentication'
16
+ autoload :Parameter, 'cabbage_doc/parameter'
17
+ autoload :Action, 'cabbage_doc/action'
18
+ autoload :Controller, 'cabbage_doc/controller'
19
+ autoload :Collection, 'cabbage_doc/collection'
20
+ autoload :WebHelper, 'cabbage_doc/web_helper'
21
+ autoload :Web, 'cabbage_doc/web'
22
+ autoload :Processor, 'cabbage_doc/processor'
23
+ autoload :Customizer, 'cabbage_doc/customizer'
24
+ autoload :Task, 'cabbage_doc/task'
25
+
26
+ class << self
27
+ def configure
28
+ Configuration.instance.tap do |config|
29
+ yield config if block_given?
30
+ config.validate!
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,184 @@
1
+ body
2
+ {
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ pre, code, .authentication form
8
+ {
9
+ font-family: monospace;
10
+ background-color: #fcf6db;
11
+ border: 1px solid #e5e0c6;
12
+ padding: 10px;
13
+ }
14
+
15
+ label, .required
16
+ {
17
+ font-weight: bold;
18
+ }
19
+
20
+ input[type=text], select
21
+ {
22
+ min-width: 200px;
23
+ }
24
+
25
+ input[type=button], input[type=submit]
26
+ {
27
+ min-width: 98px;
28
+ }
29
+
30
+ a, a:active, a:visited, a:hover
31
+ {
32
+ color: #000000;
33
+ text-decoration: none;
34
+ font-weight: bold;
35
+ }
36
+
37
+ a:hover
38
+ {
39
+ text-decoration: underline;
40
+ }
41
+
42
+ .hidden
43
+ {
44
+ display: none !important;
45
+ }
46
+
47
+ .clear
48
+ {
49
+ clear: both;
50
+ }
51
+
52
+ .application
53
+ {
54
+ margin: 10px auto;
55
+ width: 1024px;
56
+ }
57
+
58
+ .welcome, .documentation, .authentication
59
+ {
60
+ width: 100%;
61
+ }
62
+
63
+ .resource
64
+ {
65
+ color: #999999;
66
+ padding: 10px 0 10px 0px;
67
+ font-size: 1.5em;
68
+ font-weight: bold;
69
+ border-bottom: 1px solid #dddddd;
70
+ }
71
+
72
+ .action
73
+ {
74
+ margin-top: 10px;
75
+ padding: 10px;
76
+ }
77
+
78
+ .action.get
79
+ {
80
+ background-color: #e7f0f7;
81
+ border: 1px solid #aecce4;
82
+ }
83
+
84
+ .action.post
85
+ {
86
+ background-color: #ebf7f0;
87
+ border: 1px solid #c3e8d1;
88
+ }
89
+
90
+ .action.put
91
+ {
92
+ background-color: #f9f2e9;
93
+ border: 1px solid #e9cead;
94
+ }
95
+
96
+ .action.delete
97
+ {
98
+ background-color: #f5e8e8;
99
+ border: 1px solid #deb3b3;
100
+ }
101
+
102
+ .action .method
103
+ {
104
+ color: #ffffff;
105
+ font-weight: bold;
106
+ width: 80px;
107
+ text-align: center;
108
+ padding: 5px;
109
+ float: left;
110
+ margin-right: 10px;
111
+ }
112
+
113
+ .action .method.get
114
+ {
115
+ background-color: #0061ac;
116
+ }
117
+
118
+ .action .method.post
119
+ {
120
+ background-color: #10a54a;
121
+ }
122
+
123
+ .action .method.put
124
+ {
125
+ background-color: #b06300;
126
+ }
127
+
128
+ .action .method.delete
129
+ {
130
+ background-color: #a20909;
131
+ }
132
+
133
+ .action .subresource
134
+ {
135
+ float: left;
136
+ line-height: 31px;
137
+ }
138
+
139
+ .action form
140
+ {
141
+ margin-top: 10px;
142
+ margin-bottom: 0;
143
+ }
144
+
145
+ .action form table
146
+ {
147
+ border-collapse: collapse;
148
+ border-spacing: 0;
149
+ margin-bottom: 10px;
150
+ width: 100%;
151
+ }
152
+
153
+ .action form table th
154
+ {
155
+ font-weight: bold;
156
+ }
157
+
158
+ .action form table th, .action form table td
159
+ {
160
+ padding-bottom: 5px;
161
+ text-align: left;
162
+ border-bottom: 1px solid #000000;
163
+ }
164
+
165
+ .action form table td
166
+ {
167
+ padding: 10px;
168
+ }
169
+
170
+ .action form table td input[type=text], .action form table td select
171
+ {
172
+ display: block;
173
+ margin-bottom: 5px;
174
+ }
175
+
176
+ .action .response
177
+ {
178
+ margin-top: 10px;
179
+ }
180
+
181
+ .action .description
182
+ {
183
+ margin-bottom: 20px;
184
+ }
@@ -0,0 +1,113 @@
1
+ $(document).ready(function()
2
+ {
3
+ $('.controller .resource a').click(function()
4
+ {
5
+ $(this).closest('.controller').find('.actions').toggleClass('hidden');
6
+ });
7
+
8
+ $('.controller .action .subresource a').click(function()
9
+ {
10
+ $(this).closest('.action').find('form').toggleClass('hidden');
11
+ });
12
+
13
+ $('.controller .action form .add').click(function(e)
14
+ {
15
+ var el = $(this);
16
+ var td = el.closest('td');
17
+
18
+ var input = td.find('input[type=text]:first');
19
+
20
+ if(input.length == 0) input = td.find('select:first');
21
+ if(input.length == 0) return;
22
+
23
+ var new_input = input.clone();
24
+
25
+ new_input.attr('disabled', false);
26
+ new_input.removeClass('hidden');
27
+
28
+ new_input.insertBefore(el);
29
+
30
+ td.find('.remove').removeClass('hidden');
31
+ });
32
+
33
+ $('.controller .action form .remove').click(function(e)
34
+ {
35
+ var el = $(this);
36
+ var td = el.closest('td');
37
+
38
+ td.find('input[type=text]:last').remove();
39
+ td.find('select:last').remove();
40
+
41
+ if(td.find('input[type=text]').length == 1 || td.find('select').length == 1)
42
+ el.addClass('hidden');
43
+ });
44
+
45
+ $('.controller .action form .clear').click(function(e)
46
+ {
47
+ var el = $(this);
48
+ var form = el.closest('form');
49
+
50
+ var response = form.find('.response');
51
+ response.addClass('hidden');
52
+
53
+ response.find('.url').html('');
54
+ response.find('.query').html('');
55
+ response.find('.code').html('');
56
+ response.find('.headers').html('');
57
+ response.find('.body').html('');
58
+
59
+ el.addClass('hidden');
60
+ });
61
+
62
+ $('.controller .action form input[type=submit]').click(function(e)
63
+ {
64
+ e.preventDefault();
65
+
66
+ var form = $(this).closest('form');
67
+
68
+ var data = $('.authentication form').serializeArray().concat(form.serializeArray());
69
+
70
+ data.push({'name': 'method', 'value': form.attr('method') || ''});
71
+ data.push({'name': 'action', 'value': form.attr('action') || ''});
72
+
73
+ data = $.grep(data, function(v, i)
74
+ {
75
+ if(v.name.match(/\[hash\]$/))
76
+ {
77
+ var kv = v.value.split('=');
78
+ if(kv.length == 2)
79
+ {
80
+ v.name = v.name.replace(/\[hash\]$/, '[' + $.trim(kv[0]) + ']');
81
+ v.value = $.trim(kv[1]);
82
+ }
83
+ }
84
+
85
+ return v.value.length > 0;
86
+ });
87
+
88
+ console.log(data);
89
+
90
+ $.ajax('/', {
91
+ data: $.param(data),
92
+ method: 'POST',
93
+ dataType: 'json',
94
+ complete: function(data)
95
+ {
96
+ var json = data.responseJSON || {};
97
+
98
+ var response = form.find('.response');
99
+ response.removeClass('hidden');
100
+
101
+ response.find('.url').html(json.url);
102
+ response.find('.query').html(json.query);
103
+ response.find('.code').html(json.code);
104
+ response.find('.headers').html(json.headers);
105
+ response.find('.body').html(json.body);
106
+
107
+ form.find('.clear').removeClass('hidden');
108
+ },
109
+ });
110
+
111
+ return false;
112
+ });
113
+ });