markdown_record 0.1.3
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +225 -0
- data/Rakefile +9 -0
- data/app/assets/config/markdown_cms_manifest.js +1 -0
- data/app/assets/stylesheets/markdown_cms/application.css +15 -0
- data/app/controllers/markdown_record/application_controller.rb +11 -0
- data/app/controllers/markdown_record/content_controller.rb +19 -0
- data/app/controllers/markdown_record/html_controller.rb +11 -0
- data/app/controllers/markdown_record/json_controller.rb +11 -0
- data/app/helpers/markdown_record/application_helper.rb +4 -0
- data/app/helpers/markdown_record/controller_helpers.rb +39 -0
- data/app/helpers/markdown_record/view_helpers.rb +84 -0
- data/app/models/markdown_record/demo/dsl_command.rb +10 -0
- data/app/models/markdown_record/demo/section.rb +9 -0
- data/app/models/markdown_record/tests/child_model.rb +15 -0
- data/app/models/markdown_record/tests/fake_active_record_model.rb +13 -0
- data/app/models/markdown_record/tests/model.rb +15 -0
- data/app/models/markdown_record/tests/other_child_model.rb +15 -0
- data/config/routes.rb +13 -0
- data/lib/generators/markdown_record_generator.rb +44 -0
- data/lib/markdown_record/association.rb +131 -0
- data/lib/markdown_record/associations.rb +106 -0
- data/lib/markdown_record/base.rb +25 -0
- data/lib/markdown_record/cli.rb +54 -0
- data/lib/markdown_record/configuration.rb +66 -0
- data/lib/markdown_record/content_associations.rb +46 -0
- data/lib/markdown_record/content_dsl/attribute.rb +22 -0
- data/lib/markdown_record/content_dsl/directory_fragment.rb +22 -0
- data/lib/markdown_record/content_dsl/disable.rb +22 -0
- data/lib/markdown_record/content_dsl/enable.rb +22 -0
- data/lib/markdown_record/content_dsl/end_attribute.rb +22 -0
- data/lib/markdown_record/content_dsl/end_model.rb +22 -0
- data/lib/markdown_record/content_dsl/fragment.rb +22 -0
- data/lib/markdown_record/content_dsl/model.rb +23 -0
- data/lib/markdown_record/content_dsl/render_format.rb +22 -0
- data/lib/markdown_record/content_dsl/render_strategy.rb +22 -0
- data/lib/markdown_record/content_dsl/use_layout.rb +22 -0
- data/lib/markdown_record/content_dsl.rb +37 -0
- data/lib/markdown_record/content_fragment.rb +123 -0
- data/lib/markdown_record/engine.rb +13 -0
- data/lib/markdown_record/errors/base.rb +6 -0
- data/lib/markdown_record/errors/duplicate_filename_error.rb +21 -0
- data/lib/markdown_record/errors/duplicate_id_error.rb +11 -0
- data/lib/markdown_record/errors/missing_parent_error.rb +11 -0
- data/lib/markdown_record/file_saver.rb +39 -0
- data/lib/markdown_record/html_renderer.rb +194 -0
- data/lib/markdown_record/indexer.rb +34 -0
- data/lib/markdown_record/json_renderer.rb +270 -0
- data/lib/markdown_record/model_inflator.rb +107 -0
- data/lib/markdown_record/path_utilities.rb +86 -0
- data/lib/markdown_record/rendering.rb +57 -0
- data/lib/markdown_record/routes_renderer.rb +0 -0
- data/lib/markdown_record/validator.rb +59 -0
- data/lib/markdown_record/version.rb +3 -0
- data/lib/markdown_record.rb +28 -0
- data/templates/Thorfile +3 -0
- data/templates/base/content/example_content.md +3 -0
- data/templates/base/layouts/_concatenated_layout.html.erb +8 -0
- data/templates/base/layouts/_custom_layout.html.erb +7 -0
- data/templates/base/layouts/_file_layout.html.erb +8 -0
- data/templates/base/layouts/_global_layout.html.erb +8 -0
- data/templates/demo/assets/images/ruby-logo.png +0 -0
- data/templates/demo/content/10_custom_models_and_associations.md.erb +78 -0
- data/templates/demo/content/11_controller_helpers.md.erb +20 -0
- data/templates/demo/content/12_configuration.md.erb +26 -0
- data/templates/demo/content/13_sandbox/1_foo.md +12 -0
- data/templates/demo/content/13_sandbox/2_sandbox_nested/1_bar.md +18 -0
- data/templates/demo/content/1_home.md.erb +40 -0
- data/templates/demo/content/2_installation.md.erb +129 -0
- data/templates/demo/content/3_rendering_basics.md.erb +71 -0
- data/templates/demo/content/4_content_dsl.md.erb +104 -0
- data/templates/demo/content/5_routes.md.erb +43 -0
- data/templates/demo/content/6_model_basics.md.erb +105 -0
- data/templates/demo/content/7_content_frags.md.erb +78 -0
- data/templates/demo/content/8_erb_syntax_and_view_helpers.md.erb +88 -0
- data/templates/demo/content/9_layouts.md.erb +15 -0
- data/templates/demo/layouts/_concatenated_layout.html.erb +12 -0
- data/templates/demo/layouts/_custom_layout.html.erb +14 -0
- data/templates/demo/layouts/_file_layout.html.erb +15 -0
- data/templates/demo/layouts/_global_layout.html.erb +108 -0
- data/templates/markdown_record_initializer.rb +3 -0
- data/templates/render_content.thor +57 -0
- metadata +270 -0
@@ -0,0 +1,270 @@
|
|
1
|
+
require "redcarpet"
|
2
|
+
require "redcarpet/render_strip"
|
3
|
+
|
4
|
+
module MarkdownRecord
|
5
|
+
class JsonRenderer
|
6
|
+
include ::MarkdownRecord::PathUtilities
|
7
|
+
include ::MarkdownRecord::ContentDsl
|
8
|
+
|
9
|
+
def initialize(file_saver: ::MarkdownRecord::FileSaver.new)
|
10
|
+
@indexer = MarkdownRecord::Indexer.new
|
11
|
+
@json_models = {}
|
12
|
+
@described_models = []
|
13
|
+
@file_saver = file_saver
|
14
|
+
end
|
15
|
+
|
16
|
+
def render_models_for_subdirectory(subdirectory: "", **options)
|
17
|
+
@directory_meta = {}
|
18
|
+
content = @indexer.index(subdirectory: subdirectory)
|
19
|
+
|
20
|
+
rendered_subdirectory = base_content_path.join(subdirectory)
|
21
|
+
json_hash, _ = render_models_recursively(content, rendered_subdirectory, options)
|
22
|
+
|
23
|
+
save_content_recursively(json_hash, options, Pathname.new(""))
|
24
|
+
json_hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def render_models_for_file(file_path:, **options)
|
28
|
+
file = @indexer.file(file_path)
|
29
|
+
|
30
|
+
# render json models
|
31
|
+
json = render_models(file, file_path, options)
|
32
|
+
|
33
|
+
# save json
|
34
|
+
@file_saver.save_to_file(json.to_json, "#{clean_path(file_path)}.json", options)
|
35
|
+
|
36
|
+
json
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def render_models_recursively(file_or_directory, full_path, options)
|
42
|
+
case file_or_directory.class.name;
|
43
|
+
when Hash.name # if it is a directory
|
44
|
+
directory_hash, concat_hash = *render_nested_models(file_or_directory, full_path, options)
|
45
|
+
|
46
|
+
# concatenate child hashes if :concat = true
|
47
|
+
if options[:concat]
|
48
|
+
concatenate_nested_models(full_path, directory_hash, concat_hash, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
[directory_hash, concat_hash]
|
52
|
+
when String.name # if it is a file
|
53
|
+
content = render_models(file_or_directory, full_path, options)
|
54
|
+
|
55
|
+
if options[:deep] && options[:render_content_fragment_json]
|
56
|
+
add_content_fragment_for_file(content, full_path, false, @fragment_meta)
|
57
|
+
end
|
58
|
+
|
59
|
+
content_hash = { full_path.basename.to_s => content }
|
60
|
+
result = [{}, {}]
|
61
|
+
result[0] = content_hash if options[:deep]
|
62
|
+
result[1] = content_hash if options[:concat]
|
63
|
+
result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def render_nested_models(file_or_directory, full_path, options)
|
68
|
+
directory_hash = { full_path.basename.to_s => {} } # hash representing the directory and its contents
|
69
|
+
concat_hash = { full_path.basename.to_s => {} }
|
70
|
+
# iterate through directory contents
|
71
|
+
file_or_directory.each do |child_file_or_directory_name, child_file_or_directory|
|
72
|
+
|
73
|
+
# get full path for next recursion
|
74
|
+
child_full_path = full_path.join(child_file_or_directory_name)
|
75
|
+
# get response from next recursion
|
76
|
+
child_content_hash, child_concat_hash = render_models_recursively(
|
77
|
+
child_file_or_directory,
|
78
|
+
child_full_path,
|
79
|
+
options
|
80
|
+
)
|
81
|
+
# merge response hashes
|
82
|
+
concat_hash[full_path.basename.to_s].merge!(child_concat_hash)
|
83
|
+
directory_hash[full_path.basename.to_s].merge!(child_content_hash)
|
84
|
+
end
|
85
|
+
|
86
|
+
[directory_hash, concat_hash]
|
87
|
+
end
|
88
|
+
|
89
|
+
def concatenate_nested_models(full_path, directory_hash, concat_hash, options)
|
90
|
+
concatenated_json = {}
|
91
|
+
concatenate_json_recursively(concat_hash, concatenated_json)
|
92
|
+
|
93
|
+
# add content fragments if render_content_fragment_json = true
|
94
|
+
if options[:render_content_fragment_json]
|
95
|
+
filename, subdirectory = full_path_to_parts(full_path)
|
96
|
+
directory_meta = @directory_meta[clean_path("#{subdirectory}/#{filename}")] || {}
|
97
|
+
|
98
|
+
add_content_fragment_for_file(concat_hash[full_path.basename.to_s], full_path, true, directory_meta)
|
99
|
+
add_content_fragment_for_file(concatenated_json, full_path, true, directory_meta)
|
100
|
+
end
|
101
|
+
|
102
|
+
directory_hash["#{full_path.basename.to_s}.concat"] = concatenated_json
|
103
|
+
end
|
104
|
+
|
105
|
+
def render_models(content, full_path, options)
|
106
|
+
|
107
|
+
filename, subdirectory = full_path_to_parts(full_path)
|
108
|
+
|
109
|
+
@described_models = []
|
110
|
+
@json_models = {}
|
111
|
+
@fragment_meta = {}
|
112
|
+
enabled = true
|
113
|
+
|
114
|
+
content.split(HTML_COMMENT_REGEX).each do |text|
|
115
|
+
dsl_command = HTML_COMMENT_REGEX =~ text
|
116
|
+
|
117
|
+
if dsl_command
|
118
|
+
enabled = false if disable_dsl(text)
|
119
|
+
enabled = true if enable_dsl(text)
|
120
|
+
end
|
121
|
+
|
122
|
+
if dsl_command && enabled
|
123
|
+
extract_model(text, filename, subdirectory)
|
124
|
+
extract_fragment_meta(text, subdirectory)
|
125
|
+
extract_attribute(text)
|
126
|
+
pop_attribute(text)
|
127
|
+
pop_model(text)
|
128
|
+
else
|
129
|
+
append_to_attribute(text)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
@described_models.each do |model|
|
134
|
+
finalize_attribute(model)
|
135
|
+
end
|
136
|
+
|
137
|
+
@json_models.dup
|
138
|
+
end
|
139
|
+
|
140
|
+
def add_content_fragment_for_file(json, full_path, concatenated, meta)
|
141
|
+
content_fragment = fragment_attributes_from_path(full_path).merge("meta" => meta, "concatenated" => concatenated)
|
142
|
+
|
143
|
+
json["markdown_record/content_fragment"] ||= []
|
144
|
+
json["markdown_record/content_fragment"] << content_fragment
|
145
|
+
end
|
146
|
+
|
147
|
+
def concatenate_json_recursively(directory_hash, concatenated_json)
|
148
|
+
directory_hash.each do |key, value|
|
149
|
+
case value.class.name
|
150
|
+
when Hash.name
|
151
|
+
concatenate_json_recursively(value, concatenated_json)
|
152
|
+
when Array.name
|
153
|
+
concatenated_json[key] ||= []
|
154
|
+
concatenated_json[key] += value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def save_content_recursively(content, options, rendered_subdirectory)
|
160
|
+
if content&.values&.first.is_a?(Array)
|
161
|
+
fragments = content.slice("markdown_record/content_fragment")
|
162
|
+
non_fragments = content.except("markdown_record/content_fragment")
|
163
|
+
path = clean_path(rendered_subdirectory.to_s)
|
164
|
+
@file_saver.save_to_file(non_fragments.to_json, "#{path}.json", options)
|
165
|
+
@file_saver.save_to_file(fragments.to_json, "#{path}.json", options, true)
|
166
|
+
else
|
167
|
+
content.each do |key, value|
|
168
|
+
child_path = rendered_subdirectory.join(key)
|
169
|
+
save_content_recursively(value, options, child_path)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def extract_model(html, filename, subdirectory)
|
175
|
+
model = model_dsl(html)
|
176
|
+
|
177
|
+
if model
|
178
|
+
return unless model["type"].present?
|
179
|
+
|
180
|
+
model["subdirectory"] = clean_path(subdirectory)
|
181
|
+
model["filename"] = clean_path(filename)
|
182
|
+
|
183
|
+
# reset "type" to not have a prefix, in order to ensure
|
184
|
+
# consistent results between the internal only :klass filter
|
185
|
+
# which is used as an index in a hash (and has the prefix removed
|
186
|
+
# for consistency and deterministic behavior)
|
187
|
+
# and the externally filterable :type field.
|
188
|
+
model["type"] = model["type"].delete_prefix("::").strip
|
189
|
+
|
190
|
+
@json_models[model["type"]] ||= []
|
191
|
+
@json_models[model["type"]] << model
|
192
|
+
|
193
|
+
@described_models.push(model)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def extract_fragment_meta(html, subdirectory)
|
198
|
+
meta = fragment_dsl(html)
|
199
|
+
|
200
|
+
if meta
|
201
|
+
@fragment_meta = meta
|
202
|
+
end
|
203
|
+
|
204
|
+
directory_meta = directory_fragment_dsl(html)
|
205
|
+
|
206
|
+
if directory_meta
|
207
|
+
@directory_meta[subdirectory] = directory_meta
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def extract_attribute(text)
|
212
|
+
attribute, type = attribute_dsl(text)
|
213
|
+
type ||= "md"
|
214
|
+
|
215
|
+
if @described_models.last && attribute
|
216
|
+
model = @described_models.last
|
217
|
+
model[:described_attribute_type] = type
|
218
|
+
model[:described_attribute] = attribute
|
219
|
+
model[model[:described_attribute]] = []
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def append_to_attribute(text)
|
224
|
+
return if text.match(HTML_COMMENT_REGEX)
|
225
|
+
|
226
|
+
@described_models.each do |model|
|
227
|
+
next unless model[:described_attribute].present?
|
228
|
+
|
229
|
+
model[model[:described_attribute]] ||= []
|
230
|
+
model[model[:described_attribute]] << text
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def pop_attribute(text)
|
235
|
+
if @described_models.last && end_attribute_dsl(text)
|
236
|
+
finalize_attribute(@described_models.last)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def pop_model(html)
|
241
|
+
if end_model_dsl(html)
|
242
|
+
@described_models.pop
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def finalize_attribute(model)
|
247
|
+
return if model[:described_attribute].nil?
|
248
|
+
|
249
|
+
attribute_name = model.delete(:described_attribute)
|
250
|
+
attribute = model[attribute_name].join("\n")
|
251
|
+
type = model.delete(:described_attribute_type)
|
252
|
+
|
253
|
+
case type
|
254
|
+
when "html"
|
255
|
+
renderer = Redcarpet::Render::HTML.new
|
256
|
+
attribute = Redcarpet::Markdown.new(renderer).render(attribute)
|
257
|
+
when "md"
|
258
|
+
attribute = attribute.strip
|
259
|
+
when "int"
|
260
|
+
attribute = attribute.strip.gsub("\n", "").to_i
|
261
|
+
when "float"
|
262
|
+
attribute = attribute.strip.gsub("\n", "").to_f
|
263
|
+
when "string"
|
264
|
+
attribute = Redcarpet::Markdown.new(::Redcarpet::Render::StripDown).render(attribute).strip
|
265
|
+
end
|
266
|
+
|
267
|
+
model[attribute_name] = attribute
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "markdown_record/path_utilities"
|
2
|
+
|
3
|
+
module MarkdownRecord
|
4
|
+
class ModelInflator
|
5
|
+
include ::MarkdownRecord::PathUtilities
|
6
|
+
|
7
|
+
def initialize(source_models = nil)
|
8
|
+
@indexer = ::MarkdownRecord::Indexer.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def where(filters, subdirectory = nil)
|
12
|
+
path = if subdirectory.nil?
|
13
|
+
file_path = base_rendered_path
|
14
|
+
file_path = "#{file_path}_fragments" if filters[:klass] == ::MarkdownRecord::ContentFragment
|
15
|
+
"#{file_path}.json"
|
16
|
+
else
|
17
|
+
file_path = subdirectory
|
18
|
+
file_path = "#{file_path}_fragments" if filters[:klass] == ::MarkdownRecord::ContentFragment
|
19
|
+
"#{base_rendered_path}/#{file_path}.json"
|
20
|
+
end
|
21
|
+
|
22
|
+
json = json_source(path)
|
23
|
+
json.delete(::MarkdownRecord::ContentFragment.json_klass) if filters.delete(:exclude_fragments)
|
24
|
+
json = filters[:klass].present? ? json[filters.delete(:klass).json_klass] : json.values.flatten
|
25
|
+
json ||= []
|
26
|
+
|
27
|
+
filtered_models = json.select do |model|
|
28
|
+
passes_filters?(model.with_indifferent_access, filters.dup)
|
29
|
+
end
|
30
|
+
|
31
|
+
filtered_models.map do |model|
|
32
|
+
model["type"].camelize.safe_constantize&.new(model)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def json_source(path)
|
37
|
+
if Pathname.new(path).exist?
|
38
|
+
json = JSON.parse(File.read(path))
|
39
|
+
return json
|
40
|
+
end
|
41
|
+
|
42
|
+
json = ::MarkdownRecord::JsonRenderer.new.render_models_for_subdirectory(subdirectory: "",:concat => true, :deep => true, :save => false, :render_content_fragment_json => true)
|
43
|
+
|
44
|
+
filename, subdirectory = full_path_to_parts(path)
|
45
|
+
|
46
|
+
tokens = subdirectory.gsub(base_rendered_root, "").split("/")
|
47
|
+
tokens << "#{filename}.concat"
|
48
|
+
json.dig(*tokens) || {}
|
49
|
+
end
|
50
|
+
|
51
|
+
def passes_filters?(attributes, filters)
|
52
|
+
passes = true
|
53
|
+
|
54
|
+
not_filters = filters.delete(:__not__)
|
55
|
+
or_filters = filters.delete(:__or__)
|
56
|
+
and_filters = filters.delete(:__and__)
|
57
|
+
|
58
|
+
filters.each do |key, value|
|
59
|
+
passes &&= passes_filter?(attributes, key, value)
|
60
|
+
end
|
61
|
+
|
62
|
+
not_filters&.each do |key, value|
|
63
|
+
passes &&= !passes_filter?(attributes, key, value)
|
64
|
+
end
|
65
|
+
|
66
|
+
or_temp = !or_filters&.any?
|
67
|
+
or_filters&.each do |sub_filter|
|
68
|
+
or_temp ||= passes_filters?(attributes, sub_filter.dup)
|
69
|
+
end
|
70
|
+
|
71
|
+
and_temp = true
|
72
|
+
and_filters&.each do |sub_filter|
|
73
|
+
and_temp &&= passes_filters?(attributes, sub_filter.dup)
|
74
|
+
end
|
75
|
+
|
76
|
+
passes &&= or_temp
|
77
|
+
passes &&= and_temp
|
78
|
+
passes
|
79
|
+
end
|
80
|
+
|
81
|
+
def passes_filter?(attributes, filter_key, filter_value)
|
82
|
+
case filter_value.class.name
|
83
|
+
when Array.name
|
84
|
+
filter_value.include?(attributes[filter_key])
|
85
|
+
when Symbol.name
|
86
|
+
if filter_value == :not_null
|
87
|
+
!attributes[filter_key].nil?
|
88
|
+
elsif filter_value == :null
|
89
|
+
attributes[filter_key].nil?
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
93
|
+
when Hash.name
|
94
|
+
if attributes[filter_key]&.class == ActiveSupport::HashWithIndifferentAccess
|
95
|
+
passes_filters?(attributes[filter_key], filter_value)
|
96
|
+
end
|
97
|
+
|
98
|
+
when nil.class.name
|
99
|
+
attributes[filter_key].nil?
|
100
|
+
when Regexp.name
|
101
|
+
attributes[filter_key] =~ filter_value
|
102
|
+
else
|
103
|
+
filter_value == attributes[filter_key]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module MarkdownRecord
|
4
|
+
module PathUtilities
|
5
|
+
|
6
|
+
def full_path_to_parts(full_path)
|
7
|
+
rendered_path = Pathname.new("/").join(full_path)
|
8
|
+
|
9
|
+
filename = clean_path(rendered_path.basename)
|
10
|
+
filename = remove_numeric_prefixes(filename)
|
11
|
+
|
12
|
+
subdirectory = clean_path(rendered_path.parent)
|
13
|
+
subdirectory = remove_numeric_prefixes(subdirectory)
|
14
|
+
|
15
|
+
[filename, subdirectory, full_path.to_s.split(".").last]
|
16
|
+
end
|
17
|
+
|
18
|
+
def rendered_path(full_path)
|
19
|
+
rendered_path = clean_path(full_path.to_s)
|
20
|
+
rendered_path = remove_numeric_prefixes(rendered_path)
|
21
|
+
Pathname.new(rendered_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def path_to_fragment_id(full_path)
|
25
|
+
rendered_path(full_path).to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def base_content_path
|
29
|
+
basename = ::MarkdownRecord.config.content_root.basename
|
30
|
+
|
31
|
+
# must use "/" so that `.parent` returns correctly
|
32
|
+
# for top level paths.
|
33
|
+
Pathname.new("/").join(basename)
|
34
|
+
end
|
35
|
+
|
36
|
+
def erb_locals_from_path(full_path)
|
37
|
+
filename, subdirectory = *full_path_to_parts(full_path)
|
38
|
+
|
39
|
+
frag_id = path_to_fragment_id(full_path)
|
40
|
+
|
41
|
+
fragment = ::MarkdownRecord::ContentFragment.find(frag_id)
|
42
|
+
|
43
|
+
{
|
44
|
+
filename: filename,
|
45
|
+
subdirectory: subdirectory,
|
46
|
+
frag_id: frag_id,
|
47
|
+
fragment: fragment
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def fragment_attributes_from_path(full_path)
|
52
|
+
filename, subdirectory = *full_path_to_parts(full_path)
|
53
|
+
|
54
|
+
frag_id = path_to_fragment_id(full_path)
|
55
|
+
|
56
|
+
{
|
57
|
+
id: frag_id,
|
58
|
+
type: MarkdownRecord::ContentFragment.name.underscore,
|
59
|
+
subdirectory: subdirectory,
|
60
|
+
filename: filename
|
61
|
+
}.stringify_keys
|
62
|
+
end
|
63
|
+
|
64
|
+
def clean_path(path)
|
65
|
+
path.to_s.gsub("_fragments", "").gsub(/(\.concat|\.md\.erb|\.md|\.json|\.html)/,"").delete_prefix("/")
|
66
|
+
end
|
67
|
+
|
68
|
+
def base_rendered_path
|
69
|
+
::MarkdownRecord.config.rendered_content_root.join(::MarkdownRecord.config.content_root.basename)
|
70
|
+
end
|
71
|
+
|
72
|
+
def base_rendered_root
|
73
|
+
clean_path(::MarkdownRecord.config.rendered_content_root.to_s)
|
74
|
+
end
|
75
|
+
|
76
|
+
def remove_numeric_prefixes(filename_or_id)
|
77
|
+
if MarkdownRecord.config.ignore_numeric_prefix
|
78
|
+
parts = filename_or_id.split("/")
|
79
|
+
parts = parts.map { |p| p.gsub(/^\d+_/,"")}
|
80
|
+
parts.join("/")
|
81
|
+
else
|
82
|
+
filename_or_id
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module MarkdownRecord
|
2
|
+
module Rendering
|
3
|
+
def generate_render_strategy_options(options)
|
4
|
+
|
5
|
+
strategy_options = if options[:strat].present?
|
6
|
+
::MarkdownRecord.config.render_strategy_options(options[:strat].to_sym)
|
7
|
+
else
|
8
|
+
::MarkdownRecord.config.render_strategy_options
|
9
|
+
end
|
10
|
+
|
11
|
+
strategy_options.merge!(:save => options[:save])
|
12
|
+
strategy_options[:render_content_fragment_json] = options[:frag] || ::MarkdownRecord.config.render_content_fragment_json
|
13
|
+
strategy_options
|
14
|
+
end
|
15
|
+
|
16
|
+
def report_rendered_files(lines, file_saver)
|
17
|
+
file_count = (file_saver.saved_files[:json].count + file_saver.saved_files[:html].count)
|
18
|
+
|
19
|
+
record_and_save lines, "---------------------------------------------------------------"
|
20
|
+
file_saver.saved_files[:json].each do |file_name|
|
21
|
+
record_and_save lines, "rendered: #{file_name}", report_line_color(options[:save])
|
22
|
+
end
|
23
|
+
file_saver.saved_files[:html].each do |file_name|
|
24
|
+
record_and_save lines, "rendered: #{file_name}", report_line_color(options[:save])
|
25
|
+
end
|
26
|
+
record_and_save lines, "---------------------------------------------------------------"
|
27
|
+
record_and_save lines, "#{file_count} files rendered."
|
28
|
+
record_and_save lines, "#{options[:save] ? file_count : 0} files saved."
|
29
|
+
|
30
|
+
::MarkdownRecord.config.rendered_content_root.join("rendered.txt").open('wb') do |file|
|
31
|
+
file << lines.join("\n")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def report_start(lines, strategy_options, formats)
|
36
|
+
record_and_save lines, "---------------------------------------------------------------"
|
37
|
+
record_and_save lines, "rendering #{formats} content with options #{strategy_options} ..."
|
38
|
+
end
|
39
|
+
|
40
|
+
def report_line_color(saved)
|
41
|
+
color = saved ? :green : :yellow
|
42
|
+
end
|
43
|
+
|
44
|
+
def record_and_save(lines, text, color = nil)
|
45
|
+
lines << text
|
46
|
+
say text, color
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate
|
50
|
+
MarkdownRecord::Validator.new.validate
|
51
|
+
true
|
52
|
+
rescue MarkdownRecord::Errors::Base => e
|
53
|
+
say e.message, :red
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
File without changes
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "markdown_record/errors/duplicate_filename_error"
|
2
|
+
require "markdown_record/errors/duplicate_id_error"
|
3
|
+
require "markdown_record/errors/missing_parent_error"
|
4
|
+
|
5
|
+
module MarkdownRecord
|
6
|
+
class Validator
|
7
|
+
include ::MarkdownRecord::PathUtilities
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@index = ::MarkdownRecord::Indexer.new.index
|
11
|
+
@json = ::MarkdownRecord::JsonRenderer.new.render_models_for_subdirectory(subdirectory: "",:concat => true, :deep => true, :save => false, :render_content_fragment_json => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate
|
15
|
+
validate_filenames(@index)
|
16
|
+
validate_models
|
17
|
+
validate_fragments
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_filenames(val)
|
21
|
+
if val.is_a?(Hash)
|
22
|
+
temp_keys = val.keys.map { |v| remove_numeric_prefixes(v) }
|
23
|
+
|
24
|
+
dups = temp_keys.group_by{|e| clean_path(e)}.keep_if{|_, e| e.length > 1}
|
25
|
+
if dups.any?
|
26
|
+
raise ::MarkdownRecord::Errors::DuplicateFilenameError.new(dups.keys)
|
27
|
+
else
|
28
|
+
val.each do |k, v|
|
29
|
+
validate_filenames(v)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_models
|
36
|
+
@json["#{base_content_path.basename}.concat"].each do |klass, array|
|
37
|
+
ids = array.map { |o| o["id"] }
|
38
|
+
dups = ids.group_by{|e| e}.keep_if{|_, e| e.length > 1}
|
39
|
+
if dups.any?
|
40
|
+
raise ::MarkdownRecord::Errors::DuplicateIdError.new(klass, dups.keys.first)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_fragments
|
46
|
+
frags = @json["#{base_content_path.basename}.concat"]["markdown_record/content_fragment"]
|
47
|
+
frags.each do |frag|
|
48
|
+
parent_id = frag.dig("meta", "parent_id")
|
49
|
+
if parent_id
|
50
|
+
unless frags.any? { |f| f["id"] == parent_id}
|
51
|
+
raise ::MarkdownRecord::Errors::MissingParentError.new(parent_id)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "markdown_record/version"
|
2
|
+
require "markdown_record/engine"
|
3
|
+
require "markdown_record/configuration"
|
4
|
+
require "markdown_record/path_utilities"
|
5
|
+
require "markdown_record/validator"
|
6
|
+
require "markdown_record/rendering"
|
7
|
+
require "markdown_record/cli"
|
8
|
+
require "markdown_record/content_dsl"
|
9
|
+
require "markdown_record/indexer"
|
10
|
+
require "markdown_record/file_saver"
|
11
|
+
require "markdown_record/html_renderer"
|
12
|
+
require "markdown_record/json_renderer"
|
13
|
+
require "markdown_record/model_inflator"
|
14
|
+
require "markdown_record/association"
|
15
|
+
require "markdown_record/associations"
|
16
|
+
require "markdown_record/content_associations"
|
17
|
+
require "markdown_record/base"
|
18
|
+
require "markdown_record/content_fragment"
|
19
|
+
|
20
|
+
module MarkdownRecord
|
21
|
+
def self.config
|
22
|
+
Configuration.instance
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.configure
|
26
|
+
yield(Configuration.instance)
|
27
|
+
end
|
28
|
+
end
|
data/templates/Thorfile
ADDED
Binary file
|