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.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +225 -0
  4. data/Rakefile +9 -0
  5. data/app/assets/config/markdown_cms_manifest.js +1 -0
  6. data/app/assets/stylesheets/markdown_cms/application.css +15 -0
  7. data/app/controllers/markdown_record/application_controller.rb +11 -0
  8. data/app/controllers/markdown_record/content_controller.rb +19 -0
  9. data/app/controllers/markdown_record/html_controller.rb +11 -0
  10. data/app/controllers/markdown_record/json_controller.rb +11 -0
  11. data/app/helpers/markdown_record/application_helper.rb +4 -0
  12. data/app/helpers/markdown_record/controller_helpers.rb +39 -0
  13. data/app/helpers/markdown_record/view_helpers.rb +84 -0
  14. data/app/models/markdown_record/demo/dsl_command.rb +10 -0
  15. data/app/models/markdown_record/demo/section.rb +9 -0
  16. data/app/models/markdown_record/tests/child_model.rb +15 -0
  17. data/app/models/markdown_record/tests/fake_active_record_model.rb +13 -0
  18. data/app/models/markdown_record/tests/model.rb +15 -0
  19. data/app/models/markdown_record/tests/other_child_model.rb +15 -0
  20. data/config/routes.rb +13 -0
  21. data/lib/generators/markdown_record_generator.rb +44 -0
  22. data/lib/markdown_record/association.rb +131 -0
  23. data/lib/markdown_record/associations.rb +106 -0
  24. data/lib/markdown_record/base.rb +25 -0
  25. data/lib/markdown_record/cli.rb +54 -0
  26. data/lib/markdown_record/configuration.rb +66 -0
  27. data/lib/markdown_record/content_associations.rb +46 -0
  28. data/lib/markdown_record/content_dsl/attribute.rb +22 -0
  29. data/lib/markdown_record/content_dsl/directory_fragment.rb +22 -0
  30. data/lib/markdown_record/content_dsl/disable.rb +22 -0
  31. data/lib/markdown_record/content_dsl/enable.rb +22 -0
  32. data/lib/markdown_record/content_dsl/end_attribute.rb +22 -0
  33. data/lib/markdown_record/content_dsl/end_model.rb +22 -0
  34. data/lib/markdown_record/content_dsl/fragment.rb +22 -0
  35. data/lib/markdown_record/content_dsl/model.rb +23 -0
  36. data/lib/markdown_record/content_dsl/render_format.rb +22 -0
  37. data/lib/markdown_record/content_dsl/render_strategy.rb +22 -0
  38. data/lib/markdown_record/content_dsl/use_layout.rb +22 -0
  39. data/lib/markdown_record/content_dsl.rb +37 -0
  40. data/lib/markdown_record/content_fragment.rb +123 -0
  41. data/lib/markdown_record/engine.rb +13 -0
  42. data/lib/markdown_record/errors/base.rb +6 -0
  43. data/lib/markdown_record/errors/duplicate_filename_error.rb +21 -0
  44. data/lib/markdown_record/errors/duplicate_id_error.rb +11 -0
  45. data/lib/markdown_record/errors/missing_parent_error.rb +11 -0
  46. data/lib/markdown_record/file_saver.rb +39 -0
  47. data/lib/markdown_record/html_renderer.rb +194 -0
  48. data/lib/markdown_record/indexer.rb +34 -0
  49. data/lib/markdown_record/json_renderer.rb +270 -0
  50. data/lib/markdown_record/model_inflator.rb +107 -0
  51. data/lib/markdown_record/path_utilities.rb +86 -0
  52. data/lib/markdown_record/rendering.rb +57 -0
  53. data/lib/markdown_record/routes_renderer.rb +0 -0
  54. data/lib/markdown_record/validator.rb +59 -0
  55. data/lib/markdown_record/version.rb +3 -0
  56. data/lib/markdown_record.rb +28 -0
  57. data/templates/Thorfile +3 -0
  58. data/templates/base/content/example_content.md +3 -0
  59. data/templates/base/layouts/_concatenated_layout.html.erb +8 -0
  60. data/templates/base/layouts/_custom_layout.html.erb +7 -0
  61. data/templates/base/layouts/_file_layout.html.erb +8 -0
  62. data/templates/base/layouts/_global_layout.html.erb +8 -0
  63. data/templates/demo/assets/images/ruby-logo.png +0 -0
  64. data/templates/demo/content/10_custom_models_and_associations.md.erb +78 -0
  65. data/templates/demo/content/11_controller_helpers.md.erb +20 -0
  66. data/templates/demo/content/12_configuration.md.erb +26 -0
  67. data/templates/demo/content/13_sandbox/1_foo.md +12 -0
  68. data/templates/demo/content/13_sandbox/2_sandbox_nested/1_bar.md +18 -0
  69. data/templates/demo/content/1_home.md.erb +40 -0
  70. data/templates/demo/content/2_installation.md.erb +129 -0
  71. data/templates/demo/content/3_rendering_basics.md.erb +71 -0
  72. data/templates/demo/content/4_content_dsl.md.erb +104 -0
  73. data/templates/demo/content/5_routes.md.erb +43 -0
  74. data/templates/demo/content/6_model_basics.md.erb +105 -0
  75. data/templates/demo/content/7_content_frags.md.erb +78 -0
  76. data/templates/demo/content/8_erb_syntax_and_view_helpers.md.erb +88 -0
  77. data/templates/demo/content/9_layouts.md.erb +15 -0
  78. data/templates/demo/layouts/_concatenated_layout.html.erb +12 -0
  79. data/templates/demo/layouts/_custom_layout.html.erb +14 -0
  80. data/templates/demo/layouts/_file_layout.html.erb +15 -0
  81. data/templates/demo/layouts/_global_layout.html.erb +108 -0
  82. data/templates/markdown_record_initializer.rb +3 -0
  83. data/templates/render_content.thor +57 -0
  84. 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,3 @@
1
+ module MarkdownRecord
2
+ VERSION = "0.1.3"
3
+ 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
@@ -0,0 +1,3 @@
1
+ require File.expand_path("../config/environment", __FILE__)
2
+
3
+ Dir["./lib/tasks/**/*.thor"].sort.each { |f| load f }
@@ -0,0 +1,3 @@
1
+ # Example Content
2
+
3
+ This directory is where your markdown source files will live.
@@ -0,0 +1,8 @@
1
+ <style>
2
+
3
+ </style>
4
+
5
+ <div class="concatenated">
6
+ <%= raw(html) %>
7
+ <%= raw("<!--#{subdirectory}/#{filename}-->") %>
8
+ </div>
@@ -0,0 +1,7 @@
1
+ <style>
2
+
3
+ </style>
4
+ <div>
5
+ <%= raw(html) %>
6
+ <%= raw("<!--#{subdirectory}/#{filename}-->") %>
7
+ </div>
@@ -0,0 +1,8 @@
1
+ <style>
2
+
3
+ </style>
4
+
5
+ <div class="file">
6
+ <%= raw(html) %>
7
+ <%= raw("<!--#{subdirectory}/#{filename}-->") %>
8
+ </div>
@@ -0,0 +1,8 @@
1
+ <style>
2
+
3
+ </style>
4
+
5
+ <div class="container">
6
+ <%= raw(html) %>
7
+ <%= raw("<!--#{subdirectory}/#{filename}-->") %>
8
+ </div>