grape-doc 0.0.1.alpha → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6cc913997d475f2662a9a46193ef7a20f09a6512
4
- data.tar.gz: d424ec6fd9bf1bbaf98c077f734ae3cf68ebd9cf
3
+ metadata.gz: 78159d4d2d7852fadb62e8caf6b7aad1dbd0f772
4
+ data.tar.gz: 4fcba1b40c4a766cfb9d2d06e48e2563c75bd6b1
5
5
  SHA512:
6
- metadata.gz: 9e65db9a8cd40cf01d25116f2432791f235c3fef9d8b7dafaeb3ae715d554478b72c4ae0645ff81e7e50d25ea799fa4fae8b16c3cd20eed443239946597211a9
7
- data.tar.gz: e4c5014b787461dde2e770b3f50975f9b6ba50afcd0eee15768856f6735efa3e34cd7aa647dc272ca8fcdcc9d3ad6ba5d3ce0b034ebd5032cdb34308371d46ac
6
+ metadata.gz: b7780a35011633490200162fda618beb50ff8e82052c631690b99927fd82c4380d21d05a2fca12080373e32fc39b0041aaba4f2c067c8cc0142371c183991f22
7
+ data.tar.gz: 193c15ecde4b52289c5148b1b84aa4859f5561319b84c55ffcc7e40f1d2189962fb6e8632f0b62feab0c65e86ed54b2911be75fa3c2d64c6997f45ec4bfa1cf6
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
1
  source :rubygems
2
- gemspec
2
+ gemspec
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1.alpha
1
+ 0.1.0
data/grape-doc.gemspec CHANGED
@@ -20,7 +20,9 @@ Gem::Specification.new do |spec|
20
20
  spec.add_development_dependency "rake"
21
21
 
22
22
  spec.add_dependency "grape"
23
+ spec.add_dependency "loader"
24
+ spec.add_dependency "RedCloth"
23
25
  spec.add_dependency "rack-test"
24
- spec.add_dependency "rack-test-poc"
26
+ spec.add_dependency "rack-test-poc",">= 1.0.2"
25
27
 
26
28
  end
@@ -0,0 +1,30 @@
1
+ module GrapeDoc
2
+ class ApiDocParts
3
+
4
+ class H1 < StringObject
5
+ end
6
+
7
+ class H2 < H1
8
+ end
9
+
10
+ class H3 < H1
11
+ end
12
+
13
+ class H4 < H1
14
+ end
15
+
16
+ class H5 < H1
17
+ end
18
+
19
+ class H6 < H1
20
+ end
21
+
22
+ class Block < StringObject
23
+ self.markdown = "bq"
24
+ end
25
+
26
+ class Br < StringBasic
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ module GrapeDoc
2
+ class ApiDocParts
3
+
4
+ class Link < StringObject
5
+
6
+ def initialize(text,url)
7
+ super(text)
8
+ @url = url
9
+ end
10
+
11
+ def to_textile
12
+ "\"#{self}\":#{@url}"
13
+ end
14
+
15
+ end
16
+
17
+ class PictureLink < StringObjectEnded
18
+ self.markdown = '!'
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ module GrapeDoc
2
+ class ApiDocParts
3
+
4
+ class List < ArrayObject
5
+
6
+ self.markdown = '*'
7
+
8
+ def initialize(obj)
9
+ obj = case obj
10
+ when Array
11
+ obj
12
+
13
+ when String,Symbol
14
+ obj.to_s.split("\n")
15
+
16
+ else
17
+ raise(ArgumentError,'unknown format given for list object')
18
+
19
+ end
20
+
21
+ super(obj)
22
+
23
+ end
24
+
25
+ end
26
+
27
+ class NumericalList < List
28
+ self.markdown = '#'
29
+ end
30
+
31
+
32
+
33
+ end
34
+ end
@@ -0,0 +1,56 @@
1
+ module GrapeDoc
2
+ class ApiDocParts
3
+
4
+ module Parser
5
+ class << self
6
+
7
+ def parse(object)
8
+ case object
9
+
10
+ when Array
11
+ object.map{|e|
12
+ case e
13
+ when Array
14
+ e.dup
15
+
16
+ else
17
+ self.format_parse(*e)
18
+
19
+ end
20
+ }
21
+
22
+ else
23
+ # self.format_parse(object)
24
+ object
25
+
26
+ end
27
+ end
28
+
29
+ def format_parse(text,*args)
30
+ text = text.dup.to_s
31
+ args.each do |command|
32
+ case command.to_s.downcase
33
+
34
+ when /^bold/
35
+ text.replace("*#{text}*")
36
+
37
+ when /^italic/
38
+ text.replace("__#{text}__")
39
+
40
+ when /^underlined/
41
+ text.replace("+#{text}+")
42
+
43
+ when /^superscript/
44
+ text.replace("^#{text}^")
45
+
46
+ end
47
+ end;return text
48
+
49
+ end
50
+
51
+
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,20 @@
1
+ module GrapeDoc
2
+ class ApiDocParts
3
+
4
+ class Raw < StringBasic
5
+
6
+ def initialize(text)
7
+ super(text)
8
+ end
9
+
10
+ def to_textile
11
+ "<pre>#{self.to_s}</pre>"
12
+ end
13
+
14
+ end
15
+
16
+ class Text < StringBasic
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module GrapeDoc
2
+ class ApiDocParts
3
+
4
+ class Sidebar < StringObject
5
+ def to_textile
6
+ [
7
+ '<div style="float:right;">',
8
+ self.to_s,
9
+ '</div>'
10
+ ].join("\n")
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module GrapeDoc
2
+ class ApiDocParts
3
+
4
+ class Table < ArrayObject
5
+
6
+ def pust(*args)
7
+ raise(ArgumentError,'invalid input for table!') if args.any?{|e| !(e.class <= Array) }
8
+ super(*args)
9
+ end
10
+
11
+ def to_textile
12
+ self.map{|row| row.join(' | ') }.join("\n")
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ module GrapeDoc
2
+
3
+ class ApiDocumentation < Array
4
+
5
+ def create(type,*args)
6
+ raise(ArgumentError,'invalid type') unless [String,Symbol].any?{ |klass| type.class <= klass }
7
+ return Helpers.constantize("GrapeDoc::ApiDocParts::#{Helpers.camelize(type)}").new(*args)
8
+ end
9
+
10
+ def add(type,*args)
11
+ self.push(create(type,*args))
12
+ end
13
+
14
+ def br(int=1)
15
+ raise unless int.class <= Integer
16
+ int.times{self.push(ApiDocParts::Br.new("\n"))}
17
+ end
18
+
19
+ def to_textile
20
+ require 'RedCloth'
21
+ RedCloth.new(self.map{|e| e.to_textile }.join("\n"))
22
+ end;alias to_s to_textile
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,127 @@
1
+ module GrapeDoc
2
+ class Generator
3
+
4
+ def initialize(opts={})
5
+
6
+ raise(ArgumentError,'invalid options given') unless opts.class <= Hash
7
+ opts.merge!(
8
+ {
9
+ format: 'html'
10
+ }.merge(opts)
11
+ )
12
+
13
+ process_head
14
+ process_table_of_content
15
+ process_endpoints
16
+
17
+ end
18
+
19
+ def document
20
+ @api_doc ||= ApiDocumentation.new
21
+ end
22
+
23
+ def save
24
+ File.write File.join(Helpers.doc_folder_path,'api_doc.html'),
25
+ RedCloth.new(document.to_textile).to_html
26
+
27
+ end;alias save! save
28
+
29
+ private
30
+
31
+ def process_head
32
+ document.add :h1, "Rest Api Documentation (#{RackTestPoc.root.split(File::Separator)[-1]})"
33
+ end
34
+
35
+ def process_table_of_content
36
+ #TODO: Table of content here!
37
+ end
38
+
39
+ def process_endpoints
40
+
41
+ # Iterates over all subclasses (direct and indirect)
42
+ Helpers.each_grape_class do |rest_api_model|
43
+ rest_api_model.routes.each do |route|
44
+
45
+ document.add :h2, "#{route.route_method.to_s.upcase}: #{route.route_path}"
46
+ document.add :h3, 'Request'
47
+ document.add :h4, 'description'
48
+ var = case route.route_description
49
+
50
+ when Hash
51
+ (route.route_description.find{|k,v| k == 'desc' || k == :desc } || [])[1] || ''
52
+
53
+ when Array
54
+ route.route_description
55
+
56
+ else
57
+ route.route_description.to_s
58
+
59
+ end
60
+
61
+ document.add :list,[*var]
62
+
63
+ if route.route_params.length > 0
64
+ document.add :h4, 'params'
65
+
66
+ route.route_params.each do |key,value|
67
+ document.add :list,[
68
+ document.create(:text,key.to_s,:bold),
69
+ document.create(:list,value.map{ |k,v| "#{k}: #{v}" })
70
+ ]
71
+
72
+ end
73
+
74
+ end
75
+
76
+ route_path_var = route.route_path.to_s.sub(/\(\.:format\)$/,'')
77
+ @poc_data ||= Helpers.poc_data
78
+ if @poc_data && @poc_data.find{|k,v| k =~ /^#{route_path_var}/ }
79
+ if -> { @poc_data[route_path_var][route.route_method.to_s.upcase] rescue nil }.call
80
+ poc_opts = @poc_data[route_path_var][route.route_method.to_s.upcase]
81
+
82
+ document.add :h3,'Response'
83
+ # document.add :raw,poc_opts['response'].to_yaml
84
+ document.add :h4,'body'
85
+ document.add :raw,poc_opts['response']['raw_body']
86
+
87
+ document.add :h5,'options:'
88
+
89
+ document.add :list,[
90
+ "'status code: #{poc_opts['response']['status']}",
91
+ "format: #{poc_opts['response']['format']}"
92
+ ]
93
+
94
+
95
+ document.add :h4,'example'
96
+
97
+ document.add :h5,'curl sample'
98
+
99
+ document.add :raw,[
100
+ "curl ",
101
+ "-X #{route.route_method.to_s.upcase} ",
102
+ "\"http://api_url#{route_path_var}?",
103
+ "#{poc_opts['request']['query']['raw']}\""
104
+ ].join
105
+
106
+
107
+
108
+ end
109
+
110
+
111
+ end
112
+ end
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+
119
+ class << self
120
+
121
+ def new(*args)
122
+ Generator.new(*args)
123
+ end;alias generate new
124
+
125
+ end
126
+
127
+ end
@@ -0,0 +1,110 @@
1
+ module GrapeDoc
2
+ module Helpers
3
+ class << self
4
+
5
+ def each_grape_class
6
+ ::ObjectSpace.each_object(::Class) do |api_class|
7
+ next unless -> { api_class < Grape::API rescue false }.call
8
+ yield(api_class)
9
+ end if block_given?
10
+ end
11
+
12
+ def doc_folder_path
13
+ @doc_folder_path ||= -> {
14
+ doc_folder_path = File.join(RackTestPoc.root,'doc')
15
+ File.mkdir doc_folder_path unless File.exist?(doc_folder_path)
16
+ return doc_folder_path
17
+
18
+ }.call
19
+ end
20
+
21
+ def poc_file_path
22
+ return Dir.glob(
23
+ File.join(RackTestPoc.root,'test','poc','*.{yml,yaml}')
24
+ ).max_by(&File.method(:ctime))
25
+ end
26
+
27
+ def poc_data
28
+ require 'yaml'
29
+ YAML.load(File.read(poc_file_path))
30
+ rescue;{}
31
+ end
32
+
33
+ # Tries to find a constant with the name specified in the argument string.
34
+ #
35
+ # 'Module'.constantize # => Module
36
+ # 'Test::Unit'.constantize # => Test::Unit
37
+ #
38
+ # The name is assumed to be the one of a top-level constant, no matter
39
+ # whether it starts with "::" or not. No lexical context is taken into
40
+ # account:
41
+ #
42
+ # C = 'outside'
43
+ # module M
44
+ # C = 'inside'
45
+ # C # => 'inside'
46
+ # 'C'.constantize # => 'outside', same as ::C
47
+ # end
48
+ #
49
+ # NameError is raised when the name is not in CamelCase or the constant is
50
+ # unknown.
51
+ def constantize(camel_cased_word)
52
+ names = camel_cased_word.split('::')
53
+
54
+ # Trigger a builtin NameError exception including the ill-formed constant in the message.
55
+ Object.const_get(camel_cased_word) if names.empty?
56
+
57
+ # Remove the first blank element in case of '::ClassName' notation.
58
+ names.shift if names.size > 1 && names.first.empty?
59
+
60
+ names.inject(Object) do |constant, name|
61
+ if constant == Object
62
+ constant.const_get(name)
63
+ else
64
+ candidate = constant.const_get(name)
65
+ next candidate if constant.const_defined?(name, false)
66
+ next candidate unless Object.const_defined?(name)
67
+
68
+ # Go down the ancestors to check it it's owned
69
+ # directly before we reach Object or the end of ancestors.
70
+ constant = constant.ancestors.inject do |const, ancestor|
71
+ break const if ancestor == Object
72
+ break ancestor if ancestor.const_defined?(name, false)
73
+ const
74
+ end
75
+
76
+ # owner is in Object, so raise
77
+ constant.const_get(name, false)
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument
84
+ # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
85
+ # lowerCamelCase.
86
+ #
87
+ # +camelize+ will also convert '/' to '::' which is useful for converting
88
+ # paths to namespaces.
89
+ #
90
+ # 'active_model'.camelize # => "ActiveModel"
91
+ # 'active_model'.camelize(:lower) # => "activeModel"
92
+ # 'active_model/errors'.camelize # => "ActiveModel::Errors"
93
+ # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
94
+ #
95
+ # As a rule of thumb you can think of +camelize+ as the inverse of
96
+ # +underscore+, though there are cases where that does not hold:
97
+ #
98
+ # 'SSLError'.underscore.camelize # => "SslError"
99
+ def camelize(term)
100
+ string = term.to_s
101
+ string.capitalize!
102
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
103
+ string.gsub!('/', '::')
104
+ string
105
+
106
+ end
107
+
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,91 @@
1
+ module GrapeDoc
2
+ class ApiDocParts
3
+
4
+ class StringBasic < String
5
+
6
+ class << self
7
+ def markdown=(obj)
8
+ @markdown=obj
9
+ end
10
+ def markdown
11
+ @markdown || self.to_s.split('::')[-1].downcase
12
+ end
13
+ end
14
+
15
+ def initialize(*args)
16
+ args[0] = Parser.parse(args[0])
17
+ self.replace(args[0])
18
+ end
19
+
20
+ def markdown
21
+ self.class.markdown
22
+ end
23
+
24
+ alias to_textile to_s
25
+
26
+ end
27
+
28
+ class StringObject < StringBasic
29
+
30
+ def to_textile
31
+ "#{markdown}. #{self.to_s}\n"
32
+ end
33
+
34
+ end
35
+
36
+ class StringObjectEnded < StringObject
37
+
38
+ def to_textile
39
+ "#{markdown}#{self}#{markdown}\n"
40
+ end
41
+
42
+ end
43
+
44
+ class ArrayObject < Array
45
+
46
+ class << self
47
+ def markdown=(obj)
48
+ @markdown=obj
49
+ end
50
+ def markdown
51
+ @markdown || self.to_s.downcase
52
+ end
53
+ end
54
+
55
+ def initialize(object)
56
+ object = Parser.parse(object)
57
+ self.replace(object)
58
+ end
59
+
60
+ def markdown
61
+ self.class.markdown
62
+ end
63
+
64
+ def to_textile
65
+ self.map{|e|
66
+ case e
67
+
68
+ when ArrayObject
69
+ e.map{|e|
70
+
71
+ text = if e.respond_to?(:to_textile)
72
+ e.to_textile
73
+ else
74
+ e.to_s
75
+ end
76
+
77
+ "#{markdown}#{markdown} #{text}"
78
+
79
+ }.join("\n")
80
+
81
+ else
82
+ "#{markdown} #{e}"
83
+
84
+ end
85
+ }.push('').join("\n")
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
data/lib/grape-doc.rb CHANGED
@@ -1 +1,2 @@
1
- require 'grape/doc/logic'
1
+ require 'loader'
2
+ require_relative_directory_r 'grape/doc'
data/test/sample.html ADDED
@@ -0,0 +1,16 @@
1
+ <h1>Rest Api Documentation (grape-doc)</h1>
2
+ <h2><span class="caps">GET</span>: /hello(.:format)</h2>
3
+ <h3>Request</h3>
4
+ <h4>description</h4>
5
+ <ul>
6
+ <li>Hello world!</li>
7
+ </ul>
8
+ <h4>params</h4>
9
+ <ul>
10
+ <li>test
11
+ <ul>
12
+ <li>required: false</li>
13
+ <li>type: String</li>
14
+ <li>desc: it&#8217;s a test string</li>
15
+ </ul></li>
16
+ </ul>
@@ -0,0 +1,13 @@
1
+ require_relative 'test_helper'
2
+ describe 'Doc generating' do
3
+
4
+ specify 'test documentation generator' do
5
+
6
+ var = GrapeDoc.new
7
+ puts var.document.to_textile
8
+ File.write File.join(RackTestPoc.root,'test','sample.html'),
9
+ var.document.to_textile.to_html
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,33 @@
1
+ require 'grape'
2
+ require 'rack/test'
3
+ require 'rack/test/poc'
4
+
5
+ require 'grape-doc'
6
+
7
+ require 'minitest/autorun'
8
+
9
+ class TestAPI1 < Grape::API
10
+
11
+ version 'v1', using: :accept_version_header
12
+
13
+ default_format :txt
14
+ format :json
15
+
16
+ content_type :txt, 'application/text'
17
+ content_type :json, 'application/json'
18
+
19
+ desc 'Hello world!'
20
+ params do
21
+ optional :test,
22
+ type: String,
23
+ desc: 'it\'s a test string'
24
+
25
+ end
26
+ get 'hello' do
27
+ {hello: "world!"}
28
+ end
29
+
30
+ end
31
+
32
+ class TestAPI2 < TestAPI1
33
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'test_helper'
2
+ describe 'Object Space Collector' do
3
+
4
+ specify 'test that there will be api classes on calling each_grape_class' do
5
+ GrapeDoc::Helpers.each_grape_class do |api_class|
6
+ api_class.must_be_instance_of Class
7
+ (api_class < Grape::API).must_be :==, true
8
+
9
+ api_class.respond_to?(:routes).must_be :==, true
10
+ api_class.routes.respond_to?(:each).must_be :==, true
11
+
12
+ api_class.routes.each do |r|
13
+ r.route_method.must_be_instance_of String
14
+ r.route_path.must_be_instance_of String
15
+ r.route_params.must_be_instance_of Hash
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end