interdasting 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 (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: []