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