mint 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -26
  3. data/README.md +138 -95
  4. data/bin/mint +2 -81
  5. data/config/templates/base/navigation.css +136 -0
  6. data/config/templates/base/print.css +152 -0
  7. data/config/templates/{reset.css → base/reset.css} +1 -1
  8. data/config/templates/base/style.css +117 -137
  9. data/config/templates/base/utilities.css +136 -0
  10. data/config/templates/base/variables.css +124 -0
  11. data/config/templates/basic/style.css +151 -0
  12. data/config/templates/default/layout.erb +33 -3
  13. data/config/templates/default/style.css +95 -164
  14. data/config/templates/magazine/style.css +383 -0
  15. data/config/templates/nord/style.css +105 -220
  16. data/config/templates/nord-dark/style.css +82 -263
  17. data/lib/mint/commandline/parse.rb +144 -0
  18. data/lib/mint/commandline/publish.rb +46 -0
  19. data/lib/mint/commandline/run.rb +30 -0
  20. data/lib/mint/config.rb +162 -0
  21. data/lib/mint/{css.rb → css_dsl.rb} +9 -9
  22. data/lib/mint/css_parser.rb +96 -0
  23. data/lib/mint/document.rb +251 -348
  24. data/lib/mint/document_tree.rb +163 -0
  25. data/lib/mint/exceptions.rb +2 -3
  26. data/lib/mint/helpers.rb +23 -180
  27. data/lib/mint/layout.rb +26 -9
  28. data/lib/mint/renderers/css_renderer.rb +32 -0
  29. data/lib/mint/renderers/erb_renderer.rb +11 -0
  30. data/lib/mint/renderers/markdown_renderer.rb +45 -0
  31. data/lib/mint/style.rb +21 -31
  32. data/lib/mint/template.rb +30 -0
  33. data/lib/mint/version.rb +1 -1
  34. data/lib/mint/workspace.rb +171 -0
  35. data/lib/mint.rb +44 -12
  36. data/man/mint.1 +88 -47
  37. data/spec/cli/README.md +13 -13
  38. data/spec/cli/argument_parsing_spec.rb +103 -131
  39. data/spec/cli/bin_integration_spec.rb +23 -243
  40. data/spec/cli/full_workflow_integration_spec.rb +99 -442
  41. data/spec/cli/original_style_integration_spec.rb +58 -0
  42. data/spec/cli/publish_workflow_spec.rb +72 -70
  43. data/spec/commandline_path_integration_spec.rb +230 -0
  44. data/spec/config_file_integration_spec.rb +362 -0
  45. data/spec/{css_spec.rb → css_dsl_spec.rb} +7 -3
  46. data/spec/css_parser_spec.rb +207 -0
  47. data/spec/document_spec.rb +37 -242
  48. data/spec/flattened_path_spec.rb +150 -0
  49. data/spec/layout_spec.rb +42 -3
  50. data/spec/mint_spec.rb +22 -217
  51. data/spec/path_handling_spec.rb +237 -0
  52. data/spec/run_cli_tests.rb +1 -1
  53. data/spec/spec_helper.rb +3 -10
  54. data/spec/style_spec.rb +31 -56
  55. data/spec/support/cli_helpers.rb +7 -10
  56. data/spec/support/matchers.rb +1 -1
  57. data/spec/template_spec.rb +31 -0
  58. data/spec/workspace_spec.rb +177 -0
  59. metadata +78 -93
  60. data/bin/mint-epub +0 -20
  61. data/config/templates/default/css/style.css +0 -205
  62. data/config/templates/garden/layout.erb +0 -38
  63. data/config/templates/garden/style.css +0 -303
  64. data/config/templates/newspaper/layout.erb +0 -16
  65. data/config/templates/nord/layout.erb +0 -11
  66. data/config/templates/nord-dark/layout.erb +0 -11
  67. data/config/templates/protocol/layout.erb +0 -9
  68. data/config/templates/protocol/style.css +0 -25
  69. data/config/templates/zen/layout.erb +0 -11
  70. data/config/templates/zen/style.css +0 -114
  71. data/lib/mint/command_line.rb +0 -360
  72. data/lib/mint/css_template.rb +0 -37
  73. data/lib/mint/markdown_template.rb +0 -47
  74. data/lib/mint/mint.rb +0 -313
  75. data/lib/mint/plugin.rb +0 -136
  76. data/lib/mint/plugins/epub.rb +0 -293
  77. data/lib/mint/resource.rb +0 -101
  78. data/plugins/templates/epub/layouts/container.haml +0 -5
  79. data/plugins/templates/epub/layouts/content.haml +0 -35
  80. data/plugins/templates/epub/layouts/layout.haml +0 -6
  81. data/plugins/templates/epub/layouts/title.haml +0 -11
  82. data/plugins/templates/epub/layouts/toc.haml +0 -26
  83. data/spec/cli/configuration_management_spec.rb +0 -363
  84. data/spec/cli/template_management_spec.rb +0 -300
  85. data/spec/helpers_spec.rb +0 -249
  86. data/spec/plugin_spec.rb +0 -449
  87. data/spec/resource_spec.rb +0 -135
@@ -1,114 +0,0 @@
1
- :root {
2
- --screen-font-size: 16px;
3
- --screen-line-height: calc(var(--screen-font-size) * 1.4);
4
- --screen-unit: var(--screen-line-height);
5
-
6
- --print-font-size: 10pt;
7
- --print-line-height: calc(var(--print-font-size) * 1.618);
8
- --print-unit: var(--print-line-height);
9
-
10
- --zen-text: #2c2c2c;
11
- --zen-subtle: #8a8a8a;
12
- --zen-accent: #007acc;
13
- --zen-background: #fefefe;
14
- --zen-border: #e8e8e8;
15
- --link-color: var(--zen-accent);
16
- }
17
-
18
- @import "../base/style.css";
19
-
20
- body {
21
- font-family: "Times New Roman", Times, Georgia, serif;
22
- color: var(--zen-text);
23
- line-height: 1.4;
24
- background-color: var(--zen-background);
25
- font-weight: 350;
26
- letter-spacing: 0.01em;
27
- }
28
-
29
- h1, h2, h3, h4, h5, h6 {
30
- font-weight: 300;
31
- color: var(--zen-text);
32
- letter-spacing: -0.02em;
33
- }
34
-
35
- h1 {
36
- font-size: calc(var(--screen-font-size) * 2.618);
37
- margin-top: calc(var(--screen-unit) * 2);
38
- margin-bottom: calc(var(--screen-unit) * 1.5);
39
- line-height: 1.2;
40
- }
41
-
42
- h2 {
43
- font-size: calc(var(--screen-font-size) * 1.618);
44
- margin-top: calc(var(--screen-unit) * 1.618);
45
- margin-bottom: calc(var(--screen-unit) * 0.618);
46
- }
47
-
48
- p {
49
- margin: calc(var(--screen-unit) * 0.618) 0;
50
- text-align: justify;
51
- hyphens: auto;
52
- }
53
-
54
- blockquote {
55
- border-left: 2px solid var(--zen-border);
56
- padding-left: calc(var(--screen-unit) * 0.618);
57
- margin-left: 0;
58
- font-style: italic;
59
- color: var(--zen-subtle);
60
- }
61
-
62
- code {
63
- font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace;
64
- font-size: calc(var(--screen-font-size) * 0.9);
65
- background-color: rgba(0, 0, 0, 0.04);
66
- padding: 0.1em 0.3em;
67
- border-radius: 3px;
68
- font-weight: 400;
69
- }
70
-
71
- pre {
72
- background-color: rgba(0, 0, 0, 0.02);
73
- border: 1px solid var(--zen-border);
74
- border-radius: 6px;
75
- padding: calc(var(--screen-unit) * 0.618);
76
- overflow-x: auto;
77
- }
78
-
79
- pre code {
80
- background-color: transparent;
81
- padding: 0;
82
- border-radius: 0;
83
- font-size: calc(var(--screen-font-size) * 0.85);
84
- }
85
-
86
- a:link, a:visited {
87
- color: var(--zen-accent);
88
- text-decoration: none;
89
- border-bottom: 1px solid transparent;
90
- transition: border-bottom-color 0.2s ease;
91
- }
92
-
93
- a:hover {
94
- border-bottom-color: var(--zen-accent);
95
- text-decoration: none;
96
- }
97
-
98
- ul, ol {
99
- margin: calc(var(--screen-unit) * 0.5) 0;
100
- padding-left: calc(var(--screen-unit) * 1.2);
101
- }
102
-
103
- li {
104
- margin: calc(var(--screen-unit) * 0.25) 0;
105
- }
106
-
107
- #container {
108
- max-width: 120ch;
109
- margin: calc(var(--screen-unit) * 2) auto;
110
- padding: calc(var(--screen-unit) * 4) calc(var(--screen-unit) * 6);
111
- border: 1px solid #999999;
112
- background-color: var(--zen-background);
113
- box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
114
- }
@@ -1,360 +0,0 @@
1
- require "pathname"
2
- require "yaml"
3
- require "optparse"
4
- require "fileutils"
5
- require "active_support/core_ext/object/blank"
6
-
7
- module Mint
8
- module CommandLine
9
- # Parses ARGV using OptionParser
10
- #
11
- # @param [Array] argv a list of arguments to parse
12
- # @return [Hash] an object that contains parsed options, remaining arguments,
13
- # and a help message
14
- def self.parse(argv)
15
- parsed_options = {}
16
-
17
- parser = OptionParser.new do |cli|
18
- cli.banner = "Usage: mint [command] files [options]"
19
-
20
- cli.on "-t", "--template TEMPLATE", "Specify the template (layout + style)" do |t|
21
- parsed_options[:layout_or_style_or_template] = [:template, t]
22
- end
23
-
24
- cli.on "-l", "--layout LAYOUT", "Specify only the layout" do |l|
25
- parsed_options[:layout_or_style_or_template] = [:layout, l]
26
- end
27
-
28
- cli.on "-s", "--style STYLE", "Specify only the style" do |s|
29
- parsed_options[:layout_or_style_or_template] = [:style, s]
30
- end
31
-
32
- cli.on "-w", "--root ROOT", "Specify a root outside the current directory" do |r|
33
- parsed_options[:root] = r
34
- end
35
-
36
- cli.on "-o", "--output-file FORMAT", "Specify the output file format with substitutions: \#{basename}, \#{original_extension}, \#{new_extension}" do |o|
37
- parsed_options[:output_file] = o
38
- end
39
-
40
- cli.on "-d", "--destination DESTINATION", "Specify a destination directory, relative to the root" do |d|
41
- parsed_options[:destination] = d
42
- end
43
-
44
- cli.on "--inline-style", "Inline CSS into the HTML document (default)" do
45
- parsed_options[:style_mode] = :inline
46
- end
47
-
48
- cli.on "--style-destination DESTINATION", "Create stylesheet at specified directory or file path and link it" do |destination|
49
- parsed_options[:style_mode] = :external
50
- parsed_options[:style_destination] = destination
51
- end
52
-
53
- cli.on "-g", "--global", "Specify config changes on a global level" do
54
- parsed_options[:scope] = :global
55
- end
56
-
57
- cli.on "-u", "--user", "Specify config changes on a user-wide level" do
58
- parsed_options[:scope] = :user
59
- end
60
-
61
- cli.on "-l", "--local", "Specify config changes on a project-specific level" do
62
- parsed_options[:scope] = :local
63
- end
64
-
65
- cli.on "-r", "--recursive", "Recursively find all Markdown files in subdirectories" do
66
- parsed_options[:recursive] = true
67
- end
68
- end
69
-
70
- transient_argv = argv.dup
71
- parser.parse! transient_argv
72
-
73
- if parsed_options[:style_mode] == :inline && parsed_options[:style_destination]
74
- raise ArgumentError, "--inline-style and --style-destination cannot be used together"
75
- end
76
-
77
- default_options = Mint.default_options.merge(destination: Dir.getwd)
78
- { argv: transient_argv, options: default_options.merge(parsed_options), help: parser.help }
79
- end
80
-
81
- # Mint built-in commands
82
-
83
- # Prints a help banner
84
- #
85
- # @param [String, #to_s] message a message to output
86
- # @return [void]
87
- def self.help(message)
88
- puts message
89
- end
90
-
91
- # Install the named file as a template
92
- #
93
- # @param [File] file the file to install to the appropriate Mint directory
94
- # @param [String] name the template name to install as
95
- # @param [Symbol] scope the scope at which to install
96
- # @return [void]
97
- def self.install(file, name, scope = :local)
98
- if file.nil?
99
- raise "[error] No file specified for installation"
100
- end
101
-
102
- filename, ext = file.split "."
103
-
104
- template_name = name || filename
105
- type = Mint.css_formats.include?(ext) ? :style : :layout
106
- destination = Mint.template_path(template_name, scope) + "#{type}.#{ext}"
107
- FileUtils.mkdir_p File.dirname(destination)
108
-
109
- if File.exist? file
110
- FileUtils.cp file, destination
111
- else
112
- raise "[error] No such file: #{file}"
113
- end
114
- end
115
-
116
- # Uninstall the named template
117
- #
118
- # @param [String] name the name of the template to be uninstalled
119
- # @param [Symbol] scope the scope from which to uninstall
120
- # @return [void]
121
- def self.uninstall(name, scope = :local)
122
- FileUtils.rm_r Mint.template_path(name, scope)
123
- end
124
-
125
- # List the installed templates
126
- #
127
- # @param [String] filter optional filter pattern
128
- # @param [Symbol] scope the scope to list templates from
129
- # @return [void]
130
- def self.templates(filter = "", scope = :local)
131
- filter = filter.to_s # Convert nil to empty string
132
- Mint.templates(scope).
133
- grep(Regexp.new(filter)).
134
- sort.
135
- each do |template|
136
- puts "#{File.basename template} [#{template}]"
137
- end
138
- end
139
-
140
- # Processes the output file format string with substitutions
141
- #
142
- # @param [String] format_string the format string with #{} substitutions
143
- # @param [String] input_file the original input file path
144
- # @return [String] the processed output file name
145
- def self.process_output_format(format_string, input_file)
146
- basename = File.basename(input_file, ".*")
147
- original_extension = File.extname(input_file)[1..-1] || ""
148
-
149
- # TODO: Remove hardcoded new_extension
150
- new_extension = "html"
151
-
152
- format_string.
153
- gsub('#{basename}', basename).
154
- gsub('#{original_extension}', original_extension).
155
- gsub('#{new_extension}', new_extension)
156
- end
157
-
158
- # Creates a new template directory and file at the specified scope
159
- #
160
- # @param [String] name the name of the template to create
161
- # @param [Symbol] type the type of template (:layout or :style)
162
- # @param [Symbol] scope the scope at which to create the template
163
- # @return [String] the path to the created template file
164
- def self.create_template(name, type, scope)
165
- content, ext =
166
- case type
167
- when :layout
168
- [default_layout_content, "erb"]
169
- when :style
170
- [default_style_content, "css"]
171
- else
172
- abort "Invalid template type: #{type}"
173
- end
174
-
175
- template_dir = Mint.template_path(name, scope)
176
- file_path = "#{template_dir}/#{type}.#{ext}"
177
- FileUtils.mkdir_p template_dir
178
- File.write(file_path, content)
179
- file_path
180
- end
181
-
182
- # @return [String] default content for layout templates
183
- def self.default_layout_content
184
- <<~LAYOUT_TEMPLATE
185
- <!DOCTYPE html>
186
- <html>
187
- <head>
188
- <meta charset="utf-8">
189
- <title>Document</title>
190
- <% if style %>
191
- <link rel="stylesheet" href="<%= style %>">
192
- <% end %>
193
- </head>
194
- <body>
195
- <%= content %>
196
- </body>
197
- </html>
198
- LAYOUT_TEMPLATE
199
- end
200
-
201
- # @return [String] default content for style templates
202
- def self.default_style_content
203
- <<~STYLE_TEMPLATE
204
- body {
205
- font-family: -apple-system, 'Segoe UI', Roboto, sans-serif;
206
- line-height: 1.25;
207
- max-width: 960px;
208
- margin: 0 auto;
209
- padding: 2rem;
210
- color: #333;
211
- }
212
-
213
- h1, h2, h3, h4, h5, h6 {
214
- color: #2c3e50;
215
- }
216
-
217
- a {
218
- color: #3498db;
219
- text-decoration: none;
220
- }
221
-
222
- a:hover {
223
- text-decoration: underline;
224
- }
225
-
226
- code {
227
- background-color: #f8f9fa;
228
- padding: 0.2em 0.4em;
229
- border-radius: 3px;
230
- font-family: 'Monaco', 'Ubuntu Mono', monospace;
231
- }
232
- STYLE_TEMPLATE
233
- end
234
-
235
- # Retrieve named template file (probably a built-in or installed
236
- # template) and shell out that file to the user's favorite editor.
237
- #
238
- # @param [String] name the name of a template to edit
239
- # @param [Symbol] type either :layout or :style
240
- # @param [Symbol] scope the scope at which to look for/create the template
241
- # @return [void]
242
- def self.edit(name, type, scope)
243
- abort "[error] No template specified" if name.nil? || name.empty?
244
-
245
- begin
246
- file = case type
247
- when :layout
248
- Mint.lookup_layout(name)
249
- when :style
250
- Mint.lookup_style(name)
251
- else
252
- abort "[error] Invalid template type: #{type}. Use :layout or :style"
253
- end
254
- rescue Mint::TemplateNotFoundException
255
- print "Template '#{name}' does not exist. Create it? [y/N]: "
256
- response = STDIN.gets.chomp.downcase
257
-
258
- if response == 'y' || response == 'yes'
259
- file = create_template(name, type, scope)
260
- puts "Created template: #{file}"
261
- else
262
- abort "Template creation cancelled."
263
- end
264
- end
265
-
266
- editor = ENV["EDITOR"] || "vi"
267
- system "#{editor} #{file}"
268
- end
269
-
270
- # Updates configuration options persistently in the appropriate scope,
271
- # which defaults to local.
272
- #
273
- # @param [Hash] opts a structured set of options to set on Mint at the specified
274
- # scope
275
- # @param [Symbol] scope the scope at which to apply the set of options
276
- # @return [void]
277
- def self.configure(opts, scope=:local)
278
- config_directory = Mint.path_for_scope(scope)
279
- FileUtils.mkdir_p config_directory
280
- Helpers.update_yaml! "#{config_directory}/#{Mint::CONFIG_FILE}", opts
281
- end
282
-
283
- # Tries to set a config option (at the specified scope) per
284
- # the user's command.
285
- #
286
- # @param key the key to set
287
- # @param value the value to set key to
288
- # @param scope the scope at which to set the configuration
289
- # @return [void]
290
- def self.set(key, value, scope = :local)
291
- configure({ key => value }, scope)
292
- end
293
-
294
- # Displays the sum of all active configurations, where local
295
- # configurations override global ones.
296
- #
297
- # @return [void]
298
- def self.config
299
- puts YAML.dump(Mint.configuration)
300
- end
301
-
302
- # Recursively discovers Markdown files in the given directories
303
- #
304
- # @param [Array] directories the directories to search
305
- # @return [Array] an array of markdown file paths
306
- def self.discover_files_recursively(directories)
307
- markdown_files = []
308
- directories.each do |dir|
309
- if File.file?(dir)
310
- markdown_files << dir if dir =~ /\.(#{Mint::MARKDOWN_EXTENSIONS.join('|')})$/i
311
- elsif File.directory?(dir)
312
- Dir.glob("#{dir}/**/*.{#{Mint::MARKDOWN_EXTENSIONS.join(',')}}", File::FNM_CASEFOLD).each do |file|
313
- markdown_files << file
314
- end
315
- end
316
- end
317
- markdown_files.sort
318
- end
319
-
320
- # Renders and writes to file all resources described by a document.
321
- # Specifically: it publishes a document, using the document's accessors
322
- # to determine file placement and naming, and then renders its style.
323
- # This method will overwrite any existing content in a document's destination
324
- # files. The `render_style` option provides an easy way to stop Mint from
325
- # rendering a style, even if the document's style is not nil.
326
- #
327
- # @param [Array, #each] files a group of filenames
328
- # @param [Hash, #[]] commandline_options a structured set of configuration options
329
- # that will guide Mint.publish!
330
- # @return [void]
331
- def self.publish!(files, commandline_options={})
332
- # TODO: Establish commandline defaults in one place
333
- # TODO: Use `commandline_options` everywhere instead of `options` and `doc_options`
334
- options = { root: Dir.getwd }.merge(Mint.configuration_with commandline_options)
335
-
336
- if commandline_options[:recursive]
337
- files = discover_files_recursively(files.empty? ? ["."] : files)
338
- end
339
-
340
- files.each_with_index do |file, idx|
341
- # Pass all files list when processing multiple files (for navigation in templates like garden)
342
- all_files = files.size > 1 ? files : nil
343
-
344
- Document.new(file,
345
- root: options[:root],
346
- destination: options[:destination],
347
- context: options[:context],
348
- name: options[:name],
349
- style_mode: options[:style_mode],
350
- style_destination: options[:style_destination],
351
- layout: options[:layout],
352
- style: options[:style],
353
- template: options[:template],
354
- layout_or_style_or_template: options[:layout_or_style_or_template],
355
- all_files: all_files
356
- ).publish!(:render_style => (idx == 0))
357
- end
358
- end
359
- end
360
- end
@@ -1,37 +0,0 @@
1
- require 'tilt/template'
2
-
3
- module Mint
4
- class CSSTemplate < Tilt::Template
5
- self.default_mime_type = 'text/css'
6
-
7
- def prepare
8
- @data = data
9
- end
10
-
11
- def evaluate(scope, locals, &block)
12
- process_imports(@data, File.dirname(file))
13
- end
14
-
15
- def process_imports(css_content, base_dir)
16
- css_content.gsub(/@import\s+["']([^"']+)["'];?/) do |match|
17
- import_path = $1
18
-
19
- # If we find a relative path, resolve it
20
- if import_path.start_with?('../', './')
21
- full_path = File.expand_path(import_path, base_dir)
22
- else
23
- full_path = File.join(base_dir, import_path)
24
- end
25
-
26
- full_path += '.css' unless full_path.end_with?('.css')
27
-
28
- if File.exist?(full_path)
29
- imported_content = File.read(full_path)
30
- process_imports(imported_content, File.dirname(full_path))
31
- else
32
- match
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,47 +0,0 @@
1
- require 'tilt/template'
2
- require 'redcarpet'
3
-
4
- module Mint
5
- class MarkdownTemplate < Tilt::Template
6
- self.default_mime_type = 'text/html'
7
-
8
- def prepare
9
- @options = options.dup
10
-
11
- renderer_options = {
12
- filter_html: false,
13
- no_images: false,
14
- no_links: false,
15
- no_styles: false,
16
- escape_html: false,
17
- safe_links_only: false,
18
- with_toc_data: false,
19
- hard_wrap: false,
20
- prettify: false
21
- }.merge(@options)
22
-
23
- markdown_options = {
24
- tables: true,
25
- autolink: true,
26
- no_intra_emphasis: true,
27
- fenced_code_blocks: true,
28
- lax_html_blocks: false,
29
- strikethrough: true,
30
- superscript: false,
31
- footnotes: false,
32
- highlight: false,
33
- quote: false,
34
- disable_indented_code_blocks: false,
35
- space_after_headers: false,
36
- underline: false
37
- }
38
-
39
- @renderer = @options.delete(:renderer) || Redcarpet::Render::HTML.new(renderer_options)
40
- @markdown = Redcarpet::Markdown.new(@renderer, markdown_options)
41
- end
42
-
43
- def evaluate(scope, locals, &block)
44
- @markdown.render(data)
45
- end
46
- end
47
- end