markdown_record 0.1.3

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