markdown_helper 1.8.0 → 1.9.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.
- checksums.yaml +4 -4
- data/.travis.yml +5 -5
- data/CODE_OF_CONDUCT.md +74 -74
- data/Gemfile.lock +2 -1
- data/LICENSE.txt +21 -21
- data/README.md +60 -47
- data/Rakefile +16 -13
- data/bin/_include +0 -1
- data/bin/_resolve +0 -1
- data/bin/markdown_helper +3 -0
- data/bin/setup +8 -8
- data/lib/markdown_helper.rb +329 -97
- data/lib/markdown_helper/version.rb +1 -1
- data/markdown/{README.template.md → readme/README.template.md} +25 -12
- data/markdown/{code_block_ruby_template.md → readme/code_block_ruby_template.md} +0 -0
- data/markdown/{highlight_ruby_template.md → readme/highlight_ruby_template.md} +0 -0
- data/markdown/readme/highlighted_ruby.md +12 -0
- data/markdown/{include.md → readme/include.md} +0 -0
- data/markdown/{include.rb → readme/include.rb} +0 -0
- data/markdown/{include_usage.rb → readme/include_usage.rb} +0 -0
- data/markdown/{resolve.md → readme/resolve.md} +0 -0
- data/markdown/{resolve_usage.rb → readme/resolve_usage.rb} +0 -0
- data/markdown/{verbatim_ruby_template.md → readme/verbatim_ruby_template.md} +0 -0
- data/markdown/use_cases/Rakefile +53 -23
- data/markdown/use_cases/include/diagnose_circular_includes/diagnose_circular_includes.err +26 -0
- data/markdown/use_cases/include/diagnose_circular_includes/diagnose_circular_includes.rb +65 -0
- data/markdown/use_cases/include/diagnose_circular_includes/include.rb +5 -0
- data/markdown/use_cases/include/diagnose_circular_includes/includer.md +1 -0
- data/markdown/use_cases/include/diagnose_circular_includes/includer_0.md +1 -0
- data/markdown/use_cases/include/diagnose_circular_includes/includer_1.md +1 -0
- data/markdown/use_cases/include/diagnose_circular_includes/includer_2.md +1 -0
- data/markdown/use_cases/include/diagnose_circular_includes/use_case.md +98 -0
- data/markdown/use_cases/include/diagnose_circular_includes/use_case_template.md +27 -0
- data/markdown/use_cases/include/diagnose_missing_includee/diagnose_missing_includee.err +26 -0
- data/markdown/use_cases/include/diagnose_missing_includee/diagnose_missing_includee.rb +67 -0
- data/markdown/use_cases/include/diagnose_missing_includee/include.rb +5 -0
- data/markdown/use_cases/include/diagnose_missing_includee/included.md +1 -0
- data/markdown/use_cases/include/diagnose_missing_includee/includer.md +1 -0
- data/markdown/use_cases/include/diagnose_missing_includee/includer_0.md +1 -0
- data/markdown/use_cases/include/diagnose_missing_includee/includer_1.md +1 -0
- data/markdown/use_cases/include/diagnose_missing_includee/includer_2.md +1 -0
- data/markdown/use_cases/include/diagnose_missing_includee/use_case.md +100 -0
- data/markdown/use_cases/include/diagnose_missing_includee/use_case_template.md +29 -0
- data/markdown/use_cases/include/include.rb +5 -0
- data/markdown/use_cases/include/include_code_block/hello.rb +8 -0
- data/markdown/use_cases/include/include_code_block/include_code_block.rb +79 -0
- data/markdown/use_cases/include/include_code_block/included.md +14 -0
- data/markdown/use_cases/include/include_code_block/includer.md +4 -0
- data/markdown/use_cases/include/include_code_block/use_case.md +108 -0
- data/markdown/use_cases/include/include_code_block/use_case_template.md +33 -0
- data/markdown/use_cases/include/include_generated_text/include_generated_text.rb +38 -0
- data/markdown/use_cases/include/include_generated_text/use_case.md +18 -0
- data/markdown/use_cases/include/include_generated_text/use_case_template.md +18 -0
- data/markdown/use_cases/include/include_highlighted_code/hello.rb +8 -0
- data/markdown/use_cases/include/include_highlighted_code/include_highlighted_code.rb +83 -0
- data/markdown/use_cases/include/include_highlighted_code/included.md +14 -0
- data/markdown/use_cases/include/include_highlighted_code/includer.md +4 -0
- data/markdown/use_cases/include/include_highlighted_code/use_case.md +110 -0
- data/markdown/use_cases/include/include_highlighted_code/use_case_template.md +35 -0
- data/markdown/use_cases/include/include_markdown/include_markdown.rb +81 -0
- data/markdown/use_cases/include/include_markdown/included.md +13 -0
- data/markdown/use_cases/include/include_markdown/includer.md +4 -0
- data/markdown/use_cases/include/include_markdown/markdown.md +10 -0
- data/markdown/use_cases/include/include_markdown/use_case.md +106 -0
- data/markdown/use_cases/include/include_markdown/use_case_template.md +33 -0
- data/markdown/use_cases/include/include_use_case.rb +110 -0
- data/markdown/use_cases/include/include_with_added_comments/include_with_added_comments.rb +43 -60
- data/markdown/use_cases/include/include_with_added_comments/included.md +2 -2
- data/markdown/use_cases/include/include_with_added_comments/use_case.md +56 -0
- data/markdown/use_cases/include/include_with_added_comments/{template.md → use_case_template.md} +20 -1
- data/markdown/use_cases/include/interface.md +25 -0
- data/markdown/use_cases/include/nest_inclusions/included.md +5 -0
- data/markdown/use_cases/include/nest_inclusions/includee.md +3 -0
- data/markdown/use_cases/include/nest_inclusions/includer.md +3 -0
- data/markdown/use_cases/include/nest_inclusions/nest_inclusions.rb +61 -0
- data/markdown/use_cases/include/nest_inclusions/nested_includee.md +1 -0
- data/markdown/use_cases/include/nest_inclusions/use_case.md +74 -0
- data/markdown/use_cases/include/nest_inclusions/use_case_template.md +23 -0
- data/markdown/use_cases/include/reuse_text/include.rb +5 -0
- data/markdown/use_cases/include/reuse_text/included.md +2 -5
- data/markdown/use_cases/include/reuse_text/includee.md +1 -0
- data/markdown/use_cases/include/reuse_text/includer.md +2 -5
- data/markdown/use_cases/include/reuse_text/reuse_text.rb +24 -77
- data/markdown/use_cases/include/reuse_text/use_case.md +69 -0
- data/markdown/use_cases/include/reuse_text/use_case_template.md +23 -0
- data/markdown/use_cases/resolve/gemify_images/gemify_images.rb +58 -29
- data/markdown/use_cases/resolve/gemify_images/template.md +4 -0
- data/markdown/use_cases/use_case.rb +45 -0
- data/markdown/use_cases/use_cases.md +9 -2
- data/markdown_helper.gemspec +1 -1
- metadata +70 -19
- data/markdown/highlighted_ruby.md +0 -12
- data/markdown/use_cases/include/include_generated_text/build.rb +0 -46
- data/markdown/use_cases/include/include_with_added_comments/include_with_added_comments.md +0 -37
- data/markdown/use_cases/include/reuse_text/reusable_text.md +0 -1
- data/markdown/use_cases/include/reuse_text/reuse_text.md +0 -54
- data/markdown/use_cases/include/reuse_text/template.md +0 -33
data/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rake/testtask'
|
3
3
|
|
4
|
+
require_relative 'lib/markdown_helper'
|
5
|
+
|
4
6
|
Rake::TestTask.new(:test) do |t|
|
5
7
|
t.libs << 'test'
|
6
8
|
t.libs << 'lib'
|
@@ -12,19 +14,20 @@ namespace :build do
|
|
12
14
|
desc 'Build README.md file from README.template.md'
|
13
15
|
task :readme do
|
14
16
|
Rake::Task['build:usages'].invoke
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
Dir.chdir('markdown/readme') do
|
18
|
+
markdown_helper = MarkdownHelper.new
|
19
|
+
template_file_path = 'highlight_ruby_template.md'
|
20
|
+
markdown_file_path = 'highlighted_ruby.md'
|
21
|
+
markdown_helper.include(template_file_path, markdown_file_path)
|
22
|
+
# Do the resolve before the include, so that the included text is not also resolved.
|
23
|
+
# This protects example code from being also resolved, thus damaging the example code.
|
24
|
+
# Temp file must be in the same directory as its source (it becomes the source).
|
25
|
+
temp_file_path = 'temp_resolved.md'
|
26
|
+
markdown_helper.resolve('README.template.md', temp_file_path)
|
27
|
+
readme_file_path = '../../README.md'
|
28
|
+
markdown_helper.include(temp_file_path, readme_file_path)
|
29
|
+
File.delete(temp_file_path)
|
30
|
+
end
|
28
31
|
end
|
29
32
|
|
30
33
|
desc 'Build usage for executables'
|
data/bin/_include
CHANGED
data/bin/_resolve
CHANGED
data/bin/markdown_helper
CHANGED
data/bin/setup
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
#!/usr/bin/env bash
|
2
|
-
set -euo pipefail
|
3
|
-
IFS=$'\n\t'
|
4
|
-
set -vx
|
5
|
-
|
6
|
-
bundle install
|
7
|
-
|
8
|
-
# Do any other automated setup that you need to do here
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
set -vx
|
5
|
+
|
6
|
+
bundle install
|
7
|
+
|
8
|
+
# Do any other automated setup that you need to do here
|
data/lib/markdown_helper.rb
CHANGED
@@ -7,19 +7,27 @@ require 'markdown_helper/version'
|
|
7
7
|
# @author Burdette Lamar
|
8
8
|
class MarkdownHelper
|
9
9
|
|
10
|
+
class MarkdownHelperError < RuntimeError; end
|
11
|
+
class CircularIncludeError < MarkdownHelperError; end
|
12
|
+
class MissingIncludeeError < MarkdownHelperError; end
|
13
|
+
class OptionError < MarkdownHelperError; end
|
14
|
+
class EnvironmentError < MarkdownHelperError; end
|
15
|
+
|
10
16
|
IMAGE_REGEXP = /!\[([^\[]+)\]\(([^)]+)\)/
|
11
17
|
INCLUDE_REGEXP = /^@\[([^\[]+)\]\(([^)]+)\)$/
|
12
18
|
|
13
19
|
attr_accessor :pristine
|
14
20
|
|
15
21
|
def initialize(options = {})
|
22
|
+
# Confirm that we're in a git project.
|
23
|
+
MarkdownHelper.git_clone_dir_path
|
16
24
|
default_options = {
|
17
25
|
:pristine => false,
|
18
26
|
}
|
19
27
|
merged_options = default_options.merge(options)
|
20
28
|
merged_options.each_pair do |method, value|
|
21
29
|
unless self.respond_to?(method)
|
22
|
-
raise
|
30
|
+
raise OptionError.new("Unknown option: #{method}")
|
23
31
|
end
|
24
32
|
setter_method = "#{method}="
|
25
33
|
send(setter_method, value)
|
@@ -42,7 +50,7 @@ class MarkdownHelper
|
|
42
50
|
# @[:markdown](foo.md)
|
43
51
|
def include(template_file_path, markdown_file_path)
|
44
52
|
send(:generate_file, template_file_path, markdown_file_path, __method__) do |input_lines, output_lines|
|
45
|
-
send(:include_files, template_file_path, input_lines, output_lines,
|
53
|
+
send(:include_files, template_file_path, input_lines, output_lines, Inclusions.new)
|
46
54
|
end
|
47
55
|
end
|
48
56
|
|
@@ -77,7 +85,7 @@ class MarkdownHelper
|
|
77
85
|
|
78
86
|
private
|
79
87
|
|
80
|
-
def comment(text)
|
88
|
+
def self.comment(text)
|
81
89
|
"<!--#{text}-->\n"
|
82
90
|
end
|
83
91
|
|
@@ -86,27 +94,48 @@ class MarkdownHelper
|
|
86
94
|
repo_name = ENV['REPO_NAME']
|
87
95
|
unless repo_user and repo_name
|
88
96
|
message = 'ENV values for both REPO_USER and REPO_NAME must be defined.'
|
89
|
-
raise
|
97
|
+
raise EnvironmentError.new(message)
|
90
98
|
end
|
91
99
|
[repo_user, repo_name]
|
92
100
|
end
|
93
101
|
|
94
102
|
def generate_file(template_file_path, markdown_file_path, method)
|
95
103
|
output_lines = []
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
104
|
+
begin
|
105
|
+
File.open(template_file_path, 'r') do |template_file|
|
106
|
+
template_path_in_project = MarkdownHelper.path_in_project(template_file_path)
|
107
|
+
output_lines.push(MarkdownHelper.comment(" >>>>>> BEGIN GENERATED FILE (#{method.to_s}): SOURCE #{template_path_in_project} ")) unless pristine
|
108
|
+
input_lines = template_file.readlines
|
109
|
+
yield input_lines, output_lines
|
110
|
+
output_lines.push(MarkdownHelper.comment(" <<<<<< END GENERATED FILE (#{method.to_s}): SOURCE #{template_path_in_project} ")) unless pristine
|
111
|
+
end
|
112
|
+
output = output_lines.join('')
|
113
|
+
rescue => e
|
114
|
+
unless e.kind_of?(MarkdownHelperError)
|
115
|
+
message = [
|
116
|
+
e.message,
|
117
|
+
Inclusions::UNREADABLE_TEMPLATE_EXCEPTION_LABEL,
|
118
|
+
].join("\n")
|
119
|
+
e = e.exception(message)
|
120
|
+
end
|
121
|
+
raise e
|
101
122
|
end
|
102
|
-
|
103
|
-
|
104
|
-
|
123
|
+
begin
|
124
|
+
File.write(markdown_file_path, output)
|
125
|
+
rescue => e
|
126
|
+
unless e.kind_of?(MarkdownHelperError)
|
127
|
+
message = [
|
128
|
+
e.message,
|
129
|
+
Inclusions::UNWRITABLE_OUTPUT_EXCEPTION_LABEL,
|
130
|
+
].join("\n")
|
131
|
+
e = e.exception(message)
|
132
|
+
end
|
133
|
+
raise e
|
105
134
|
end
|
106
135
|
output
|
107
136
|
end
|
108
137
|
|
109
|
-
def include_files(includer_file_path, input_lines, output_lines,
|
138
|
+
def include_files(includer_file_path, input_lines, output_lines, inclusions)
|
110
139
|
|
111
140
|
input_lines.each_with_index do |input_line, line_index|
|
112
141
|
match_data = input_line.match(INCLUDE_REGEXP)
|
@@ -115,73 +144,42 @@ class MarkdownHelper
|
|
115
144
|
next
|
116
145
|
end
|
117
146
|
treatment = match_data[1]
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
:code_block
|
122
|
-
when ':markdown'
|
123
|
-
:markdown
|
124
|
-
when ':verbatim'
|
125
|
-
message = "Treatment ':verbatim' is deprecated; please use treatment ':markdown'."
|
126
|
-
warn(message)
|
127
|
-
:markdown
|
128
|
-
when ':comment'
|
129
|
-
:comment
|
130
|
-
else
|
131
|
-
treatment
|
132
|
-
end
|
133
|
-
new_inclusion = Inclusion.new(
|
147
|
+
cited_includee_file_path = match_data[2]
|
148
|
+
inclusions.include(
|
149
|
+
input_line.chomp,
|
134
150
|
includer_file_path,
|
135
|
-
|
136
|
-
|
151
|
+
line_index + 1,
|
152
|
+
cited_includee_file_path,
|
153
|
+
treatment,
|
154
|
+
output_lines,
|
155
|
+
self
|
137
156
|
)
|
138
|
-
included_real_path = new_inclusion.included_real_path
|
139
|
-
if treatment == :markdown
|
140
|
-
previously_included = markdown_inclusions.include?(included_real_path)
|
141
|
-
if previously_included
|
142
|
-
markdown_inclusions.store(included_real_path, new_inclusion)
|
143
|
-
message_lines = ['Includes are circular:']
|
144
|
-
i = 0
|
145
|
-
markdown_inclusions.each_with_index do |path_and_inclusion, i|
|
146
|
-
_, inclusion = *path_and_inclusion
|
147
|
-
message_lines.push(" Level #{i}:")
|
148
|
-
message_lines.push(" Includer: #{inclusion.includer_file_path}:#{inclusion.includer_line_number}")
|
149
|
-
message_lines.push(" Relative file path: #{inclusion.relative_included_file_path}")
|
150
|
-
message_lines.push(" Included file path: #{inclusion.included_file_path}")
|
151
|
-
message_lines.push(" Real file_path: #{inclusion.included_real_path}")
|
152
|
-
end
|
153
|
-
message = message_lines.join("\n")
|
154
|
-
raise RuntimeError.new(message)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
output_lines.push(comment(" >>>>>> BEGIN INCLUDED FILE (#{treatment}): SOURCE #{new_inclusion.included_file_path} ")) unless pristine
|
158
|
-
include_lines = File.readlines(new_inclusion.included_file_path)
|
159
|
-
unless include_lines.last.match("\n")
|
160
|
-
message = "Warning: Included file has no trailing newline: #{relative_included_file_path}"
|
161
|
-
warn(message)
|
162
|
-
end
|
163
|
-
case treatment
|
164
|
-
when :markdown
|
165
|
-
# Pass through unadorned, but honor any nested includes.
|
166
|
-
markdown_inclusions.store(included_real_path, new_inclusion)
|
167
|
-
include_files(new_inclusion.included_file_path, include_lines, output_lines, markdown_inclusions)
|
168
|
-
markdown_inclusions.delete(included_real_path)
|
169
|
-
when :comment
|
170
|
-
output_lines.push(comment(include_lines.join('')))
|
171
|
-
else
|
172
|
-
# Use the file name as a label.
|
173
|
-
file_name_line = format("<code>%s</code>\n", File.basename(relative_included_file_path))
|
174
|
-
output_lines.push(file_name_line)
|
175
|
-
# Put into code block.
|
176
|
-
language = treatment == :code_block ? '' : treatment
|
177
|
-
output_lines.push("```#{language}\n")
|
178
|
-
output_lines.push(*include_lines)
|
179
|
-
output_lines.push("```\n")
|
180
|
-
end
|
181
|
-
output_lines.push(comment(" <<<<<< END INCLUDED FILE (#{treatment}): SOURCE #{new_inclusion.included_file_path} ")) unless pristine
|
182
157
|
end
|
183
158
|
end
|
184
159
|
|
160
|
+
def self.git_clone_dir_path
|
161
|
+
git_dir = `git rev-parse --git-dir`.chomp
|
162
|
+
unless $?.success?
|
163
|
+
message = <<EOT
|
164
|
+
|
165
|
+
Markdown helper must run inside a .git project.
|
166
|
+
That is, the working directory one of its parents must be a .git directory.
|
167
|
+
EOT
|
168
|
+
raise RuntimeError.new(message)
|
169
|
+
end
|
170
|
+
if git_dir == '.git'
|
171
|
+
path = `pwd`.chomp
|
172
|
+
else
|
173
|
+
path = File.dirname(git_dir).chomp
|
174
|
+
end
|
175
|
+
realpath = Pathname.new(path.sub(%r|/c/|, 'C:/')).realpath
|
176
|
+
realpath.to_s
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.path_in_project(path)
|
180
|
+
path.sub(MarkdownHelper.git_clone_dir_path + '/', '')
|
181
|
+
end
|
182
|
+
|
185
183
|
def resolve_images(template_file_path, input_lines, output_lines)
|
186
184
|
input_lines.each do |input_line|
|
187
185
|
scan_data = input_line.scan(IMAGE_REGEXP)
|
@@ -189,7 +187,7 @@ class MarkdownHelper
|
|
189
187
|
output_lines.push(input_line)
|
190
188
|
next
|
191
189
|
end
|
192
|
-
output_lines.push(comment(" >>>>>> BEGIN RESOLVED IMAGES: INPUT-LINE '#{input_line}' ")) unless pristine
|
190
|
+
output_lines.push(MarkdownHelper.comment(" >>>>>> BEGIN RESOLVED IMAGES: INPUT-LINE '#{input_line}' ")) unless pristine
|
193
191
|
output_line = input_line
|
194
192
|
scan_data.each do |alt_text, path_and_attributes|
|
195
193
|
original_image_file_path, attributes_s = path_and_attributes.split(/\s?\|\s?/, 2)
|
@@ -206,14 +204,6 @@ class MarkdownHelper
|
|
206
204
|
if original_image_file_path.start_with?('http')
|
207
205
|
image_path = original_image_file_path
|
208
206
|
else
|
209
|
-
git_dir = `git rev-parse --git-dir`.chomp
|
210
|
-
if git_dir == '.git'
|
211
|
-
git_clone_dir_path = `pwd`.chomp
|
212
|
-
else
|
213
|
-
git_clone_dir_path = git_dir.chomp
|
214
|
-
end
|
215
|
-
git_clone_dir_pathname = Pathname.new(git_clone_dir_path.sub(%r|/c/|, 'C:/')).realpath
|
216
|
-
git_clone_dir_path = git_clone_dir_pathname.to_s
|
217
207
|
absolute_template_file_path = File.absolute_path(template_file_path)
|
218
208
|
template_dir_path = File.dirname(absolute_template_file_path)
|
219
209
|
absolute_file_path = File.join(
|
@@ -221,7 +211,7 @@ class MarkdownHelper
|
|
221
211
|
original_image_file_path,
|
222
212
|
)
|
223
213
|
absolute_file_path = Pathname.new(absolute_file_path).cleanpath.to_s
|
224
|
-
relative_image_file_path =
|
214
|
+
relative_image_file_path = MarkdownHelper.path_in_project(absolute_file_path)
|
225
215
|
repo_user, repo_name = repo_user_and_name
|
226
216
|
image_path = File.join(
|
227
217
|
"https://raw.githubusercontent.com/#{repo_user}/#{repo_name}/master",
|
@@ -237,34 +227,276 @@ class MarkdownHelper
|
|
237
227
|
output_line = output_line.sub(IMAGE_REGEXP, img_element)
|
238
228
|
end
|
239
229
|
output_lines.push(output_line)
|
240
|
-
output_lines.push(comment(" <<<<<< END RESOLVED IMAGES: INPUT-LINE '#{input_line}' ")) unless pristine
|
230
|
+
output_lines.push(MarkdownHelper.comment(" <<<<<< END RESOLVED IMAGES: INPUT-LINE '#{input_line}' ")) unless pristine
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
class Inclusions
|
236
|
+
|
237
|
+
attr_accessor :inclusions
|
238
|
+
|
239
|
+
def initialize
|
240
|
+
self.inclusions = []
|
241
|
+
end
|
242
|
+
|
243
|
+
def include(
|
244
|
+
include_description,
|
245
|
+
includer_file_path,
|
246
|
+
includer_line_number,
|
247
|
+
cited_includee_file_path,
|
248
|
+
treatment,
|
249
|
+
output_lines,
|
250
|
+
markdown_helper
|
251
|
+
)
|
252
|
+
treatment = case treatment
|
253
|
+
when ':code_block'
|
254
|
+
:code_block
|
255
|
+
when ':markdown'
|
256
|
+
:markdown
|
257
|
+
when ':verbatim'
|
258
|
+
message = "Treatment ':verbatim' is deprecated; please use treatment ':markdown'."
|
259
|
+
warn(message)
|
260
|
+
:markdown
|
261
|
+
when ':comment'
|
262
|
+
:comment
|
263
|
+
when ':pre'
|
264
|
+
:pre
|
265
|
+
else
|
266
|
+
treatment
|
267
|
+
end
|
268
|
+
new_inclusion = Inclusion.new(
|
269
|
+
include_description,
|
270
|
+
includer_file_path,
|
271
|
+
includer_line_number,
|
272
|
+
cited_includee_file_path
|
273
|
+
)
|
274
|
+
if treatment == :markdown
|
275
|
+
check_circularity(new_inclusion)
|
276
|
+
end
|
277
|
+
includee_path_in_project = MarkdownHelper.path_in_project(new_inclusion.absolute_includee_file_path)
|
278
|
+
output_lines.push(MarkdownHelper.comment(" >>>>>> BEGIN INCLUDED FILE (#{treatment}): SOURCE #{includee_path_in_project} ")) unless markdown_helper.pristine
|
279
|
+
begin
|
280
|
+
include_lines = File.readlines(new_inclusion.absolute_includee_file_path)
|
281
|
+
rescue => e
|
282
|
+
inclusions.push(new_inclusion)
|
283
|
+
message = [
|
284
|
+
MISSING_INCLUDEE_EXCEPTION_LABEL,
|
285
|
+
backtrace_inclusions,
|
286
|
+
].join("\n")
|
287
|
+
e = MissingIncludeeError.new(message)
|
288
|
+
e.set_backtrace([])
|
289
|
+
raise e
|
290
|
+
end
|
291
|
+
unless include_lines.last.match("\n")
|
292
|
+
message = "Warning: Included file has no trailing newline: #{cited_includee_file_path}"
|
293
|
+
warn(message)
|
294
|
+
end
|
295
|
+
case treatment
|
296
|
+
when :markdown
|
297
|
+
# Pass through unadorned, but honor any nested includes.
|
298
|
+
inclusions.push(new_inclusion)
|
299
|
+
markdown_helper.send(:include_files, new_inclusion.absolute_includee_file_path, include_lines, output_lines, self)
|
300
|
+
inclusions.pop
|
301
|
+
when :comment
|
302
|
+
output_lines.push(MarkdownHelper.comment(include_lines.join('')))
|
303
|
+
when :pre
|
304
|
+
output_lines.push("<pre>\n")
|
305
|
+
output_lines.push(include_lines.join(''))
|
306
|
+
output_lines.push("</pre>\n")
|
307
|
+
else
|
308
|
+
# Use the file name as a label.
|
309
|
+
file_name_line = format("```%s```:\n", File.basename(cited_includee_file_path))
|
310
|
+
output_lines.push(file_name_line)
|
311
|
+
# Put into code block.
|
312
|
+
language = treatment == :code_block ? '' : treatment
|
313
|
+
output_lines.push("```#{language}\n")
|
314
|
+
output_lines.push(*include_lines)
|
315
|
+
output_lines.push("```\n")
|
316
|
+
end
|
317
|
+
output_lines.push(MarkdownHelper.comment(" <<<<<< END INCLUDED FILE (#{treatment}): SOURCE #{includee_path_in_project} ")) unless markdown_helper.pristine
|
318
|
+
end
|
319
|
+
|
320
|
+
CIRCULAR_EXCEPTION_LABEL = 'Includes are circular:'
|
321
|
+
UNREADABLE_TEMPLATE_EXCEPTION_LABEL = 'Could not read template file.'
|
322
|
+
UNWRITABLE_OUTPUT_EXCEPTION_LABEL = 'Could not write markdown file.'
|
323
|
+
MISSING_INCLUDEE_EXCEPTION_LABEL = 'Could not read include file,'
|
324
|
+
LEVEL_LABEL = ' Level'
|
325
|
+
BACKTRACE_LABEL = ' Backtrace (innermost include first):'
|
326
|
+
|
327
|
+
def check_circularity(new_inclusion)
|
328
|
+
previous_inclusions = inclusions.collect {|x| x.real_includee_file_path}
|
329
|
+
previously_included = previous_inclusions.include?(new_inclusion.real_includee_file_path)
|
330
|
+
if previously_included
|
331
|
+
inclusions.push(new_inclusion)
|
332
|
+
message = [
|
333
|
+
CIRCULAR_EXCEPTION_LABEL,
|
334
|
+
backtrace_inclusions,
|
335
|
+
].join("\n")
|
336
|
+
e = MarkdownHelper::CircularIncludeError.new(message)
|
337
|
+
e.set_backtrace([])
|
338
|
+
raise e
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def backtrace_inclusions
|
343
|
+
lines = [BACKTRACE_LABEL]
|
344
|
+
inclusions.reverse.each_with_index do |inclusion, i|
|
345
|
+
lines.push("#{LEVEL_LABEL} #{i}:")
|
346
|
+
level_lines = inclusion.to_lines(indentation_level = 3)
|
347
|
+
lines.push(*level_lines)
|
348
|
+
end
|
349
|
+
lines.join("\n")
|
350
|
+
end
|
351
|
+
|
352
|
+
def self.assert_io_exception(test, expected_exception_class, exception_label, e)
|
353
|
+
test.assert_kind_of(expected_exception_class, e)
|
354
|
+
lines = e.message.split("\n")
|
355
|
+
_ = lines.shift # Message from original exception.
|
356
|
+
label_line = lines.shift
|
357
|
+
test.assert_equal(exception_label, label_line)
|
358
|
+
end
|
359
|
+
|
360
|
+
def self.assert_inclusion_exception(test, expected_exception_class, exception_label, expected_inclusions, e)
|
361
|
+
test.assert_kind_of(expected_exception_class, e)
|
362
|
+
lines = e.message.split("\n")
|
363
|
+
label_line = lines.shift
|
364
|
+
test.assert_equal(exception_label, label_line)
|
365
|
+
backtrace_line = lines.shift
|
366
|
+
test.assert_equal(BACKTRACE_LABEL, backtrace_line)
|
367
|
+
level_line_count = 1 + Inclusion::LINE_COUNT
|
368
|
+
level_count = lines.size / level_line_count
|
369
|
+
# Backtrace levels are innermost first, opposite of inclusions.
|
370
|
+
reversed_inclusions = expected_inclusions.inclusions.reverse
|
371
|
+
(0...level_count).each do |level_index|
|
372
|
+
level_line = lines.shift
|
373
|
+
inclusion_lines = lines.shift(Inclusion::LINE_COUNT)
|
374
|
+
test.assert_equal("#{LEVEL_LABEL} #{level_index}:", level_line)
|
375
|
+
expected_inclusion = reversed_inclusions[level_index]
|
376
|
+
expected_inclusion.assert_lines(test, level_index, inclusion_lines)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def self.assert_circular_exception(test, expected_inclusions, e)
|
381
|
+
self.assert_inclusion_exception(
|
382
|
+
test,
|
383
|
+
CircularIncludeError,
|
384
|
+
CIRCULAR_EXCEPTION_LABEL,
|
385
|
+
expected_inclusions,
|
386
|
+
e
|
387
|
+
)
|
388
|
+
end
|
389
|
+
|
390
|
+
def self.assert_includee_missing_exception(test, expected_inclusions, e)
|
391
|
+
self.assert_inclusion_exception(
|
392
|
+
test,
|
393
|
+
Exception,
|
394
|
+
MISSING_INCLUDEE_EXCEPTION_LABEL,
|
395
|
+
expected_inclusions,
|
396
|
+
e
|
397
|
+
)
|
398
|
+
end
|
399
|
+
|
400
|
+
def self.assert_template_exception(test, e)
|
401
|
+
self.assert_io_exception(
|
402
|
+
test,
|
403
|
+
Exception,
|
404
|
+
UNREADABLE_TEMPLATE_EXCEPTION_LABEL,
|
405
|
+
e
|
406
|
+
)
|
407
|
+
end
|
408
|
+
|
409
|
+
def self.assert_output_exception(test, e)
|
410
|
+
self.assert_io_exception(
|
411
|
+
test,
|
412
|
+
Exception,
|
413
|
+
UNWRITABLE_OUTPUT_EXCEPTION_LABEL,
|
414
|
+
e
|
415
|
+
)
|
241
416
|
end
|
242
417
|
|
243
418
|
end
|
244
419
|
|
245
420
|
class Inclusion
|
246
421
|
|
422
|
+
LINE_COUNT = 5
|
423
|
+
|
247
424
|
attr_accessor \
|
248
425
|
:includer_file_path,
|
249
426
|
:includer_line_number,
|
250
|
-
:
|
251
|
-
:
|
252
|
-
:
|
427
|
+
:include_description,
|
428
|
+
:absolute_includee_file_path,
|
429
|
+
:cited_includee_file_path,
|
430
|
+
:include_description
|
253
431
|
|
254
432
|
def initialize(
|
433
|
+
include_description,
|
255
434
|
includer_file_path,
|
256
435
|
includer_line_number,
|
257
|
-
|
436
|
+
cited_includee_file_path
|
258
437
|
)
|
259
|
-
|
260
|
-
File.dirname(includer_file_path),
|
261
|
-
relative_included_file_path,
|
262
|
-
)
|
438
|
+
self.include_description = include_description
|
263
439
|
self.includer_file_path = includer_file_path
|
264
440
|
self.includer_line_number = includer_line_number
|
265
|
-
self.
|
266
|
-
self.
|
267
|
-
self.
|
441
|
+
self.cited_includee_file_path = cited_includee_file_path
|
442
|
+
self.absolute_includee_file_path = absolute_includee_file_path
|
443
|
+
self.absolute_includee_file_path = File.absolute_path(File.join(
|
444
|
+
File.dirname(includer_file_path),
|
445
|
+
cited_includee_file_path,
|
446
|
+
))
|
447
|
+
end
|
448
|
+
|
449
|
+
def real_includee_file_path
|
450
|
+
# Would raise exception unless exists.
|
451
|
+
return nil unless File.exist?(absolute_includee_file_path)
|
452
|
+
Pathname.new(absolute_includee_file_path).realpath.to_s
|
453
|
+
end
|
454
|
+
|
455
|
+
def to_lines(indentation_level)
|
456
|
+
def indentation(level)
|
457
|
+
' ' * level
|
458
|
+
end
|
459
|
+
relative_inluder_file_path = MarkdownHelper.path_in_project(includer_file_path)
|
460
|
+
relative_inludee_file_path = MarkdownHelper.path_in_project(absolute_includee_file_path)
|
461
|
+
text = <<EOT
|
462
|
+
#{indentation(indentation_level)}Includer:
|
463
|
+
#{indentation(indentation_level+1)}Location: #{relative_inluder_file_path}:#{includer_line_number}
|
464
|
+
#{indentation(indentation_level+1)}Include description: #{include_description}
|
465
|
+
#{indentation(indentation_level)}Includee:
|
466
|
+
#{indentation(indentation_level+1)}File path: #{relative_inludee_file_path}
|
467
|
+
EOT
|
468
|
+
text.split("\n")
|
469
|
+
end
|
470
|
+
|
471
|
+
def assert_lines(test, level_index, actual_lines)
|
472
|
+
level_label = "Level #{level_index}:"
|
473
|
+
# Includer label.
|
474
|
+
includee_label = actual_lines.shift
|
475
|
+
test.assert_match(/^\s*Includer:$/, includee_label, level_label)
|
476
|
+
# Includer locatioin.
|
477
|
+
location = actual_lines.shift
|
478
|
+
message = "#{level_label} includer location"
|
479
|
+
test.assert_match(/^\s*Location:/, location, message)
|
480
|
+
includer_realpath = Pathname.new(includer_file_path).realpath.to_s
|
481
|
+
relative_path = MarkdownHelper.path_in_project(includer_realpath)
|
482
|
+
r = Regexp.new(Regexp.escape("#{relative_path}:#{includer_line_number}") + '$')
|
483
|
+
test.assert_match(r, location, message)
|
484
|
+
# Include description.
|
485
|
+
description = actual_lines.shift
|
486
|
+
message = "#{level_label} include description"
|
487
|
+
test.assert_match(/^\s*Include description:/, description, message)
|
488
|
+
r = Regexp.new(Regexp.escape("#{include_description}") + '$')
|
489
|
+
test.assert_match(r, description, message)
|
490
|
+
# Includee label.
|
491
|
+
includee_label = actual_lines.shift
|
492
|
+
test.assert_match(/^\s*Includee:$/, includee_label, level_label)
|
493
|
+
# Includee file path.
|
494
|
+
includee_file_path = actual_lines.shift
|
495
|
+
message = "#{level_label} includee cited file path"
|
496
|
+
test.assert_match(/^\s*File path:/, includee_file_path, message)
|
497
|
+
relative_path = MarkdownHelper.path_in_project(absolute_includee_file_path)
|
498
|
+
r = Regexp.new(Regexp.escape("#{relative_path}") + '$')
|
499
|
+
test.assert_match(r, includee_file_path, message)
|
268
500
|
end
|
269
501
|
|
270
502
|
end
|