cabbage_doc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/cabbage_doc/action.rb +86 -0
- data/lib/cabbage_doc/authentication.rb +54 -0
- data/lib/cabbage_doc/client.rb +27 -0
- data/lib/cabbage_doc/cloneable.rb +9 -0
- data/lib/cabbage_doc/collection.rb +67 -0
- data/lib/cabbage_doc/configuration.rb +52 -0
- data/lib/cabbage_doc/controller.rb +117 -0
- data/lib/cabbage_doc/customizer.rb +9 -0
- data/lib/cabbage_doc/example.rb +25 -0
- data/lib/cabbage_doc/pacto_helper.rb +27 -0
- data/lib/cabbage_doc/parameter.rb +72 -0
- data/lib/cabbage_doc/params.rb +59 -0
- data/lib/cabbage_doc/parser.rb +47 -0
- data/lib/cabbage_doc/path.rb +9 -0
- data/lib/cabbage_doc/processor.rb +41 -0
- data/lib/cabbage_doc/processors/contracts.rb +12 -0
- data/lib/cabbage_doc/processors/documentation.rb +15 -0
- data/lib/cabbage_doc/processors/rspec.rb +11 -0
- data/lib/cabbage_doc/request.rb +70 -0
- data/lib/cabbage_doc/response.rb +37 -0
- data/lib/cabbage_doc/singleton.rb +15 -0
- data/lib/cabbage_doc/task.rb +72 -0
- data/lib/cabbage_doc/version.rb +3 -0
- data/lib/cabbage_doc/web.rb +39 -0
- data/lib/cabbage_doc/web_helper.rb +25 -0
- data/lib/cabbage_doc.rb +34 -0
- data/web/public/css/application.css +184 -0
- data/web/public/js/application.js +113 -0
- data/web/public/js/jquery-1.12.3.min.js +5 -0
- data/web/views/action.haml +20 -0
- data/web/views/array.haml +13 -0
- data/web/views/authentication.haml +25 -0
- data/web/views/controller.haml +7 -0
- data/web/views/documentation.haml +3 -0
- data/web/views/enumeration.haml +9 -0
- data/web/views/index.haml +8 -0
- data/web/views/layout.haml +10 -0
- data/web/views/parameter.haml +12 -0
- data/web/views/response.haml +14 -0
- data/web/views/text.haml +1 -0
- data/web/views/welcome.haml +1 -0
- metadata +196 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 72c954cb92be482568747328289d2c65daf273a6
|
4
|
+
data.tar.gz: aafdefb0496f2a2152402c492b4cc8b4eb11ef77
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 16cd85c66b78ad28fa3c2791e7df1922cd33160754148f0cf42b4861d66705e521d3421ed5ead623643f9a046ea802713c81b8a3f6d2292ac8e9b78955f82897
|
7
|
+
data.tar.gz: 5afa6dec9856b545e4c0ba4d4ce691b623dd153cc8dc45f11bae3e66ac10ee567d81950e8398b52ece74eab5459cdee6a372b90deac2092ebf9b5f14097c7d17
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
class Action
|
3
|
+
include Parser
|
4
|
+
|
5
|
+
METHODS = %w(GET POST PUT DELETE).freeze
|
6
|
+
METHODS_REGEXP = METHODS.join('|').freeze
|
7
|
+
|
8
|
+
attr_reader :label, :name, :description, :path, :method, :parameters, :examples
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@parameters = []
|
12
|
+
@examples = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(text)
|
16
|
+
@method, @path = parse_method_and_path(text)
|
17
|
+
return unless valid?
|
18
|
+
|
19
|
+
@name = parse_name(text)
|
20
|
+
@label = parse_label(text)
|
21
|
+
@description = parse_description(text)
|
22
|
+
|
23
|
+
@parameters, @examples = parse_parameters_and_examples(text)
|
24
|
+
|
25
|
+
valid?
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?
|
29
|
+
@method && @path
|
30
|
+
end
|
31
|
+
|
32
|
+
def param?(key)
|
33
|
+
@parameters.any? { |parameter| parameter.name =~ /#{key}/ }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# FIXME: get rid of this and optimize direct regex matches
|
39
|
+
def text_to_lines(text)
|
40
|
+
text.gsub(/\r\n?/, "\n").split(/\n/)[0..-2].map do |line|
|
41
|
+
line.strip!
|
42
|
+
line.slice!(0)
|
43
|
+
line.strip!
|
44
|
+
line if line.size > 0
|
45
|
+
end.compact
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_parameters_and_examples(text)
|
49
|
+
# FIXME: rewrite this to do a 'scan' with the right Regexp
|
50
|
+
parameters = []
|
51
|
+
examples = []
|
52
|
+
|
53
|
+
lines = text_to_lines(text)
|
54
|
+
|
55
|
+
lines.each do |line|
|
56
|
+
if parameter = Parameter.parse(line)
|
57
|
+
parameters << parameter
|
58
|
+
elsif example = Example.parse(line)
|
59
|
+
examples << example
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
[parameters, examples]
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_method_and_path(text)
|
67
|
+
m = text.match(/#\s*(#{METHODS_REGEXP}):\s*(.*?)$/)
|
68
|
+
[m[1].strip.upcase, m[2].strip] if m
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_name(text)
|
72
|
+
m = text.match(/def\s+(.*?)\s*#\s*#{MARKER}$/)
|
73
|
+
m[1].strip if m
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_label(text)
|
77
|
+
m = text.match(/#\s*Public:(.*?)$/)
|
78
|
+
m[1].strip if m
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_description(text)
|
82
|
+
m = text.match(/#\s*Description:(.*?)$/)
|
83
|
+
m[1].strip if m
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
class Authentication
|
3
|
+
class << self
|
4
|
+
def new(request = nil)
|
5
|
+
super().tap do |auth|
|
6
|
+
yield(auth) if block_given?
|
7
|
+
Configuration.instance.authentication.call(auth, request)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :type,
|
13
|
+
:username,
|
14
|
+
:password,
|
15
|
+
:token,
|
16
|
+
:domain,
|
17
|
+
:subdomain,
|
18
|
+
:subdomains,
|
19
|
+
:scheme,
|
20
|
+
:path,
|
21
|
+
:user_agent,
|
22
|
+
:configurable,
|
23
|
+
:verbose
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
Configuration.instance.tap do |config|
|
27
|
+
@domain = config.domain
|
28
|
+
@scheme = config.scheme
|
29
|
+
@path = config.path
|
30
|
+
@user_agent = config.title
|
31
|
+
end
|
32
|
+
|
33
|
+
@verbose = false
|
34
|
+
@subdomains = []
|
35
|
+
@configurable = []
|
36
|
+
@type = :basic
|
37
|
+
end
|
38
|
+
|
39
|
+
def valid?
|
40
|
+
case type
|
41
|
+
when :basic
|
42
|
+
username && password
|
43
|
+
when :token
|
44
|
+
!token.nil?
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def configurable?
|
51
|
+
@configurable.any?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
class Client
|
3
|
+
include HTTParty
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def new(auth)
|
7
|
+
Class.new(self) do |klass|
|
8
|
+
klass.headers "User-Agent" => auth.user_agent
|
9
|
+
|
10
|
+
if auth.subdomain
|
11
|
+
klass.base_uri "#{auth.scheme}://#{auth.subdomain}.#{auth.domain}/#{auth.path}"
|
12
|
+
else
|
13
|
+
klass.base_uri "#{auth.scheme}://#{auth.domain}/#{auth.path}"
|
14
|
+
end
|
15
|
+
|
16
|
+
if auth.type == :basic
|
17
|
+
klass.basic_auth auth.username, auth.password
|
18
|
+
elsif auth.token
|
19
|
+
klass.headers "Authorization" => "#{auth.type.to_s.capitalize} #{auth.token}"
|
20
|
+
end
|
21
|
+
|
22
|
+
debug_output $stdout if auth.verbose
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module CabbageDoc
|
4
|
+
class Collection
|
5
|
+
include Singleton
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
FILENAME = "controllers.yml".freeze
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@_controllers = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(controller)
|
15
|
+
@_controllers << controller
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_action(method, path)
|
19
|
+
action = nil
|
20
|
+
|
21
|
+
@_controllers.each do |controller|
|
22
|
+
action = controller.find_action(method, path)
|
23
|
+
break if action
|
24
|
+
end
|
25
|
+
|
26
|
+
action
|
27
|
+
end
|
28
|
+
|
29
|
+
def each
|
30
|
+
@_controllers.each do |controller|
|
31
|
+
yield controller
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse!(filename)
|
36
|
+
text = File.read(filename) rescue nil
|
37
|
+
return false unless text
|
38
|
+
|
39
|
+
controller = Controller.parse(text)
|
40
|
+
return false unless controller
|
41
|
+
|
42
|
+
controllers = controller.eval(text)
|
43
|
+
|
44
|
+
@_controllers.concat(controllers)
|
45
|
+
|
46
|
+
controllers.any?
|
47
|
+
end
|
48
|
+
|
49
|
+
def clear!
|
50
|
+
@_controllers = []
|
51
|
+
end
|
52
|
+
|
53
|
+
def load!
|
54
|
+
@_controllers = YAML.load(File.read(filename)) rescue [] unless @_controllers.any?
|
55
|
+
end
|
56
|
+
|
57
|
+
def save!
|
58
|
+
open(filename, 'w') { |f| f.write(YAML.dump(@_controllers)) } rescue nil
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def filename
|
64
|
+
@_filename ||= Path.join(Configuration.instance.root, FILENAME)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
class Configuration
|
3
|
+
include Singleton
|
4
|
+
|
5
|
+
DEFAULTS = {
|
6
|
+
version: 'v1',
|
7
|
+
path: 'api/v1',
|
8
|
+
title: 'Cabbage Doc',
|
9
|
+
scheme: 'https',
|
10
|
+
verbose: false
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
OPTIONAL_ATTRIBUTES = %i(welcome path scheme version title verbose authentication).freeze
|
14
|
+
REQUIRED_ATTRIBUTES = %i(domain controllers root).freeze
|
15
|
+
ATTRIBUTES = (OPTIONAL_ATTRIBUTES + REQUIRED_ATTRIBUTES).freeze
|
16
|
+
CALLABLE_ATTRIBUTES = %i(controllers authentication).freeze
|
17
|
+
|
18
|
+
attr_accessor *ATTRIBUTES
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
DEFAULTS.each do |attr, value|
|
22
|
+
send(:"#{attr}=", value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate!
|
27
|
+
validate_required!
|
28
|
+
validate_callable!
|
29
|
+
validate_root!
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def validate_required!
|
35
|
+
REQUIRED_ATTRIBUTES.each do |attr|
|
36
|
+
raise ArgumentError, "#{attr} is required" unless send(attr)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_callable!
|
41
|
+
CALLABLE_ATTRIBUTES.each do |attr|
|
42
|
+
if (value = send(attr)) && !value.respond_to?(:call)
|
43
|
+
raise ArgumentError, "#{attr} is not callable"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate_root!
|
49
|
+
raise ArgumentError, "#{root} directory doesn't exist" unless Dir.exists?(root)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
class Controller
|
3
|
+
include Parser
|
4
|
+
include Cloneable
|
5
|
+
|
6
|
+
attr_reader :label, :klass, :name, :path, :actions
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@actions = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(text)
|
13
|
+
@label, @path, @klass = parse_label_path_and_class(text)
|
14
|
+
return false unless @label && @klass
|
15
|
+
|
16
|
+
@name = compose_name(klass)
|
17
|
+
|
18
|
+
@actions = parse_actions(text) unless template?
|
19
|
+
|
20
|
+
valid?
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
@name && (actions? || template?)
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_action(method, path)
|
28
|
+
@actions.detect do |action|
|
29
|
+
action.method == method && action.path == path
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def eval(text)
|
34
|
+
return [self] unless template?
|
35
|
+
|
36
|
+
templates = []
|
37
|
+
|
38
|
+
templates += parse_templates(@path)
|
39
|
+
templates += parse_templates(@label)
|
40
|
+
|
41
|
+
return [] unless templates.any?
|
42
|
+
|
43
|
+
count = templates.first[:values].count
|
44
|
+
|
45
|
+
(1..count).map do |i|
|
46
|
+
template_text = text.dup
|
47
|
+
|
48
|
+
templates.each do |template|
|
49
|
+
template_text.gsub!(template[:text], template[:values].shift.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
self.class.parse(template_text)
|
53
|
+
end.compact
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def compose_label(metadata, klass)
|
59
|
+
metadata[:label] || klass.sub(/Controller$/, '')
|
60
|
+
end
|
61
|
+
|
62
|
+
def compose_path(metadata, klass)
|
63
|
+
Path.join('/', Configuration.instance.path, metadata[:path] || compose_name(klass))
|
64
|
+
end
|
65
|
+
|
66
|
+
def compose_name(klass)
|
67
|
+
compose_label({}, klass).downcase
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_label_path_and_class(text)
|
71
|
+
klass = parse_class(text)
|
72
|
+
return unless klass
|
73
|
+
|
74
|
+
metadata = parse_metadata(text)
|
75
|
+
|
76
|
+
[compose_label(metadata, klass), compose_path(metadata, klass), klass]
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_actions(text)
|
80
|
+
actions = []
|
81
|
+
|
82
|
+
text.scan(/(#\s*Public:\s*.*?(#{Action::METHODS_REGEXP}):.*?def\s+.*?\s*#\s*#{MARKER})/m) do
|
83
|
+
actions << Action.parse($1.strip)
|
84
|
+
end
|
85
|
+
|
86
|
+
actions.compact
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_class(text)
|
90
|
+
m = text.match(/class\s+(.*?)\s+?#\s+?#{MARKER}$/)
|
91
|
+
m[1].strip.split('<').first.strip if m
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_metadata(text)
|
95
|
+
m = text.match(/(#\s*Public:\s*.*?class\s+.*?\s*#\s*#{MARKER})/m)
|
96
|
+
return {} unless m
|
97
|
+
|
98
|
+
metadata = m[1].strip
|
99
|
+
|
100
|
+
{}.tap do |hash|
|
101
|
+
m = metadata.match(/#\s*Public:(.*?)$/)
|
102
|
+
hash[:label] = m[1].strip if m
|
103
|
+
|
104
|
+
m = metadata.match(/#\s*PATH:\s*\/(.*?)$/)
|
105
|
+
hash[:path] = m[1].strip if m
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def actions?
|
110
|
+
@actions.any?
|
111
|
+
end
|
112
|
+
|
113
|
+
def template?
|
114
|
+
@path =~ /\/{.*?,.*?}/
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
class Example
|
3
|
+
include Parser
|
4
|
+
|
5
|
+
attr_reader :label, :params
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@params = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse(text)
|
12
|
+
m = text.match(/^(.*?)\s+-\s+(\(.*?\))$/)
|
13
|
+
return false unless m
|
14
|
+
|
15
|
+
@label = m[1].strip
|
16
|
+
@params = parse_option(m[2].strip)
|
17
|
+
|
18
|
+
valid?
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
!@label.nil?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
module PactoHelper
|
3
|
+
def pacto_enable!
|
4
|
+
WebMock.allow_net_connect!
|
5
|
+
|
6
|
+
Pacto.configure do |config|
|
7
|
+
config.contracts_path = Configuration.instance.root
|
8
|
+
end
|
9
|
+
|
10
|
+
Pacto.generate!
|
11
|
+
end
|
12
|
+
|
13
|
+
def pacto_disable!
|
14
|
+
Pacto.stop_generating!
|
15
|
+
end
|
16
|
+
|
17
|
+
def pacto_available?
|
18
|
+
require 'forwardable'
|
19
|
+
require 'pacto'
|
20
|
+
|
21
|
+
defined?(Pacto)
|
22
|
+
rescue LoadError => e
|
23
|
+
puts "WARNING: Pacto is not available."
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
class Parameter
|
3
|
+
include Parser
|
4
|
+
|
5
|
+
TYPES = %i(numeric decimal integer string id enumeration array date time timestamp hash)
|
6
|
+
|
7
|
+
attr_reader :label, :name, :type, :type_label, :default, :values, :required
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@values = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(text)
|
14
|
+
m = text.match(/^(.*?\s+\(.*?\).*?)\s+-\s+(.*?)$/)
|
15
|
+
return false unless m
|
16
|
+
|
17
|
+
@name, @type_label, @required = parse_name_type_required(m[1].strip)
|
18
|
+
@type = @type_label.downcase.to_sym if @type_label
|
19
|
+
|
20
|
+
@required = !!@required
|
21
|
+
|
22
|
+
@label, @default, @values = parse_label_default_values(m[2].strip)
|
23
|
+
@values ||= []
|
24
|
+
|
25
|
+
valid?
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?
|
29
|
+
@type && TYPES.include?(@type)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parse_label_default_values(text)
|
35
|
+
m = text.match(/^(.*?)\s*(\(.*?\))?$/)
|
36
|
+
return unless m
|
37
|
+
|
38
|
+
index, options = parse_options(text)
|
39
|
+
|
40
|
+
arr = [text[0..index-1].strip]
|
41
|
+
|
42
|
+
if options.any?
|
43
|
+
arr << options[:default]
|
44
|
+
arr << Array(options[:values])
|
45
|
+
end
|
46
|
+
|
47
|
+
arr
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_name_type_required(text)
|
51
|
+
text.split(/\s+/).map(&:strip).map do |component|
|
52
|
+
if component =~ /^\((.*?)\)$/
|
53
|
+
$1
|
54
|
+
elsif component =~ /\[required\]/i
|
55
|
+
true
|
56
|
+
else
|
57
|
+
component
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_options(text)
|
63
|
+
m = text.match(/:.*?\)$/)
|
64
|
+
return [0, {}] unless m
|
65
|
+
|
66
|
+
index = text.rindex('(')
|
67
|
+
return [0, {}] unless index
|
68
|
+
|
69
|
+
[index, parse_option(text[index..-1].strip)]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
class Params
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(params, collection)
|
6
|
+
@_params = convert(params, collection)
|
7
|
+
end
|
8
|
+
|
9
|
+
def each
|
10
|
+
@_params.each do |k, v|
|
11
|
+
yield(k, v)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def delete(key)
|
16
|
+
@_params.delete(key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def find(key)
|
20
|
+
@_params[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
@_params.any?
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
@_params
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_query
|
32
|
+
@_params.map do |k, v|
|
33
|
+
if v.is_a?(Array)
|
34
|
+
v.map { |vv| "#{k}[]=#{CGI.escape(vv)}" }
|
35
|
+
else
|
36
|
+
"#{k}=#{CGI.escape(v)}"
|
37
|
+
end
|
38
|
+
end.flatten.join('&')
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def convert(params, collection)
|
44
|
+
method = params['method']
|
45
|
+
action = params['action']
|
46
|
+
|
47
|
+
return {} unless action && method
|
48
|
+
|
49
|
+
action = collection.find_action(method, action)
|
50
|
+
return {} unless action
|
51
|
+
|
52
|
+
{}.tap do |hash|
|
53
|
+
params.each do |k, v|
|
54
|
+
hash[k] = v if action.param?(k.sub(/\[\]$/, ''))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module CabbageDoc
|
2
|
+
module Parser
|
3
|
+
class << self
|
4
|
+
def included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def parse(text)
|
11
|
+
instance = new
|
12
|
+
instance if instance.parse(text)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(text)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_option(text)
|
21
|
+
m = text.match(/^\((.*?)\)$/)
|
22
|
+
return {} unless m
|
23
|
+
|
24
|
+
{}.tap do |hash|
|
25
|
+
m[1].split(/,/).map(&:strip).each do |o|
|
26
|
+
k, v = o.split(':').map(&:strip)
|
27
|
+
next unless k && v
|
28
|
+
|
29
|
+
v = v.split('|').map(&:strip)
|
30
|
+
v = v.first if v.size == 1
|
31
|
+
|
32
|
+
hash[k.to_sym] = v
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_templates(text)
|
38
|
+
templates = []
|
39
|
+
|
40
|
+
text.scan(/(\{(.*?)\})/) do
|
41
|
+
templates << { text: $1, values: $2.split(/,/).map(&:strip) }
|
42
|
+
end
|
43
|
+
|
44
|
+
templates
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|