interdasting 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +23 -0
  4. data/app/assets/javascripts/interdasting/application.js +198 -0
  5. data/app/assets/stylesheets/interdasting/application.scss +51 -0
  6. data/app/controllers/interdasting/application_controller.rb +13 -0
  7. data/app/helpers/interdasting/application_helper.rb +24 -0
  8. data/app/views/docs/interdasting/_action.markdown.erb +23 -0
  9. data/app/views/docs/interdasting/_action.slim +43 -0
  10. data/app/views/docs/interdasting/_controller.markdown.erb +7 -0
  11. data/app/views/docs/interdasting/_controller.slim +9 -0
  12. data/app/views/docs/interdasting/_documentation.markdown.erb +4 -0
  13. data/app/views/docs/interdasting/_documentation.slim +3 -0
  14. data/app/views/docs/interdasting/_hash_param.slim +7 -0
  15. data/app/views/docs/interdasting/_version.slim +7 -0
  16. data/app/views/docs/interdasting/index.slim +3 -0
  17. data/app/views/docs/interdasting/show.markdown.erb +3 -0
  18. data/app/views/docs/interdasting/show.slim +4 -0
  19. data/app/views/layouts/interdasting/_modal.slim +12 -0
  20. data/app/views/layouts/interdasting/_navigation.slim +10 -0
  21. data/app/views/layouts/interdasting/application.slim +9 -0
  22. data/config/routes.rb +5 -0
  23. data/lib/interdasting.rb +30 -0
  24. data/lib/interdasting/documentation.rb +46 -0
  25. data/lib/interdasting/documentation/action.rb +97 -0
  26. data/lib/interdasting/documentation/controller.rb +57 -0
  27. data/lib/interdasting/engine.rb +12 -0
  28. data/lib/interdasting/parser.rb +105 -0
  29. data/lib/interdasting/router.rb +86 -0
  30. data/lib/interdasting/version.rb +3 -0
  31. data/lib/tasks/interdasting_tasks.rake +85 -0
  32. metadata +270 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6ba6bc08627a70924d0a296b836229b2bd9e8956
4
+ data.tar.gz: 7a4b2661e3e53b9894f4d7829a7ecb7ae0d20e61
5
+ SHA512:
6
+ metadata.gz: 7eedceccdd1c12f8fd99e846fc33c22b394f2e187fa7de900d8ced645f60399bcf72176440c87e5c6fa65da68891b30b8a81982a3291cfbca5f57183a2b5479d
7
+ data.tar.gz: 73b9763b4c8b1d2e08118af238aeeb405a37be75c3857af85a764e29caadc5824e87b94508618663826940808d8157ef5996c16f032c40c239493e03dcd4745d
@@ -0,0 +1,20 @@
1
+ Copyright 2015 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Interdasting'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/test_app/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
@@ -0,0 +1,198 @@
1
+ //= require jquery
2
+ //= require jquery_ujs
3
+ //= require bootstrap
4
+
5
+ $(document).ready(function () {
6
+ $('#try-out-modal').on('show.bs.modal', function (event) {
7
+ var $modal = $(this);
8
+ var $triggerButton = $(event.relatedTarget);
9
+ var $testButton = $modal.find('.btn.try');
10
+ $modal.find('h4.modal-title').text('' +
11
+ $triggerButton.data('request-method') + ' ' + $triggerButton.data('url')
12
+ );
13
+ $modal.find('.modal-body').html(
14
+ contentForModal(
15
+ $triggerButton.data('url'),
16
+ $triggerButton.data('request-method'),
17
+ $triggerButton.data('params'),
18
+ $triggerButton.data('saved_params'),
19
+ $triggerButton.data('saved_text')
20
+ )
21
+ );
22
+ $testButton.click(function(){
23
+ saveParams($modal.find('.modal-body'), $triggerButton);
24
+ var $response = $modal.find('.response')
25
+ $modal.find('.response-body').removeClass('hidden');
26
+ addSpinner($response);
27
+ $.ajax(
28
+ buildRequestParams($modal.find('.modal-body'), $triggerButton.data('url'), $triggerButton.data('request-method'))
29
+ ).done(function(data, textStatus, jqXHR) {
30
+ response = jqXHR.responseText
31
+ $response.html('');
32
+ $response.text(response);
33
+ $(window).trigger('resize');
34
+ });
35
+ });
36
+ }).on('hidden.bs.modal', function (e) {
37
+ var $modal = $(this);
38
+ $modal.find('h4.modal-title').text('Try it');
39
+ $modal.find('.modal-body').html('');
40
+ })
41
+
42
+ function buildRequestParams($object, url, method) {
43
+ var $inputs = $object.find('input[data-key]');
44
+ var $requestBody = $object.find('textarea');
45
+ var data = null;
46
+ var processData = true;
47
+
48
+ if ($inputs.length !== 0) {
49
+ data = {}
50
+ $inputs.each(function(i, $input) {
51
+ $input = $($input);
52
+ if (url.indexOf('{' + $input.data('key') + '}')) {
53
+ url = url.replace('{' + $input.data('key') + '}', $input.val())
54
+ } else {
55
+ data[$input.data('key')] = $input.val();
56
+ }
57
+ });
58
+ }
59
+ if ($requestBody.length !== 0) {
60
+ processData = false;
61
+ data = $requestBody.val();
62
+ }
63
+
64
+ return {
65
+ url: url,
66
+ type: method,
67
+ processData: processData,
68
+ data: data
69
+ }
70
+ }
71
+
72
+ function saveParams($object, $saveToObject) {
73
+ var $inputs = $object.find('input[data-key]');
74
+ var $requestBody = $object.find('textarea');
75
+
76
+ if ($inputs.length !== 0) {
77
+ params = {};
78
+ $inputs.each(function(i, $input) {
79
+ $input = $($input);
80
+ params[$input.data('key')] = $input.val();
81
+ });
82
+ $saveToObject.data('saved_params', params);
83
+ }
84
+
85
+ if ($requestBody.length !== 0) {
86
+ $saveToObject.data('saved_text', $requestBody.val());
87
+ }
88
+
89
+ return true;
90
+ }
91
+
92
+ function addSpinner($object) {
93
+ var message = '<center>'+
94
+ '<span class="glyphicon glyphicon-refresh gly-spin"></span>'+
95
+ ' Waiting for response...'+
96
+ '</center>';
97
+ $object.html(message);
98
+ }
99
+
100
+ function contentForModal(url, method, params, savedParams, savedText) {
101
+ var content = ''
102
+ content += '<h3>Request body</h3>'
103
+ if (method === 'GET' && params) {
104
+ content += paramsInputTable(params, savedParams)
105
+ } else {
106
+ var regex = /\{[^\s]+\}/;
107
+ var match = regex.exec(url);
108
+ if (match && match.length !== 0) {
109
+ tempParams = {}
110
+ $.each(match, function(i, m){ tempParams[m.replace(/[\{\}]/g,'')] = i });
111
+ content += paramsInputTable(tempParams, savedParams)
112
+ }
113
+ var saved = ''
114
+ if (savedText) saved = savedText;
115
+ content += '<textarea class="form-control" rows="6" cols="90">' + saved + '</textarea>';
116
+ }
117
+ if (method !== 'GET' && params) {
118
+ content += '<h4>Expected params</h4>'
119
+ content += paramsTable(params)
120
+ }
121
+ content += '<div class="response-body hidden">'
122
+ content += '<hr>';
123
+ content += '<h3>Response</h3>'
124
+ content += '<pre><code class="response"></code></pre>'
125
+ content += '</div>'
126
+
127
+ return content;
128
+ }
129
+
130
+ function paramsInputTable(params, savedParams) {
131
+ content = '<table class="table table-striped">';
132
+ content += '<thead>';
133
+ content += '<tr>'+
134
+ '<th>Param</th>'+
135
+ '<th>Value</th>'+
136
+ '</tr>';
137
+ content += '</thead>';
138
+ $.each(params, function(k, v) {
139
+ content += paramsInputTableRow(k, v, null, savedParams);
140
+ });
141
+ content += '</table>';
142
+ return content
143
+ }
144
+
145
+ function paramsInputTableRow(k, v, wrapper, savedParams) {
146
+ var content = '';
147
+ var saved = '';
148
+ if (wrapper) k = wrapper + '[' + k + ']';
149
+ if (savedParams && savedParams[k]) saved = savedParams[k];
150
+ if (typeof v === 'object') {
151
+ $.each(v, function(kk, vv) {
152
+ content += paramsInputTableRow(kk, vv, k, savedParams);
153
+ });
154
+ } else {
155
+ content += '<tr>'+
156
+ '<td>' + k + '</td>'+
157
+ '<td><input type="text" class="form-control" value="' + saved + '" data-key="' + k + '"></td>'+
158
+ '</tr>';
159
+ }
160
+ return content;
161
+ }
162
+
163
+ function paramsTable(params) {
164
+ content = '<table class="table table-striped">';
165
+ content += '<thead>';
166
+ content += '<tr>'+
167
+ '<th>Name</th>'+
168
+ '<th>Type</th>'+
169
+ '</tr>';
170
+ content += '</thead>';
171
+ $.each(params, function(k, v){
172
+ content += paramsTableRow(k, v, null);
173
+ });
174
+ content += '</table>';
175
+ return content
176
+ }
177
+
178
+ function paramsTableRow(k, v, wrapper) {
179
+ var content = '';
180
+ if (wrapper) k = wrapper + '[' + k + ']';
181
+ if (typeof v === 'object') {
182
+ $.each(v, function(kk, vv) {
183
+ content += paramsTableRow(kk, vv, k);
184
+ });
185
+ } else {
186
+ content += '<tr>'+
187
+ '<td>' + k + '</td>'+
188
+ '<td>' + v + '</td>'+
189
+ '</tr>';
190
+ }
191
+ return content;
192
+ }
193
+
194
+ function indentResponse(response) {
195
+ // TODO: code to indent the response depending on if it's a JSON or XML response
196
+ return response;
197
+ }
198
+ });
@@ -0,0 +1,51 @@
1
+ /*
2
+ *= require_tree .
3
+ *= require_self
4
+ */
5
+ @import "bootstrap-sprockets";
6
+ @import "bootstrap";
7
+
8
+ body {
9
+ padding-top: 70px;
10
+ }
11
+
12
+ .gly-spin {
13
+ -webkit-animation: spin 2s infinite linear;
14
+ -moz-animation: spin 2s infinite linear;
15
+ -o-animation: spin 2s infinite linear;
16
+ animation: spin 2s infinite linear;
17
+ }
18
+ @-moz-keyframes spin {
19
+ 0% {
20
+ -moz-transform: rotate(0deg);
21
+ }
22
+ 100% {
23
+ -moz-transform: rotate(359deg);
24
+ }
25
+ }
26
+ @-webkit-keyframes spin {
27
+ 0% {
28
+ -webkit-transform: rotate(0deg);
29
+ }
30
+ 100% {
31
+ -webkit-transform: rotate(359deg);
32
+ }
33
+ }
34
+ @-o-keyframes spin {
35
+ 0% {
36
+ -o-transform: rotate(0deg);
37
+ }
38
+ 100% {
39
+ -o-transform: rotate(359deg);
40
+ }
41
+ }
42
+ @keyframes spin {
43
+ 0% {
44
+ -webkit-transform: rotate(0deg);
45
+ transform: rotate(0deg);
46
+ }
47
+ 100% {
48
+ -webkit-transform: rotate(359deg);
49
+ transform: rotate(359deg);
50
+ }
51
+ }
@@ -0,0 +1,13 @@
1
+ module Interdasting
2
+ class ApplicationController < ActionController::Base
3
+ def index
4
+ @docs = Interdasting.documentation
5
+ render 'docs/interdasting/index'
6
+ end
7
+
8
+ def show
9
+ @doc = Interdasting.documentation_for_version(params[:version])
10
+ render 'docs/interdasting/show'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module Interdasting
2
+ module ApplicationHelper
3
+ def doc_id(doc)
4
+ "version_#{web_safe(doc.version)}"
5
+ end
6
+
7
+ def controller_id(controller)
8
+ "#{doc_id(controller.documentation)}_controller_"\
9
+ "#{web_safe(controller.name)}"
10
+ end
11
+
12
+ def action_id(action)
13
+ "#{controller_id(action.controller)}_action_#{web_safe(action.name)}"
14
+ end
15
+
16
+ def method_id(action, method)
17
+ "#{action_id(action)}_method_#{web_safe(method)}"
18
+ end
19
+
20
+ def web_safe(string)
21
+ string.to_s.humanize.sub(' ', '').underscore
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ ### <%= action.name %>
2
+
3
+ <% action.methods.each do |method| %>
4
+ #### <%= method.upcase %>
5
+
6
+ > __URL:__ <%= '' || action.url %>
7
+
8
+ <% if action.description(method) %>
9
+ >__DESCRIPTION:__ <%= action.description(method) %>
10
+ <% end %>
11
+
12
+ <% if action.params(method) %>
13
+ >__PARAMS:__
14
+
15
+ >| Name | Type |
16
+ >| ---- | ---- |
17
+ <% action.params(method).each do |k, v| %>
18
+ >| <%= k %> | <%= v %> |
19
+ <% end %>
20
+ <% end %>
21
+
22
+
23
+ <% end %>
@@ -0,0 +1,43 @@
1
+ .panel.panel-default
2
+ .panel-heading
3
+ h4.panel-title
4
+ = link_to "#{action.name}", "##{action_id(action)}", data: { toggle: 'collapse' }
5
+ .panel-collapse.collapse id="#{action_id(action)}"
6
+ .panel-body
7
+ ul.nav.nav-tabs
8
+ - action.methods.each_with_index do |method, index|
9
+ li class="#{index == 0 ? 'active' : ''}"
10
+ = link_to method.upcase, "##{method_id(action, method)}", data: { toggle: 'tab' }
11
+ .tab-content
12
+ - action.methods.each_with_index do |method, index|
13
+ .tab-pane class="#{index == 0 ? 'active' : ''}" id="#{method_id(action, method)}"
14
+
15
+ .col-md-6
16
+ .row
17
+ .col-xs-12
18
+ - if action.url(method)
19
+ h4 URL
20
+ = action.url
21
+ .col-xs-12
22
+ - if action.description(method)
23
+ h4 Description
24
+ = simple_format(action.description(method))
25
+ .col-md-6
26
+ .row
27
+ .col-xs-12
28
+ - if action.params(method)
29
+ h4 Params
30
+ table.table.table-striped
31
+ thead
32
+ tr
33
+ th Name
34
+ th Type
35
+ tbody
36
+ = render 'docs/interdasting/hash_param', hash: action.params(method), wrap: nil
37
+
38
+ - if action.url(method)
39
+ .col-xs-12
40
+ hr
41
+ = link_to '#', class: 'btn btn-primary try-out', data: { request_method: method, params: action.params(method), url: action.url, toggle: 'modal', target: '#try-out-modal' } do
42
+ span.glyphicon.glyphicon-new-window
43
+ | Try it out
@@ -0,0 +1,7 @@
1
+ ## <%= controller.name %>
2
+
3
+ <% controller.actions.each do |action| %>
4
+ <%= render 'docs/interdasting/action.markdown.erb', action: action %>
5
+
6
+ ___
7
+ <% end %>
@@ -0,0 +1,9 @@
1
+ .panel.panel-default
2
+ .panel-heading
3
+ h4.panel-title
4
+ = link_to "#{controller.name}", "##{controller_id(controller)}", data: { toggle: 'collapse' }
5
+ .panel-collapse.collapse id="#{controller_id(controller)}"
6
+ .panel-body
7
+ .panel-group id="#{controller_id(controller)}_accordion"
8
+ - controller.actions.each do |action|
9
+ = render 'docs/interdasting/action', action: action
@@ -0,0 +1,4 @@
1
+ <% doc.controllers.each do |controller| %>
2
+ <%= render 'docs/interdasting/controller.markdown.erb', controller: controller %>
3
+
4
+ <% end %>
@@ -0,0 +1,3 @@
1
+ .panel-group id="#{doc_id(doc)}_accordion"
2
+ - doc.controllers.each do |controller|
3
+ = render 'docs/interdasting/controller', controller: controller
@@ -0,0 +1,7 @@
1
+ - hash.each do |k, v|
2
+ - if v.is_a?(Hash)
3
+ = render 'docs/interdasting/hash_param', hash: v, wrap: wrap ? "#{wrap}[#{k}]" : k
4
+ - else
5
+ tr
6
+ td = wrap ? "#{wrap}[#{k}]" : k
7
+ td = v
@@ -0,0 +1,7 @@
1
+ .panel.panel-default
2
+ .panel-heading
3
+ h4.panel-title
4
+ = link_to "Version #{doc.version}", "##{doc_id(doc)}", data: { toggle: 'collapse' }
5
+ .panel-collapse.collapse id="#{doc_id(doc)}"
6
+ .panel-body
7
+ = render 'docs/interdasting/documentation', doc: doc
@@ -0,0 +1,3 @@
1
+ .panel-group#versions_accordion
2
+ - @docs.each do |doc|
3
+ = render 'docs/interdasting/version', doc: doc
@@ -0,0 +1,3 @@
1
+ # <%= @doc.version %>
2
+
3
+ <%= render 'docs/interdasting/documentation.markdown.erb', doc: @doc %>
@@ -0,0 +1,4 @@
1
+ h1= @doc.version
2
+ hr
3
+ .col-xs-12
4
+ = render 'docs/interdasting/documentation', doc: @doc
@@ -0,0 +1,12 @@
1
+ .modal.fade#try-out-modal
2
+ .modal-dialog
3
+ .modal-content
4
+ .modal-header
5
+ button.close data-dismiss='modal'
6
+ h4.modal-title Try it
7
+ .modal-body
8
+ .modal-footer
9
+ button.btn.btn-default data-dismiss='modal'
10
+ | Close
11
+ button.btn.btn-primary.try
12
+ | Try it
@@ -0,0 +1,10 @@
1
+ - if defined?(interdasting)
2
+ .navbar.navbar-default.navbar-fixed-top
3
+ .container-fluid
4
+ .navbar-header
5
+ = link_to 'Documentation', interdasting.root_path, class: 'navbar-brand'
6
+ .collapse.navbar-collapse
7
+ ul.nav.navbar-nav
8
+ li = link_to 'All versions', interdasting.root_path
9
+ - Interdasting::Router.versions.each do |v|
10
+ li = link_to v.to_s, interdasting.version_path(version: v)
@@ -0,0 +1,9 @@
1
+ head
2
+ title Interdasting
3
+ = stylesheet_link_tag "interdasting/application", media: "all"
4
+ = javascript_include_tag "interdasting/application"
5
+ body
6
+ = render 'layouts/interdasting/navigation'
7
+ .container
8
+ = yield
9
+ = render 'layouts/interdasting/modal'
@@ -0,0 +1,5 @@
1
+ Interdasting::Engine.routes.draw do
2
+ root 'application#index'
3
+ get 'versions', to: 'application#index', as: 'versions'
4
+ get 'version/:version', to: 'application#show', as: 'version'
5
+ end
@@ -0,0 +1,30 @@
1
+ require 'pry'
2
+ require 'slim'
3
+ require 'indentation-parser'
4
+ require 'interdasting/engine'
5
+ require 'interdasting/router'
6
+ require 'interdasting/parser'
7
+ require 'interdasting/documentation'
8
+
9
+ module Interdasting
10
+ def self.documentation
11
+ Router.api_full.map do |version, controller|
12
+ Documentation.new(version, controller)
13
+ end
14
+ end
15
+
16
+ def self.documentation_for_version(version)
17
+ Documentation.new(version, Router.api_full[version])
18
+ end
19
+
20
+ def self.documentation_for_files(files, version_name = 'Unknown')
21
+ fake_controlers = files.map do |file|
22
+ fake_controller = {
23
+ path: file,
24
+ actions: Hash[Parser.method_comments(file).keys.map { |m, _c| [m, []] }]
25
+ }
26
+ [File.basename(file, '.rb'), fake_controller]
27
+ end
28
+ Documentation.new(version_name, Hash[fake_controlers])
29
+ end
30
+ end
@@ -0,0 +1,46 @@
1
+ require 'interdasting/documentation/controller'
2
+ require 'interdasting/documentation/action'
3
+
4
+ module Interdasting
5
+ class Documentation
6
+ attr_reader :version
7
+ attr_reader :controllers
8
+
9
+ def initialize(version, controllers_hash)
10
+ @version = version
11
+ @controllers = []
12
+ @controllers_in = controllers_hash
13
+ generate
14
+ end
15
+
16
+ def should_update?
17
+ should_update = false
18
+ controllers.each { |c| should_update ||= c.should_update? }
19
+ should_update
20
+ end
21
+
22
+ def update
23
+ controllers.each(&:update)
24
+ self
25
+ end
26
+
27
+ def update!
28
+ controllers.each(&:update!)
29
+ self
30
+ end
31
+
32
+ private
33
+
34
+ def generate
35
+ build_controllers
36
+ update
37
+ end
38
+
39
+ def build_controllers
40
+ @controllers = []
41
+ @controllers_in.each do |n, v|
42
+ @controllers << Controller.new(n, v[:path], v[:actions], self)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,97 @@
1
+ module Interdasting
2
+ class Documentation
3
+ class Action
4
+ attr_reader :name
5
+ attr_reader :controller
6
+ attr_reader :comments
7
+
8
+ def initialize(name, comments, methods, params, controller)
9
+ @name = name
10
+ @comments = comments
11
+ @methods = sanitize_methods(methods)
12
+ @params = params
13
+ @params.delete(:version) if params
14
+ @params = @params.presence
15
+ @controller = controller
16
+ generate
17
+ end
18
+
19
+ def url(method = default_method)
20
+ (@doc[method] && @doc[method]['URL']) || @doc['URL'] ||
21
+ router_url(method)
22
+ end
23
+
24
+ def description(method = default_method)
25
+ (@doc[method] && @doc[method]['DOC']) || @doc['DOC']
26
+ end
27
+
28
+ def params(method = default_method)
29
+ (@doc[method] && @doc[method]['PARAMS']) || @doc['PARAMS'] ||
30
+ @params
31
+ end
32
+
33
+ def default_method
34
+ methods.first || 'GET'
35
+ end
36
+
37
+ def methods
38
+ @methods.presence ||
39
+ @doc.keys.select { |m| Parser.http_keywords.include?(m) }
40
+ end
41
+
42
+ private
43
+
44
+ def generate
45
+ return @doc = {} unless @comments.present?
46
+ @doc = Interdasting::Parser.parse_comments(@comments).value
47
+ end
48
+
49
+ def _routes
50
+ Interdasting::Router.app_routes
51
+ end
52
+
53
+ def sanitize_methods(methods)
54
+ return unless methods
55
+ methods.map do |m|
56
+ if m.is_a?(String)
57
+ m
58
+ else
59
+ m.to_s.sub('(?-mix:^', '').sub('$)', '').split('|')
60
+ end
61
+ end.flatten
62
+ end
63
+
64
+ def router_url(method = default_method)
65
+ route = _routes.url_for(url_params(method))
66
+ route = route.split('?').first unless method == 'GET'
67
+ CGI.unescape(route)
68
+ rescue ActionController::UrlGenerationError
69
+ nil
70
+ end
71
+
72
+ def url_params(method = default_method)
73
+ hash = {}
74
+ hash = deflated_url_params(params(method)) if @params
75
+ hash.merge(
76
+ controller: controller.full_name.downcase,
77
+ action: name,
78
+ version: controller.documentation.version,
79
+ only_path: true
80
+ )
81
+ end
82
+
83
+ def deflated_url_params(hash, wrapper = nil)
84
+ nh = {}
85
+ hash.each do |k, v|
86
+ new_v = wrapper ? "#{wrapper}[#{k}]" : k
87
+ if v.is_a?(Hash)
88
+ nh[k] = deflated_url_params(v, k)
89
+ else
90
+ nh[k] = "{#{new_v}}"
91
+ end
92
+ end
93
+ nh.symbolize_keys
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,57 @@
1
+ module Interdasting
2
+ class Documentation
3
+ class Controller
4
+ attr_accessor :name
5
+ attr_accessor :file
6
+ attr_reader :documentation
7
+ attr_reader :actions
8
+
9
+ def initialize(name, file, actions_hash, documentation)
10
+ @file_md5 = ''
11
+ @actions = []
12
+ @actions_in = actions_hash
13
+ self.name = name
14
+ self.file = file
15
+ @documentation = documentation
16
+ update!
17
+ end
18
+
19
+ def update
20
+ should_update? && update!
21
+ end
22
+
23
+ def update!
24
+ @file_md5 = generate_hash
25
+ generate
26
+ end
27
+
28
+ def should_update?
29
+ return false if generate_hash == @file_md5
30
+ true
31
+ end
32
+
33
+ def name
34
+ return unless @name
35
+ @name.split('/').last.sub('_controller', '').humanize
36
+ end
37
+
38
+ def full_name
39
+ @name
40
+ end
41
+
42
+ private
43
+
44
+ def generate
45
+ @actions = []
46
+ comments = Interdasting::Parser.method_comments(file)
47
+ @actions_in.each do |name, action|
48
+ @actions << Action.new(name, comments[name], action[:methods], action[:params], self)
49
+ end
50
+ end
51
+
52
+ def generate_hash
53
+ Digest::MD5.file(file).hexdigest
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,12 @@
1
+ module Interdasting
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Interdasting
4
+ require 'jquery-rails'
5
+ require 'bootstrap-sass'
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec
9
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,105 @@
1
+ module Interdasting
2
+ module Parser
3
+ class << self
4
+ def comments_for_method(method_name, file_path)
5
+ method_comments(file_path)[method_name.to_s]
6
+ end
7
+
8
+ def method_comments(file_path)
9
+ comments = {}
10
+ temp_comment = []
11
+ File.read(file_path).each_line do |line|
12
+ if extract_method_comment(line, comments, temp_comment)
13
+ temp_comment = []
14
+ end
15
+ end
16
+ clean_comments(comments)
17
+ end
18
+
19
+ def parse_comments(comments)
20
+ indentation_parser.read(comments, {})
21
+ end
22
+
23
+ def keywords
24
+ http_keywords + parser_keywords
25
+ end
26
+
27
+ def http_keywords
28
+ %w(GET POST PUT PATCH DELETE)
29
+ end
30
+
31
+ def parser_keywords
32
+ string_keywords + hash_keywords
33
+ end
34
+
35
+ private
36
+
37
+ def string_keywords
38
+ %w(DOC URL)
39
+ end
40
+
41
+ def hash_keywords
42
+ %w(PARAMS)
43
+ end
44
+
45
+ def indentation_parser
46
+ IndentationParser.new do |p|
47
+ indentation_parser_default(p)
48
+ indentation_parser_leafs(p)
49
+ end
50
+ end
51
+
52
+ def indentation_parser_default(p)
53
+ p.default do |parent, source|
54
+ parent ||= {}
55
+ words = source.split
56
+ keyword, key = words.first.upcase, words.first
57
+ if words.count == 1 && keywords.include?(keyword)
58
+ parent[keyword] = string_keywords.include?(keyword) ? '' : {}
59
+ elsif words.count == 1 && parent.is_a?(Hash)
60
+ parent[key] = {}
61
+ end
62
+ end
63
+ end
64
+
65
+ def indentation_parser_leafs(p)
66
+ p.on_leaf do |parent, source|
67
+ case parent
68
+ when String
69
+ parent << ' ' if parent.length != 0
70
+ parent << source.try(:strip) || ''
71
+ when Hash
72
+ k, v = source.split(':', 2)
73
+ parent[k] = v ? v.try(:strip) : {}
74
+ end
75
+ end
76
+ end
77
+
78
+ def extract_method_comment(line, comments = {}, temp_comment = [])
79
+ return true unless valid_line?(line)
80
+ if line =~ /^\s*def\s+\w+$/
81
+ comments[method_name(line)] = temp_comment.join("\n")
82
+ true
83
+ else
84
+ temp_comment << line
85
+ false
86
+ end
87
+ end
88
+
89
+ def valid_line?(line)
90
+ line =~ /^\s*#.*$/ || line =~ /^\s*def\s+\w+$/ || line =~ /^\s+$/
91
+ end
92
+
93
+ def method_name(line)
94
+ line.match(/^\s*def\s+\w+$/).to_s.split(' ').last
95
+ end
96
+
97
+ def clean_comments(comments)
98
+ comments.each do |k, v|
99
+ comments[k] = v.gsub(/^\s*#\s?/, '').gsub(/\n+/, "\n")
100
+ end
101
+ comments
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,86 @@
1
+ module Interdasting
2
+ module Router
3
+ class << self
4
+ def api_full
5
+ result = {}
6
+ versions.each { |v| result[v] = {} }
7
+ result.each { |k, v| fill_controllers_hash_for_version(k, v) }
8
+ result
9
+ end
10
+
11
+ def api_controller_paths(controllers = api_controllers)
12
+ cp = controllers.map do |c|
13
+ c.instance_methods(false).map do |m|
14
+ c.instance_method(m).source_location.first
15
+ end
16
+ end
17
+ cp.flatten.uniq.compact
18
+ end
19
+
20
+ def api_controllers(names = api_controller_names)
21
+ names.map do |cn|
22
+ cn += '_controller'
23
+ cn.classify.constantize
24
+ end
25
+ end
26
+
27
+ def api_controller_names
28
+ ar = routes.named_routes.select { |_k, v| v.defaults[:rp_prefix] }
29
+ ar.values.map { |r| r.defaults[:controller] }.uniq
30
+ end
31
+
32
+ def routes_for_version(version)
33
+ routes.to_a.select { |v| v && v.defaults[:version] == version }
34
+ end
35
+
36
+ def versions
37
+ routes.to_a.map { |r| r && r.defaults[:version] }.uniq.compact
38
+ end
39
+
40
+ def routes
41
+ app_routes.routes
42
+ end
43
+
44
+ def app_routes
45
+ Rails.application.class.routes
46
+ end
47
+
48
+ private
49
+
50
+ def fill_controllers_hash_for_version(version, hash)
51
+ routes = routes_for_version(version)
52
+ routes.each do |r|
53
+ route_controller(hash, r)
54
+ end
55
+ end
56
+
57
+ def route_controller(ch, r)
58
+ cn = r.defaults[:controller]
59
+ ch[cn] ||= {}
60
+ ch[cn][:path] ||= api_controller_paths(api_controllers([cn])).first
61
+ ch[cn][:actions] ||= {}
62
+ route_action(ch[cn][:actions], r)
63
+ end
64
+
65
+ def route_action(hash, r)
66
+ ah = hash[r.defaults[:action]] ||= {}
67
+ ah[:params] = route_params(r)
68
+ route_methods(ah, r)
69
+ end
70
+
71
+ def route_params(r)
72
+ Hash[
73
+ r.required_parts.map do |name|
74
+ type = r.constraints[name.to_sym] || '???'
75
+ [name, type]
76
+ end
77
+ ]
78
+ end
79
+
80
+ def route_methods(hash, r)
81
+ hash[:methods] ||= []
82
+ hash[:methods] << r.constraints[:request_method]
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,3 @@
1
+ module Interdasting
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,85 @@
1
+ namespace :interdasting do
2
+ desc 'Generates static HTML documentation files'
3
+ generator_args = [:version_name, :input_files, :output_folder]
4
+ task :generate, generator_args => :environment do |_task, args|
5
+ version, input, output = sanitize_generator_input(args)
6
+ fail('A version name has to be provided') unless version
7
+ fail('At leas one input file has to be provided') unless input
8
+
9
+ doc = Interdasting.documentation_for_files(input, version)
10
+
11
+ template_folder = generator_template_folder
12
+ layout_path = template_folder + '/layouts/interdasting/application.slim'
13
+ template_path = template_folder + '/docs/interdasting/show.slim'
14
+
15
+ prepare_generator_output(output, version, 'html')
16
+
17
+ view_builder = generator_view_builder(template_folder)
18
+ view_builder.instance_variable_set(:@doc, doc)
19
+ layout = Slim::Template.new { File.open(layout_path, 'r').read }
20
+ view = Slim::Template.new { File.open(template_path, 'r').read }
21
+ html = layout.render(view_builder) do
22
+ view.render(view_builder).html_safe
23
+ end
24
+ File.open(output, 'w+') do |file|
25
+ file.puts(html)
26
+ end
27
+ end
28
+ task :gen, generator_args => :generate
29
+
30
+ desc 'Generates static MARKDOWN documentation files'
31
+ md_generator_args = generator_args
32
+ task :generate_markdown, md_generator_args => :environment do |_task, args|
33
+ version, input, output = sanitize_generator_input(args)
34
+
35
+ doc = Interdasting.documentation_for_files(input, version)
36
+
37
+ template_folder = generator_template_folder
38
+ template_path = template_folder + '/docs/interdasting/show.markdown.erb'
39
+
40
+ prepare_generator_output(output, version, 'markdown')
41
+
42
+ view_builder = generator_view_builder(template_folder)
43
+ view_builder.instance_variable_set(:@doc, doc)
44
+ view = ERB.new(File.open(template_path, 'r').read)
45
+ html = view.result(view_builder.instance_eval { binding })
46
+ File.open(output, 'w+') do |file|
47
+ file.puts(html)
48
+ end
49
+ end
50
+ task :generate_md, md_generator_args => :generate_markdown
51
+ task :gen_md, md_generator_args => :generate_markdown
52
+
53
+ ###############
54
+ ### HELPERS ###
55
+ ###############
56
+
57
+ def sanitize_generator_input(args)
58
+ version = args[:version_name].presence
59
+ # TODO: Potential bug with pathnames containing spaces
60
+ input = args[:input_files] && args[:input_files].split.presence
61
+ output = args[:output_folder].presence ||
62
+ Rails.root.join('public', 'system', 'documentation').to_s
63
+ fail('A version name has to be provided') unless version
64
+ fail('At leas one input file has to be provided') unless input
65
+ [version, input, output]
66
+ end
67
+
68
+ def generator_template_folder
69
+ File.expand_path('../../../app/views', __FILE__)
70
+ end
71
+
72
+ def prepare_generator_output(output, name, ext)
73
+ FileUtils.mkdir_p(output)
74
+ output = output[-1] == '/' ? output : output << '/'
75
+ output << "#{name}.#{ext}"
76
+ end
77
+
78
+ def generator_view_builder(template_folder)
79
+ lookup_context = ActionView::LookupContext.new(template_folder)
80
+ view_builder = ActionView::Base.new(lookup_context)
81
+ view_builder.extend ActionView::Helpers
82
+ view_builder.extend Interdasting::ApplicationHelper
83
+ view_builder
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,270 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: interdasting
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Stanko Krtalić Rusendić
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: slim-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: jquery-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sass-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bootstrap-sass
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.3'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: indentation-parser
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.2'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: factory_girl_rails
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.5'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.5'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rocket_pants
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.10'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.10'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.10'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.10'
153
+ - !ruby/object:Gem::Dependency
154
+ name: pry-rails
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.3'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.3'
167
+ - !ruby/object:Gem::Dependency
168
+ name: better_errors
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '2.1'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.1'
181
+ - !ruby/object:Gem::Dependency
182
+ name: binding_of_caller
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '0.7'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '0.7'
195
+ - !ruby/object:Gem::Dependency
196
+ name: sqlite3
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '1.3'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '1.3'
209
+ description: Unintrusive interactive API documentation generator for rails apps
210
+ email:
211
+ - stanko.krtalic@gmial.com
212
+ executables: []
213
+ extensions: []
214
+ extra_rdoc_files: []
215
+ files:
216
+ - MIT-LICENSE
217
+ - Rakefile
218
+ - app/assets/javascripts/interdasting/application.js
219
+ - app/assets/stylesheets/interdasting/application.scss
220
+ - app/controllers/interdasting/application_controller.rb
221
+ - app/helpers/interdasting/application_helper.rb
222
+ - app/views/docs/interdasting/_action.markdown.erb
223
+ - app/views/docs/interdasting/_action.slim
224
+ - app/views/docs/interdasting/_controller.markdown.erb
225
+ - app/views/docs/interdasting/_controller.slim
226
+ - app/views/docs/interdasting/_documentation.markdown.erb
227
+ - app/views/docs/interdasting/_documentation.slim
228
+ - app/views/docs/interdasting/_hash_param.slim
229
+ - app/views/docs/interdasting/_version.slim
230
+ - app/views/docs/interdasting/index.slim
231
+ - app/views/docs/interdasting/show.markdown.erb
232
+ - app/views/docs/interdasting/show.slim
233
+ - app/views/layouts/interdasting/_modal.slim
234
+ - app/views/layouts/interdasting/_navigation.slim
235
+ - app/views/layouts/interdasting/application.slim
236
+ - config/routes.rb
237
+ - lib/interdasting.rb
238
+ - lib/interdasting/documentation.rb
239
+ - lib/interdasting/documentation/action.rb
240
+ - lib/interdasting/documentation/controller.rb
241
+ - lib/interdasting/engine.rb
242
+ - lib/interdasting/parser.rb
243
+ - lib/interdasting/router.rb
244
+ - lib/interdasting/version.rb
245
+ - lib/tasks/interdasting_tasks.rake
246
+ homepage: http://github.com/stankec
247
+ licenses:
248
+ - MIT
249
+ metadata: {}
250
+ post_install_message:
251
+ rdoc_options: []
252
+ require_paths:
253
+ - lib
254
+ required_ruby_version: !ruby/object:Gem::Requirement
255
+ requirements:
256
+ - - ">="
257
+ - !ruby/object:Gem::Version
258
+ version: '0'
259
+ required_rubygems_version: !ruby/object:Gem::Requirement
260
+ requirements:
261
+ - - ">="
262
+ - !ruby/object:Gem::Version
263
+ version: '0'
264
+ requirements: []
265
+ rubyforge_project:
266
+ rubygems_version: 2.2.2
267
+ signing_key:
268
+ specification_version: 4
269
+ summary: Interactive API documentation for rails apps
270
+ test_files: []