grape-doc 0.0.1.alpha → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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