cabbage_doc 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 (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
+ });