markdown_ruby_documentation 0.1.0

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 (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