markdown_ruby_documentation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +4 -0
  5. data/CODE_OF_CONDUCT.md +13 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +134 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +7 -0
  12. data/lib/github-markdown.css +656 -0
  13. data/lib/markdown_ruby_documentation.rb +29 -0
  14. data/lib/markdown_ruby_documentation/constants_presenter.rb +38 -0
  15. data/lib/markdown_ruby_documentation/default_erb_methods.rb +11 -0
  16. data/lib/markdown_ruby_documentation/generate.rb +107 -0
  17. data/lib/markdown_ruby_documentation/git_hub_link.rb +74 -0
  18. data/lib/markdown_ruby_documentation/git_hub_project.rb +29 -0
  19. data/lib/markdown_ruby_documentation/instance_to_class_methods.rb +44 -0
  20. data/lib/markdown_ruby_documentation/markdown_presenter.rb +36 -0
  21. data/lib/markdown_ruby_documentation/method.rb +101 -0
  22. data/lib/markdown_ruby_documentation/method/class_method.rb +13 -0
  23. data/lib/markdown_ruby_documentation/method/instance_method.rb +11 -0
  24. data/lib/markdown_ruby_documentation/method/null_method.rb +28 -0
  25. data/lib/markdown_ruby_documentation/method_linker.rb +41 -0
  26. data/lib/markdown_ruby_documentation/print_method_source.rb +19 -0
  27. data/lib/markdown_ruby_documentation/reject_blank_methods.rb +9 -0
  28. data/lib/markdown_ruby_documentation/summary.rb +32 -0
  29. data/lib/markdown_ruby_documentation/template_parser.rb +348 -0
  30. data/lib/markdown_ruby_documentation/version.rb +3 -0
  31. data/lib/markdown_ruby_documentation/write_markdown_to_disk.rb +29 -0
  32. data/markdown_ruby_documenation.gemspec +30 -0
  33. metadata +162 -0
@@ -0,0 +1,29 @@
1
+ require "markdown_ruby_documentation/version"
2
+ require "markdown_ruby_documentation/summary"
3
+ require "markdown_ruby_documentation/instance_to_class_methods"
4
+ require "markdown_ruby_documentation/print_method_source"
5
+ require "markdown_ruby_documentation/template_parser"
6
+ require "markdown_ruby_documentation/markdown_presenter"
7
+ require "markdown_ruby_documentation/generate"
8
+ require "markdown_ruby_documentation/git_hub_link"
9
+ require "markdown_ruby_documentation/git_hub_project"
10
+ require "markdown_ruby_documentation/reject_blank_methods"
11
+ require "markdown_ruby_documentation/method_linker"
12
+ require "markdown_ruby_documentation/method"
13
+ require "markdown_ruby_documentation/method/instance_method"
14
+ require "markdown_ruby_documentation/method/class_method"
15
+ require "markdown_ruby_documentation/method/null_method"
16
+ require "markdown_ruby_documentation/write_markdown_to_disk"
17
+ require "markdown_ruby_documentation/default_erb_methods"
18
+ require "markdown_ruby_documentation/constants_presenter"
19
+ require "ruby-progressbar"
20
+ require "active_support/core_ext/string"
21
+ require "method_source"
22
+ require "json"
23
+ require "active_support/dependencies/autoload"
24
+ require "active_support/number_helper"
25
+
26
+ module MarkdownRubyDocumentation
27
+ START_TOKEN = "=mark_doc".freeze
28
+ END_TOKEN = "=mark_end".freeze
29
+ end
@@ -0,0 +1,38 @@
1
+ module MarkdownRubyDocumentation
2
+ class ConstantsPresenter
3
+
4
+ attr_reader :subject
5
+
6
+ def initialize(subject)
7
+ @subject = subject
8
+ end
9
+
10
+ def call(interface)
11
+ constants.each do |const_name, value|
12
+ interface[const_name] = { text: value, method_object: Method.create("#{subject.name}::#{const_name}", null_method: true) }
13
+ end
14
+ interface
15
+ end
16
+
17
+ private
18
+
19
+ def constants
20
+ subject.constants.each_with_object({}) do |v, const|
21
+ c = subject.const_get(v)
22
+ const[v] = format(c) unless c.class == Module || c.class == Class
23
+ end
24
+ end
25
+
26
+ def format(value)
27
+ case value
28
+ when Fixnum
29
+ ActiveSupport::NumberHelper.number_to_delimited(value)
30
+ when String
31
+ value.inspect
32
+ else
33
+ value
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ module MarkdownRubyDocumentation
2
+ module DefaultErbMethods
3
+ def link_to_markdown(klass, title:)
4
+ "[#{title}](#{klass})"
5
+ end
6
+
7
+ def self.erb_methods_module
8
+ self
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,107 @@
1
+ module MarkdownRubyDocumentation
2
+ class Generate
3
+ # @param [Class] subjects ruby classes to generate documentation from.
4
+ # @param [Module] erb_methods must contain #link_to_markdown and contain any additional methods for comment ERB
5
+ # @param [Proc] output_proc given name: and text: for use in saving the the files.
6
+ def self.run(
7
+ subjects:, erb_methods: DefaultErbMethods, output_proc: -> (name:, text:) { { name => text } }
8
+ )
9
+ erb_methods_class = Class.new
10
+ erb_methods_class.extend TemplateParser::CommentMacros
11
+ erb_methods_class.extend erb_methods
12
+ TemplateParser::CommentMacros.include erb_methods
13
+ left_padding = subjects.map(&:name).group_by(&:size).max.first
14
+ progressbar = ProgressBar.create(title: "Compiling Markdown".ljust(left_padding), total: subjects.count+ 1)
15
+ pages = subjects.map do |subject|
16
+ progressbar.title = subject.name.ljust(left_padding)
17
+ Page.new(subject: subject,
18
+ output_proc: output_proc,
19
+ erb_methods_class: erb_methods_class).call.tap { progressbar.increment }
20
+ end
21
+
22
+ return_value = pages.each_with_object({}) do |page, hash|
23
+ name_parts = page.subject.name.split("::")
24
+ name = name_parts.pop
25
+ namespace = name_parts.join("::")
26
+ hash[namespace] ||= {}
27
+ hash[namespace].merge!({ name => page })
28
+ hash
29
+ end
30
+ progressbar.title = "Markdown Documentation Compilation Complete".ljust(left_padding)
31
+ progressbar.finish
32
+ return_value
33
+ end
34
+
35
+ class Page
36
+ attr_reader :subject, :output_proc, :erb_methods_class
37
+
38
+ def initialize(subject:,
39
+ methods: [],
40
+ output_proc:,
41
+ erb_methods_class:)
42
+ initialize_methods(methods, subject)
43
+ @erb_methods_class = erb_methods_class
44
+ @subject = subject
45
+ methods = methods
46
+ @methods = methods
47
+ @output_proc = output_proc
48
+ end
49
+
50
+ def call
51
+ methods_pipes = run_pipeline(methods_pipeline)
52
+ text = run_pipeline(string_pipeline, methods_pipes)
53
+ output_proc.call(name: subject.name,
54
+ text: text)
55
+ self
56
+ end
57
+
58
+ private
59
+ def initialize_methods(methods, subject)
60
+ if methods.empty?
61
+ all_instance_and_class_methods(methods, subject)
62
+ else
63
+ methods.map! { |method| method.is_a?(Symbol) ? InstanceMethod.new("#{subject.name}##{method}", context: subject) : method }
64
+ end
65
+ end
66
+
67
+ def all_instance_and_class_methods(methods, subject)
68
+ instance_m = subject.instance_methods(false).concat(subject.private_instance_methods(false))
69
+ klass_m = subject.methods(false).concat(subject.private_methods(false)) - Object.methods
70
+ methods.concat instance_m.map { |method| InstanceMethod.new("#{subject.name}##{method}", context: subject) }
71
+ methods.concat klass_m.map { |method| ClassMethod.new("#{subject.name}.#{method}", context: subject) }
72
+ end
73
+
74
+ def methods_pipeline
75
+ [
76
+ TemplateParser.new(subject, @methods),
77
+ RejectBlankMethod,
78
+ GitHubLink.new(subject: subject),
79
+ ConstantsPresenter.new(subject),
80
+ MarkdownPresenter.new(summary: summary, title_key: section_key),
81
+ ]
82
+ end
83
+
84
+ def string_pipeline
85
+ [
86
+ MethodLinker.new(section_key: section_key, root_path: "./"),
87
+ ]
88
+ end
89
+
90
+ def summary
91
+ @summary ||= Summary.new(subject: subject, erb_methods_class: erb_methods_class)
92
+ end
93
+
94
+ def run_pipeline(pipeline, last_result=nil)
95
+ last_result ||= pipeline.shift.call
96
+ pipeline.each do |pipe|
97
+ last_result = pipe.call(last_result)
98
+ end
99
+ last_result
100
+ end
101
+
102
+ def section_key
103
+ subject.name.underscore.gsub("/", "-")
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,74 @@
1
+ module MarkdownRubyDocumentation
2
+ class GitHubLink
3
+ attr_reader :subject, :base_url, :root
4
+
5
+ def initialize(subject:, base_url: GitHubProject.url, root: GitHubProject.root_path)
6
+ @subject = subject
7
+ @base_url = base_url
8
+ @root = root
9
+ end
10
+
11
+ def call(hash)
12
+ hash.each do |name, values|
13
+ hash[name][:text] = "#{values[:text]}\n\n[show on github](#{create_link(name: name, method_object: values[:method_object])})"
14
+ end
15
+ end
16
+
17
+ def create_link(name: nil, method_object: nil)
18
+ if name && method_object.nil?
19
+ method_object = Method.create("##{name}")
20
+ end
21
+ MethodUrl.new(subject: subject, base_url: base_url, root: root, method_object: method_object).to_s
22
+ end
23
+
24
+ class FileUrl
25
+ attr_reader :file_path, :base_url, :root
26
+
27
+ def initialize(file_path:, base_url: GitHubProject.url, root: GitHubProject.root_path)
28
+ @file_path = file_path
29
+ @base_url = base_url
30
+ @root = root
31
+ end
32
+
33
+ def to_s
34
+ link(file_path)
35
+ end
36
+
37
+ def to_pathname
38
+ Pathname(to_s)
39
+ end
40
+
41
+ def link(file, lineno=nil)
42
+ str = File.join(base_url, "blob", blob(file), relative_path(file))
43
+ unless lineno.nil?
44
+ str << "#L#{lineno}"
45
+ end
46
+ str.chomp
47
+ end
48
+
49
+ def blob(file)
50
+ GitHubProject.branch
51
+ end
52
+
53
+ def relative_path(file)
54
+ file.sub(root, "")
55
+ end
56
+ end
57
+
58
+ class MethodUrl
59
+ attr_reader :base_url, :method_object, :subject, :root
60
+
61
+ def initialize(subject:, method_object:, base_url: GitHubProject.url, root: GitHubProject.root_path)
62
+ @subject = subject
63
+ @base_url = base_url
64
+ @root = root
65
+ @method_object = method_object
66
+ end
67
+
68
+ def to_s
69
+ file, lineno = subject.public_send(method_object.type, method_object.name).source_location
70
+ FileUrl.new(file_path: file, base_url: base_url, root: root).link(file, lineno)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,29 @@
1
+ module MarkdownRubyDocumentation
2
+ class GitHubProject
3
+ class << self
4
+ def git_url
5
+ `git config --get remote.origin.url`.chomp
6
+ end
7
+
8
+ def url
9
+ "https://github.com/#{git_url.split(":").last.gsub(".git", "")}".chomp
10
+ end
11
+
12
+ def root_path
13
+ `git rev-parse --show-toplevel`.chomp
14
+ end
15
+
16
+ def set_branch(branch)
17
+ @branch = branch
18
+ end
19
+
20
+ def branch
21
+ @branch || current_branch
22
+ end
23
+
24
+ def current_branch
25
+ `git rev-parse --abbrev-ref HEAD`.chomp
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ module MarkdownRubyDocumentation
2
+ class InstanceToClassMethods
3
+ attr_reader :method_object
4
+
5
+ def initialize(method:)
6
+ @method_object = method
7
+ end
8
+
9
+ def eval_instance_method
10
+ _module = method_object.context.const_set(new_class_name, Class.new(method_object.context))
11
+ rescue_and_define_method(_module) do |_module|
12
+ create_method(method_object, _module)
13
+ _module.send(method_object.name)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def new_class_name
20
+ "InstanceToClassMethods#{method_object.context.name}#{('A'..'Z').to_a.sample(5).join}".delete("::")
21
+ end
22
+
23
+ def rescue_and_define_method(_module, &block)
24
+ block.call(_module)
25
+ rescue NameError => e
26
+ if (undefined_method = e.message.match(/undefined local variable or method `(.+)'/).try!(:captures).try!(:first))
27
+ undefined_method = Method.create("##{undefined_method}", context: method_object.context)
28
+ create_method(undefined_method, _module)
29
+ rescue_and_define_method(_module, &block)
30
+ else
31
+ raise e
32
+ end
33
+ end
34
+
35
+ def create_method(method, m=Module.new)
36
+ m.class_eval <<-RUBY, __FILE__, __LINE__ + 1
37
+ def self.#{method.name}
38
+ #{PrintMethodSource.new(method: method).print}
39
+ end
40
+ RUBY
41
+ m
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,36 @@
1
+ module MarkdownRubyDocumentation
2
+ class MarkdownPresenter
3
+
4
+ attr_reader :items, :title_key, :summary
5
+
6
+ def initialize(items: nil, summary:, title_key:, skip_blanks: true)
7
+ @items = items
8
+ @summary = summary
9
+ @title_key = title_key
10
+ @skip_blanks = skip_blanks
11
+ end
12
+
13
+ def call(items=nil)
14
+ @items ||= items
15
+ <<-MD
16
+ # #{summary.title}
17
+ #{summary.summary}
18
+
19
+ #{present_items}
20
+ MD
21
+ end
22
+
23
+ private
24
+
25
+ def present_items
26
+ items.map do |name, hash|
27
+ begin
28
+ %[## #{name.to_s.titleize}\n#{hash[:text]}] unless hash[:text].blank?
29
+ rescue =>e
30
+ puts e
31
+ end
32
+ end.join("\n\n")
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,101 @@
1
+ module MarkdownRubyDocumentation
2
+ class Method
3
+ attr_reader :method_reference
4
+ protected :method_reference
5
+
6
+ def initialize(method_reference, context: Kernel)
7
+ @method_reference = method_reference.to_s
8
+ @context = context
9
+ end
10
+
11
+ # @param [String] method_reference
12
+ # @example
13
+ # ".class_method_name" class method in the current scope.
14
+ # "Constant.class_method_name" class method on a specific constant.
15
+ # "SomeClass#instance_method_name" an instance method on a specific constant.
16
+ # "#instance_method_name" an instance method in the current scope.
17
+ def self.create(method_reference, null_method: false, context: Kernel)
18
+ case method_reference
19
+ when InstanceMethod
20
+ InstanceMethod.new(method_reference, context: context)
21
+ when ClassMethod
22
+ ClassMethod.new(method_reference, context: context)
23
+ else
24
+ if null_method
25
+ NullMethod.new(method_reference, context: context)
26
+ else
27
+ raise ArgumentError, "method_reference is formatted incorrectly: '#{method_reference}'"
28
+ end
29
+ end
30
+ end
31
+
32
+ def ==(other_method)
33
+ self.class == other_method.class && other_method.method_reference == self.method_reference
34
+ end
35
+
36
+ alias :eql? :==
37
+
38
+ def hash
39
+ @method_reference.hash
40
+ end
41
+
42
+ def self.===(value)
43
+ if value.is_a?(String)
44
+ value.include?(type_symbol)
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ # @return [String]
51
+ def type_symbol
52
+ self.class.type_symbol
53
+ end
54
+
55
+ # @return [Class]
56
+ def context
57
+ if method_reference.start_with?(type_symbol)
58
+ @context
59
+ else
60
+ constant = method_reference.split(type_symbol).first
61
+ begin
62
+ constant.constantize
63
+ rescue NameError => e
64
+ @context.const_get(constant)
65
+ end
66
+ end
67
+ end
68
+
69
+ def context_name
70
+ if method_reference.start_with?(type_symbol)
71
+ @context.name
72
+ else
73
+ method_reference.split(type_symbol).first
74
+ end
75
+ end
76
+
77
+ # @return [Symbol]
78
+ def name
79
+ method_reference.split(type_symbol).last.try!(:to_sym)
80
+ end
81
+
82
+ # @return [String]
83
+ def to_s
84
+ method_reference
85
+ end
86
+
87
+ # @return [String]
88
+ def inspect
89
+ "#<#{self.class.name} #{to_s}>"
90
+ end
91
+
92
+ # @return [Proc]
93
+ def to_proc
94
+ context.public_send(type, name)
95
+ end
96
+
97
+ def type
98
+ raise NotImplementedError
99
+ end
100
+ end
101
+ end