markdown_ruby_documentation 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +134 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/github-markdown.css +656 -0
- data/lib/markdown_ruby_documentation.rb +29 -0
- data/lib/markdown_ruby_documentation/constants_presenter.rb +38 -0
- data/lib/markdown_ruby_documentation/default_erb_methods.rb +11 -0
- data/lib/markdown_ruby_documentation/generate.rb +107 -0
- data/lib/markdown_ruby_documentation/git_hub_link.rb +74 -0
- data/lib/markdown_ruby_documentation/git_hub_project.rb +29 -0
- data/lib/markdown_ruby_documentation/instance_to_class_methods.rb +44 -0
- data/lib/markdown_ruby_documentation/markdown_presenter.rb +36 -0
- data/lib/markdown_ruby_documentation/method.rb +101 -0
- data/lib/markdown_ruby_documentation/method/class_method.rb +13 -0
- data/lib/markdown_ruby_documentation/method/instance_method.rb +11 -0
- data/lib/markdown_ruby_documentation/method/null_method.rb +28 -0
- data/lib/markdown_ruby_documentation/method_linker.rb +41 -0
- data/lib/markdown_ruby_documentation/print_method_source.rb +19 -0
- data/lib/markdown_ruby_documentation/reject_blank_methods.rb +9 -0
- data/lib/markdown_ruby_documentation/summary.rb +32 -0
- data/lib/markdown_ruby_documentation/template_parser.rb +348 -0
- data/lib/markdown_ruby_documentation/version.rb +3 -0
- data/lib/markdown_ruby_documentation/write_markdown_to_disk.rb +29 -0
- data/markdown_ruby_documenation.gemspec +30 -0
- 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,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
|