cabbage_doc 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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cabbage_doc/action.rb +86 -0
  3. data/lib/cabbage_doc/authentication.rb +54 -0
  4. data/lib/cabbage_doc/client.rb +27 -0
  5. data/lib/cabbage_doc/cloneable.rb +9 -0
  6. data/lib/cabbage_doc/collection.rb +67 -0
  7. data/lib/cabbage_doc/configuration.rb +52 -0
  8. data/lib/cabbage_doc/controller.rb +117 -0
  9. data/lib/cabbage_doc/customizer.rb +9 -0
  10. data/lib/cabbage_doc/example.rb +25 -0
  11. data/lib/cabbage_doc/pacto_helper.rb +27 -0
  12. data/lib/cabbage_doc/parameter.rb +72 -0
  13. data/lib/cabbage_doc/params.rb +59 -0
  14. data/lib/cabbage_doc/parser.rb +47 -0
  15. data/lib/cabbage_doc/path.rb +9 -0
  16. data/lib/cabbage_doc/processor.rb +41 -0
  17. data/lib/cabbage_doc/processors/contracts.rb +12 -0
  18. data/lib/cabbage_doc/processors/documentation.rb +15 -0
  19. data/lib/cabbage_doc/processors/rspec.rb +11 -0
  20. data/lib/cabbage_doc/request.rb +70 -0
  21. data/lib/cabbage_doc/response.rb +37 -0
  22. data/lib/cabbage_doc/singleton.rb +15 -0
  23. data/lib/cabbage_doc/task.rb +72 -0
  24. data/lib/cabbage_doc/version.rb +3 -0
  25. data/lib/cabbage_doc/web.rb +39 -0
  26. data/lib/cabbage_doc/web_helper.rb +25 -0
  27. data/lib/cabbage_doc.rb +34 -0
  28. data/web/public/css/application.css +184 -0
  29. data/web/public/js/application.js +113 -0
  30. data/web/public/js/jquery-1.12.3.min.js +5 -0
  31. data/web/views/action.haml +20 -0
  32. data/web/views/array.haml +13 -0
  33. data/web/views/authentication.haml +25 -0
  34. data/web/views/controller.haml +7 -0
  35. data/web/views/documentation.haml +3 -0
  36. data/web/views/enumeration.haml +9 -0
  37. data/web/views/index.haml +8 -0
  38. data/web/views/layout.haml +10 -0
  39. data/web/views/parameter.haml +12 -0
  40. data/web/views/response.haml +14 -0
  41. data/web/views/text.haml +1 -0
  42. data/web/views/welcome.haml +1 -0
  43. 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,9 @@
1
+ require 'yaml'
2
+
3
+ module CabbageDoc
4
+ module Cloneable
5
+ def clone
6
+ YAML.load(YAML.dump(self))
7
+ end
8
+ end
9
+ 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,9 @@
1
+ require 'fileutils'
2
+
3
+ module CabbageDoc
4
+ class Customizer
5
+ def perform
6
+ FileUtils.cp_r(Web::ROOT, Configuration.instance.root)
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,9 @@
1
+ module CabbageDoc
2
+ class Path
3
+ class << self
4
+ def join(*args)
5
+ args.join('/').gsub(/\/+/, '/')
6
+ end
7
+ end
8
+ end
9
+ end