cabbage_doc 0.0.1

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