httpdoc 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.markdown +137 -0
- data/VERSION +1 -0
- data/bin/httpdoc +38 -0
- data/lib/httpdoc/generator.rb +36 -0
- data/lib/httpdoc/model.rb +47 -0
- data/lib/httpdoc/parser.rb +166 -0
- data/lib/httpdoc/rendering.rb +156 -0
- data/lib/httpdoc.rb +4 -0
- metadata +89 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Alexander Staubo
|
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.
|
data/README.markdown
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
Httpdoc is a very simple documentation generator for generating public API documentation for an HTTP-based web service, parsed from comments embedded in Ruby source code. It's API philosophy-agnostic and supports REST and such if you like. For example, an HTTP API can be described like so:
|
2
|
+
|
3
|
+
## List stuff.
|
4
|
+
#
|
5
|
+
# @return A list of stuff in XML.
|
6
|
+
# @status 200
|
7
|
+
# @status 403 If you do not have permission to list the stuff.
|
8
|
+
# @example
|
9
|
+
# @request
|
10
|
+
# GET #{base_url}/create/31
|
11
|
+
#
|
12
|
+
# @response
|
13
|
+
# HTTP/1.1 200 OK
|
14
|
+
# Content-Type: application/xml; charset=utf-8
|
15
|
+
#
|
16
|
+
# <stuff>...</stuff>
|
17
|
+
# @end
|
18
|
+
#
|
19
|
+
def list
|
20
|
+
...
|
21
|
+
end
|
22
|
+
|
23
|
+
Limitations
|
24
|
+
-----------
|
25
|
+
|
26
|
+
Httpdoc is currently limited to Rails applications. I plan to generalize the internals to support Sinatra and other frameworks, which should be quite trivial.
|
27
|
+
|
28
|
+
Since Httpdoc does not read Rails routes, it currently requires per-action URL paths to be explicitly written out in the documentation strings. I will be working on route inference.
|
29
|
+
|
30
|
+
For historic reasons, only Textile and HTML is supported in documentation fragments. I plan to phase out Textile and make Markdown the default format.
|
31
|
+
|
32
|
+
Format
|
33
|
+
------
|
34
|
+
|
35
|
+
The documentation format is vaguely inspired by JavaDoc conventions.
|
36
|
+
|
37
|
+
All documentation comments are indicated by double hashes, like so:
|
38
|
+
|
39
|
+
## This line introduces a documentation comment, and
|
40
|
+
# this line continues it.
|
41
|
+
|
42
|
+
This first part of the documentation comment is the main description of the class or action.
|
43
|
+
|
44
|
+
Attributes consist of a key and a value:
|
45
|
+
|
46
|
+
# @foo Bar
|
47
|
+
|
48
|
+
Here, `foo` is an attribute, and `Bar` is its value. Attributes can have multi-line values; a value ends when the next attribute starts or the entire documentation comment terminates.
|
49
|
+
|
50
|
+
Some attributes take two arguments, such as in the case of `param`:
|
51
|
+
|
52
|
+
# @param key Some key.
|
53
|
+
|
54
|
+
Here, `key` is a parameter name, `Some key` is its description. See below.
|
55
|
+
|
56
|
+
A controller class is preceded by a block which introduces the controller:
|
57
|
+
|
58
|
+
## This API does stuff.
|
59
|
+
class StuffController < ApplicationController
|
60
|
+
|
61
|
+
A controller class supports two attributes, `title` and `url`:
|
62
|
+
|
63
|
+
* `@title [title]`: specify a heading for the API itself.
|
64
|
+
|
65
|
+
* `@url [url]`: a base URL or relative path for the API. This is combined with the `--base-url` option passed to Httpdoc on the command line. Currently Httpdoc does not infer this stuff from Rails routes, so it can be overridden here if the controller name does not match the actual path used.
|
66
|
+
|
67
|
+
For example:
|
68
|
+
|
69
|
+
## This API does stuff.
|
70
|
+
#
|
71
|
+
# @title Stuff
|
72
|
+
# @url http://stuff.ly/api/
|
73
|
+
#
|
74
|
+
class StuffController < ApplicationController
|
75
|
+
|
76
|
+
Methods are described thusly:
|
77
|
+
|
78
|
+
## Create stuff that can be shared with other users.
|
79
|
+
#
|
80
|
+
def create
|
81
|
+
...
|
82
|
+
end
|
83
|
+
|
84
|
+
Methods support the following attributes:
|
85
|
+
|
86
|
+
* `@param [name] [description]`: which describes a parameter name.
|
87
|
+
|
88
|
+
* `@status [code] [description]`: describes a possible HTTP status code and its meaning.
|
89
|
+
|
90
|
+
* `@return [description]`: says what the action produces in terms of output.
|
91
|
+
|
92
|
+
* `@short [description]`: specifies a short description, usable as a title.
|
93
|
+
|
94
|
+
* `@url [url]`: specifies the URL of the HTTP call, either an absolute URL or a relative path. This is combined with the `@url` attribute on the class itself, and the `--base-url` option passed to Httpdoc on the command line. Currently Httpdoc does not infer this stuff from Rails routes, so it can be overridden here if the controller name does not match the actual path used. If not specified, the name of the method itself is assumed.
|
95
|
+
|
96
|
+
* `@example`: introduces an example block. It's terminated with an `@end` attribute. Within the example block, `@request` introduces the request, and `@response` the response.
|
97
|
+
|
98
|
+
Here's a complete example of a method doc:
|
99
|
+
|
100
|
+
## Create stuff that can be shared with other users.
|
101
|
+
#
|
102
|
+
# @url create/:amount
|
103
|
+
# @short Create stuff
|
104
|
+
# @param amount The amount of stuff to create, an integer between 3 and 42.
|
105
|
+
# @return The URL to the stuff.
|
106
|
+
# @status 201
|
107
|
+
# @status 403 If you do not have permission to create the stuff.
|
108
|
+
# @example
|
109
|
+
# @request
|
110
|
+
# POST #{base_url}/create/31
|
111
|
+
#
|
112
|
+
# @response
|
113
|
+
# HTTP/1.1 201 Created
|
114
|
+
# Content-Type: text/plain; charset=utf-8
|
115
|
+
#
|
116
|
+
# http://#{base_url}/stuff/9953
|
117
|
+
# @end
|
118
|
+
#
|
119
|
+
def create
|
120
|
+
...
|
121
|
+
end
|
122
|
+
|
123
|
+
Usage
|
124
|
+
-----
|
125
|
+
|
126
|
+
To generate documentation for a bunch of controllers:
|
127
|
+
|
128
|
+
httpdoc --base-url=http://mysite.com/api/v2/ --template=mytemplate --output-dir=doc/ app/controllers/api/v2/*.rb
|
129
|
+
|
130
|
+
Look in the `examples` directory for an example controller and its pre-generated documentation example. To generate the example controller's documentation, using the example template:
|
131
|
+
|
132
|
+
httpdoc -t examples/single_file_template.erb -o examples/ examples/*.rb
|
133
|
+
|
134
|
+
Requirements
|
135
|
+
------------
|
136
|
+
|
137
|
+
* RedCloth
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/bin/httpdoc
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'httpdoc'
|
5
|
+
|
6
|
+
template_name = 'single_file'
|
7
|
+
base_url = 'http://example.org/'
|
8
|
+
output_directory = '.'
|
9
|
+
|
10
|
+
ARGV.options do |opts|
|
11
|
+
opts.banner = "Usage: #{File.basename($0)} [OPTIONS ...] [INPUT ...]"
|
12
|
+
opts.separator ""
|
13
|
+
opts.on("-t", "--template=TEMPLATE", String, "Specify template (default: #{template_name})") do |value|
|
14
|
+
template_name = value
|
15
|
+
end
|
16
|
+
opts.on("-o", "--output-dir=DIRECTORY", String, "Output directory (defaults to current directory)") do |value|
|
17
|
+
output_directory = value
|
18
|
+
end
|
19
|
+
opts.on("--base-url=URL", String, "Base URL of HTTP interface (eg., http://myapp.org/)") do |value|
|
20
|
+
base_url = value
|
21
|
+
end
|
22
|
+
opts.on("-h", "--help", "Show this help message.") do
|
23
|
+
puts opts
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
opts.parse!
|
27
|
+
if ARGV.empty?
|
28
|
+
puts "Nothing to do. Run with -h for help."
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
generator = Httpdoc::Generator.new
|
34
|
+
generator.output_directory = output_directory
|
35
|
+
generator.template_name = template_name
|
36
|
+
generator.input_paths = ARGV
|
37
|
+
generator.base_url = base_url
|
38
|
+
generator.generate!
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Httpdoc
|
2
|
+
|
3
|
+
class Generator
|
4
|
+
|
5
|
+
attr_accessor :base_url
|
6
|
+
attr_accessor :output_directory
|
7
|
+
attr_accessor :input_paths
|
8
|
+
attr_accessor :template_name
|
9
|
+
|
10
|
+
def generate!
|
11
|
+
file_names = @input_paths.map { |path|
|
12
|
+
path = path.gsub(/\/$/, '')
|
13
|
+
if File.file?(path)
|
14
|
+
path
|
15
|
+
else
|
16
|
+
Dir.glob("#{path}/**/*_controller.rb")
|
17
|
+
end
|
18
|
+
}.flatten
|
19
|
+
file_names.each do |file_name|
|
20
|
+
parser = RubyCommentParser.new(file_name)
|
21
|
+
parser.parse
|
22
|
+
if parser.controller
|
23
|
+
renderer = Rendering::SingleFileRenderer.new(:base_url => @base_url)
|
24
|
+
renderer.template_name = @template_name
|
25
|
+
output_filename = File.join(@output_directory, File.basename(file_name).gsub(/\.rb/, ''))
|
26
|
+
output_filename << ".html"
|
27
|
+
File.open(output_filename, "w") do |file|
|
28
|
+
file << renderer.render_controller(parser.controller)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Httpdoc
|
2
|
+
|
3
|
+
class Parameter
|
4
|
+
attr_accessor :name
|
5
|
+
attr_accessor :description
|
6
|
+
end
|
7
|
+
|
8
|
+
class Example
|
9
|
+
attr_accessor :request
|
10
|
+
attr_accessor :response
|
11
|
+
end
|
12
|
+
|
13
|
+
class Status
|
14
|
+
attr_accessor :code
|
15
|
+
attr_accessor :description
|
16
|
+
end
|
17
|
+
|
18
|
+
class Action
|
19
|
+
def initialize
|
20
|
+
@parameters = []
|
21
|
+
@examples = []
|
22
|
+
@statuses = []
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :url
|
26
|
+
attr_accessor :short_description
|
27
|
+
attr_accessor :description
|
28
|
+
attr_accessor :parameters
|
29
|
+
attr_accessor :examples
|
30
|
+
attr_accessor :statuses
|
31
|
+
attr_accessor :return
|
32
|
+
end
|
33
|
+
|
34
|
+
class Controller
|
35
|
+
def initialize
|
36
|
+
@actions = []
|
37
|
+
@constants = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_accessor :title
|
41
|
+
attr_accessor :description
|
42
|
+
attr_accessor :actions
|
43
|
+
attr_accessor :constants
|
44
|
+
attr_accessor :url
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module Httpdoc
|
2
|
+
|
3
|
+
module ControllerDocParser
|
4
|
+
|
5
|
+
def self.parse(doc)
|
6
|
+
controller = Controller.new
|
7
|
+
if doc =~ /\A(.*?)^\s*@/m
|
8
|
+
controller.description = $1.strip
|
9
|
+
end
|
10
|
+
doc.scan(/@title\s+(.*?)(^[@]|\z)/m).each do |s|
|
11
|
+
controller.title = $1.strip
|
12
|
+
break
|
13
|
+
end
|
14
|
+
doc.scan(/@url\s+(.*?)(?=^@|\z)/m) do
|
15
|
+
controller.url = $1.strip
|
16
|
+
break
|
17
|
+
end
|
18
|
+
controller
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
module ActionDocParser
|
24
|
+
|
25
|
+
def self.parse(name, doc, actions = [])
|
26
|
+
action = Action.new
|
27
|
+
if doc =~ /\A(.*?)^\s*@/m
|
28
|
+
action.description = $1.strip
|
29
|
+
end
|
30
|
+
doc.scan(/@param\s+(.*?)(?:\s+(.*?))?(?=^\s*@|\z)/m) do
|
31
|
+
param = Parameter.new
|
32
|
+
param.name = $1
|
33
|
+
param.description = $2
|
34
|
+
action.parameters << param
|
35
|
+
end
|
36
|
+
doc.scan(/@status\s+(.*?)(?:\s+(.*?))?(?=^\s*@|\z)/m) do
|
37
|
+
status = Status.new
|
38
|
+
status.code = $1.strip
|
39
|
+
status.description = $2.strip
|
40
|
+
action.statuses << status
|
41
|
+
end
|
42
|
+
doc.scan(/@return\s+(.*?)(?=^@|\z)/m) do
|
43
|
+
action.return = $1.strip
|
44
|
+
break
|
45
|
+
end
|
46
|
+
doc.scan(/@short\s+(.*?)(?=^@|\z)/m) do
|
47
|
+
action.short_description = $1.strip
|
48
|
+
break
|
49
|
+
end
|
50
|
+
doc.scan(/@url\s+(.*?)(?=^@|\z)/m) do
|
51
|
+
action.url = $1.strip
|
52
|
+
break
|
53
|
+
end
|
54
|
+
action.url ||= name
|
55
|
+
doc.scan(/@example\s*\n(.*?)(^\s*@end|\z)/m) do
|
56
|
+
example = Example.new
|
57
|
+
s = $1
|
58
|
+
s.scan(/(^[\t ]*)@request\s+(.*?)(?=^\s*@|\z)/m) do
|
59
|
+
padding, req = $1, $2
|
60
|
+
example.request = req.split("\n").map { |line|
|
61
|
+
line = line[padding.length..-1] || '' if line[0, padding.length] == padding
|
62
|
+
line
|
63
|
+
}.join("\n")
|
64
|
+
break
|
65
|
+
end
|
66
|
+
s.scan(/(^[\t ]*)@response\s+(.*?)(?=^\s*@|\z)/m) do
|
67
|
+
padding, res = $1, $2
|
68
|
+
example.response = res.split("\n").map { |line|
|
69
|
+
line = (line[padding.length..-1] || '') if line[0, padding.length] == padding
|
70
|
+
line
|
71
|
+
}.join("\n")
|
72
|
+
break
|
73
|
+
end
|
74
|
+
action.examples << example
|
75
|
+
break
|
76
|
+
end
|
77
|
+
actions << action
|
78
|
+
actions
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
class RubyCommentParser
|
84
|
+
|
85
|
+
def initialize(file_name)
|
86
|
+
@file_name = file_name
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse
|
90
|
+
reset
|
91
|
+
File.open(@file_name) do |file|
|
92
|
+
file.readlines.each do |line|
|
93
|
+
case @state
|
94
|
+
when :top
|
95
|
+
case line
|
96
|
+
when /\A\s*##(.*)/
|
97
|
+
@buffer << $1
|
98
|
+
@buffer << "\n"
|
99
|
+
@state = :class_doc
|
100
|
+
end
|
101
|
+
when :class_doc
|
102
|
+
case line
|
103
|
+
when /\A\s*#+\s?(.*)/
|
104
|
+
@buffer << $1
|
105
|
+
@buffer << "\n"
|
106
|
+
when /(^|\s*)class \w/
|
107
|
+
unless @buffer.empty?
|
108
|
+
@controller = ControllerDocParser.parse(@buffer)
|
109
|
+
@buffer = ''
|
110
|
+
end
|
111
|
+
@state = :class_def
|
112
|
+
end
|
113
|
+
when :class_def
|
114
|
+
case line
|
115
|
+
when /^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)/
|
116
|
+
if @controller
|
117
|
+
@controller.constants[$1] = $2
|
118
|
+
end
|
119
|
+
when /\A\s*##(.*)/
|
120
|
+
@buffer << $1
|
121
|
+
@state = :method_def
|
122
|
+
end
|
123
|
+
when :method_def
|
124
|
+
case line
|
125
|
+
when /\A\s*#+\s?(.*)/
|
126
|
+
@buffer << $1
|
127
|
+
@buffer << "\n"
|
128
|
+
when /\A\s*def ([^\s#\(]+)/
|
129
|
+
name = $1
|
130
|
+
unless @buffer.empty?
|
131
|
+
if @controller
|
132
|
+
ActionDocParser.parse(name, @buffer, @controller.actions)
|
133
|
+
end
|
134
|
+
@buffer = ''
|
135
|
+
end
|
136
|
+
@state = :class_def
|
137
|
+
else
|
138
|
+
unless @buffer.empty?
|
139
|
+
if @controller
|
140
|
+
ActionDocParser.parse(nil, @buffer, @controller.actions)
|
141
|
+
end
|
142
|
+
@buffer = ''
|
143
|
+
end
|
144
|
+
@state = :class_def
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
if @controller
|
150
|
+
@controller.url ||= $1 if @file_name =~ /(\w+)_controller\./
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
attr_reader :controller
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def reset
|
159
|
+
@controller = nil
|
160
|
+
@buffer = ''
|
161
|
+
@state = :top
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "erb"
|
3
|
+
require "redcloth"
|
4
|
+
|
5
|
+
module Httpdoc
|
6
|
+
module Rendering
|
7
|
+
|
8
|
+
class UndefinedVariableError < Exception
|
9
|
+
def initialize(name)
|
10
|
+
super("Undefined variable #{name}")
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
attr_reader :name
|
14
|
+
end
|
15
|
+
|
16
|
+
class UndefinedConstantError < Exception
|
17
|
+
def initialize(name)
|
18
|
+
super("Undefined constant #{name}")
|
19
|
+
@name = name
|
20
|
+
end
|
21
|
+
attr_reader :name
|
22
|
+
end
|
23
|
+
|
24
|
+
class ControllerContext
|
25
|
+
|
26
|
+
def initialize(renderer, controller)
|
27
|
+
@renderer = renderer
|
28
|
+
@controller = controller
|
29
|
+
end
|
30
|
+
|
31
|
+
def h(s)
|
32
|
+
s ||= ''
|
33
|
+
return s.gsub(/#\{(.*)\}/) {
|
34
|
+
name = $1
|
35
|
+
case name
|
36
|
+
when /^[A-Z0-9_]+$/
|
37
|
+
value = @controller.constants[name]
|
38
|
+
raise UndefinedConstantError.new(name) unless value
|
39
|
+
value = eval(value)
|
40
|
+
when "base_url"
|
41
|
+
value = base_url.gsub(/\/$/, '')
|
42
|
+
else
|
43
|
+
raise UndefinedVariableError, name
|
44
|
+
end
|
45
|
+
value
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def doc_fragment_to_html(s)
|
50
|
+
s = s.gsub("\n", ' ')
|
51
|
+
RedCloth.new(s).to_html
|
52
|
+
end
|
53
|
+
|
54
|
+
def expand_url_with_subtitutions(url)
|
55
|
+
url = [base_url, url].join("/") unless url =~ /^\//
|
56
|
+
return URI.join(@renderer.base_url, url).to_s.gsub(/:([\w_]+)/, '<strong><\1></strong>')
|
57
|
+
end
|
58
|
+
|
59
|
+
def base_url
|
60
|
+
controller_url = @controller.url
|
61
|
+
controller_url ||= "/"
|
62
|
+
return URI.join(@renderer.base_url, controller_url).to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def escape_html(h)
|
66
|
+
return nil unless h
|
67
|
+
h = h.dup
|
68
|
+
h.gsub!("&", "&")
|
69
|
+
h.gsub!("<", "<")
|
70
|
+
h.gsub!(">", ">")
|
71
|
+
h.gsub!("\n", "<br/>")
|
72
|
+
h
|
73
|
+
end
|
74
|
+
|
75
|
+
def format_request(req)
|
76
|
+
envelope, body = req.split("\n\n")
|
77
|
+
lines = envelope.split("\n")
|
78
|
+
first_line, header_lines = lines[0], (lines[1..-1] || [])
|
79
|
+
result = "<div class='request'>"
|
80
|
+
result << "<strong class='request_first_line'>#{h(first_line)}</strong><br/>"
|
81
|
+
result << header_lines.map { |h|
|
82
|
+
name, value = h.split(":\s*")
|
83
|
+
"<span class='request_header_line'><strong class='request_header_name'>#{escape_html(name)}" <<
|
84
|
+
"</strong>: <span class='request_header_value'>#{escape_html(value)}</span></span>"
|
85
|
+
}.join("<br/>")
|
86
|
+
if body and body != ''
|
87
|
+
result << "<br/>"
|
88
|
+
result << "<div class='request_body'>#{escape_html(body.strip)}</div>"
|
89
|
+
end
|
90
|
+
result << "</div>"
|
91
|
+
result
|
92
|
+
end
|
93
|
+
|
94
|
+
def format_response(res)
|
95
|
+
envelope, body = res.split("\n\n")
|
96
|
+
lines = envelope.split("\n")
|
97
|
+
first_line, header_lines = lines[0], (lines[1..-1] || [])
|
98
|
+
result = "<div class='response'>"
|
99
|
+
result << "<strong class='response_first_line'>#{h(first_line)}</strong><br/>"
|
100
|
+
result << header_lines.map { |h|
|
101
|
+
name, value = h.scan(/(.+):\s*(.+)/)[0]
|
102
|
+
"<span class='response_header_line'><strong class='response_header_name'>#{escape_html(name)}" <<
|
103
|
+
"</strong>: <span class='response_header_value'>#{escape_html(value)}</span></span>"
|
104
|
+
}.join("<br/>")
|
105
|
+
if body and body != ''
|
106
|
+
result << "<br/><br/>"
|
107
|
+
result << "<div class='response_body'>#{escape_html(body)}</div>"
|
108
|
+
end
|
109
|
+
result << "</div>"
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_binding
|
114
|
+
return binding
|
115
|
+
end
|
116
|
+
|
117
|
+
attr_reader :controller
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
class SingleFileRenderer
|
122
|
+
|
123
|
+
def initialize(options = {})
|
124
|
+
@base_url = options[:base_url]
|
125
|
+
@base_url ||= 'http://example.com/'
|
126
|
+
end
|
127
|
+
|
128
|
+
def render_controller(controller)
|
129
|
+
%w(erb).each do |extension|
|
130
|
+
template_file_name = "#{@template_name}"
|
131
|
+
template_file_name = "#{template_file_name}.#{extension}" unless template_file_name =~ /#{Regexp.escape(extension)}$/
|
132
|
+
unless File.exist?(template_file_name)
|
133
|
+
possible_template_file_name = File.join(File.dirname(__FILE__), "templates/#{template_file_name}")
|
134
|
+
if File.exist?(possible_template_file_name)
|
135
|
+
template_file_name = possible_template_file_name
|
136
|
+
end
|
137
|
+
end
|
138
|
+
template_content = File.read(template_file_name)
|
139
|
+
context = ControllerContext.new(self, controller)
|
140
|
+
erb = ERB.new(template_content, nil, "-")
|
141
|
+
return erb.result(context.get_binding)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def find_template(name)
|
146
|
+
%(erb).each do |extension|
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
attr_reader :base_url
|
151
|
+
attr_accessor :template_name
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
data/lib/httpdoc.rb
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: httpdoc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Alexander Staubo
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-02-23 00:00:00 +01:00
|
19
|
+
default_executable: httpdoc
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: RedCloth
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: Simple documentation generator for publishing APIs from Rails applications.
|
36
|
+
email: alex@bengler.no
|
37
|
+
executables:
|
38
|
+
- httpdoc
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.markdown
|
44
|
+
files:
|
45
|
+
- LICENSE
|
46
|
+
- README.markdown
|
47
|
+
- VERSION
|
48
|
+
- lib/httpdoc.rb
|
49
|
+
- lib/httpdoc/generator.rb
|
50
|
+
- lib/httpdoc/model.rb
|
51
|
+
- lib/httpdoc/parser.rb
|
52
|
+
- lib/httpdoc/rendering.rb
|
53
|
+
- bin/httpdoc
|
54
|
+
has_rdoc: false
|
55
|
+
homepage: http://github.com/origo/httpdoc
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.3.7
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Simple documentation generator for publishing APIs from Rails applications.
|
88
|
+
test_files: []
|
89
|
+
|