mint 0.8.1 → 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.
- checksums.yaml +4 -4
- data/Gemfile +1 -26
- data/README.md +117 -37
- data/bin/mint +2 -81
- data/config/templates/base/navigation.css +136 -0
- data/config/templates/base/print.css +152 -0
- data/config/templates/{reset.css → base/reset.css} +1 -1
- data/config/templates/base/style.css +117 -137
- data/config/templates/base/utilities.css +136 -0
- data/config/templates/base/variables.css +124 -0
- data/config/templates/basic/style.css +151 -0
- data/config/templates/default/layout.erb +33 -3
- data/config/templates/default/style.css +95 -164
- data/config/templates/magazine/style.css +383 -0
- data/config/templates/nord/style.css +105 -220
- data/config/templates/nord-dark/style.css +82 -263
- data/lib/mint/commandline/parse.rb +144 -0
- data/lib/mint/commandline/publish.rb +46 -0
- data/lib/mint/commandline/run.rb +30 -0
- data/lib/mint/config.rb +162 -0
- data/lib/mint/{css.rb → css_dsl.rb} +9 -9
- data/lib/mint/css_parser.rb +45 -25
- data/lib/mint/document.rb +250 -365
- data/lib/mint/document_tree.rb +163 -0
- data/lib/mint/exceptions.rb +2 -3
- data/lib/mint/helpers.rb +23 -180
- data/lib/mint/layout.rb +26 -9
- data/lib/mint/renderers/css_renderer.rb +32 -0
- data/lib/mint/renderers/erb_renderer.rb +11 -0
- data/lib/mint/renderers/markdown_renderer.rb +45 -0
- data/lib/mint/style.rb +21 -31
- data/lib/mint/template.rb +30 -0
- data/lib/mint/version.rb +1 -1
- data/lib/mint/workspace.rb +171 -0
- data/lib/mint.rb +44 -12
- data/man/mint.1 +85 -44
- data/spec/cli/README.md +2 -2
- data/spec/cli/argument_parsing_spec.rb +89 -147
- data/spec/cli/bin_integration_spec.rb +23 -243
- data/spec/cli/full_workflow_integration_spec.rb +99 -442
- data/spec/cli/original_style_integration_spec.rb +58 -0
- data/spec/cli/publish_workflow_spec.rb +72 -70
- data/spec/commandline_path_integration_spec.rb +230 -0
- data/spec/config_file_integration_spec.rb +362 -0
- data/spec/{css_spec.rb → css_dsl_spec.rb} +7 -3
- data/spec/css_parser_spec.rb +59 -1
- data/spec/document_spec.rb +37 -242
- data/spec/flattened_path_spec.rb +150 -0
- data/spec/layout_spec.rb +42 -3
- data/spec/mint_spec.rb +22 -217
- data/spec/path_handling_spec.rb +237 -0
- data/spec/run_cli_tests.rb +1 -1
- data/spec/spec_helper.rb +3 -10
- data/spec/style_spec.rb +31 -56
- data/spec/support/cli_helpers.rb +7 -10
- data/spec/support/matchers.rb +1 -1
- data/spec/template_spec.rb +31 -0
- data/spec/workspace_spec.rb +177 -0
- metadata +75 -89
- data/bin/mint-epub +0 -20
- data/config/templates/garden/layout.erb +0 -38
- data/config/templates/garden/style.css +0 -303
- data/config/templates/nord/layout.erb +0 -11
- data/config/templates/nord-dark/layout.erb +0 -11
- data/config/templates/zen/layout.erb +0 -11
- data/config/templates/zen/style.css +0 -114
- data/lib/mint/command_line.rb +0 -360
- data/lib/mint/css_template.rb +0 -37
- data/lib/mint/markdown_template.rb +0 -47
- data/lib/mint/mint.rb +0 -313
- data/lib/mint/plugin.rb +0 -136
- data/lib/mint/plugins/epub.rb +0 -293
- data/lib/mint/resource.rb +0 -101
- data/plugins/templates/epub/layouts/container.haml +0 -5
- data/plugins/templates/epub/layouts/content.haml +0 -35
- data/plugins/templates/epub/layouts/layout.haml +0 -6
- data/plugins/templates/epub/layouts/title.haml +0 -11
- data/plugins/templates/epub/layouts/toc.haml +0 -26
- data/spec/cli/configuration_management_spec.rb +0 -363
- data/spec/cli/template_management_spec.rb +0 -300
- data/spec/helpers_spec.rb +0 -249
- data/spec/plugin_spec.rb +0 -449
- data/spec/resource_spec.rb +0 -135
data/lib/mint/config.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require "toml"
|
2
|
+
|
3
|
+
module Mint
|
4
|
+
class Config
|
5
|
+
attr_accessor :files
|
6
|
+
attr_accessor :stdin_mode
|
7
|
+
attr_accessor :help
|
8
|
+
attr_accessor :verbose
|
9
|
+
attr_accessor :layout_name
|
10
|
+
attr_accessor :style_name
|
11
|
+
attr_accessor :style_mode
|
12
|
+
attr_accessor :output_file_format
|
13
|
+
attr_accessor :working_directory # This can't be set by the user
|
14
|
+
attr_accessor :destination_directory
|
15
|
+
attr_accessor :style_destination_directory
|
16
|
+
attr_accessor :preserve_structure
|
17
|
+
attr_accessor :insert_title_heading
|
18
|
+
attr_accessor :autodrop
|
19
|
+
attr_accessor :navigation
|
20
|
+
attr_accessor :navigation_depth
|
21
|
+
attr_accessor :navigation_title
|
22
|
+
|
23
|
+
DEFAULT_STDIN_MODE = false
|
24
|
+
DEFAULT_VERBOSE = false
|
25
|
+
DEFAULT_LAYOUT_NAME = "default"
|
26
|
+
DEFAULT_STYLE_NAME = "default"
|
27
|
+
DEFAULT_STYLE_MODE = :inline
|
28
|
+
DEFAULT_OUTPUT_FILE_FORMAT = '%{name}.%{ext}'
|
29
|
+
DEFAULT_WORKING_DIRECTORY = lambda { Pathname.getwd.expand_path }
|
30
|
+
DEFAULT_DESTINATION_DIRECTORY = lambda { Pathname.getwd.expand_path }
|
31
|
+
DEFAULT_PRESERVE_STRUCTURE = true
|
32
|
+
DEFAULT_INSERT_TITLE_HEADING = false
|
33
|
+
DEFAULT_AUTODROP = true
|
34
|
+
DEFAULT_NAVIGATION = false
|
35
|
+
DEFAULT_NAVIGATION_DEPTH = 3
|
36
|
+
DEFAULT_NAVIGATION_TITLE = nil
|
37
|
+
|
38
|
+
def initialize(options = {})
|
39
|
+
@stdin_mode = options[:stdin_mode] if options.key?(:stdin_mode)
|
40
|
+
@help = options[:help]
|
41
|
+
@verbose = options[:verbose] if options.key?(:verbose)
|
42
|
+
@layout_name = options[:layout_name] || options[:template_name]
|
43
|
+
@style_name = options[:style_name] || options[:template_name]
|
44
|
+
@style_mode = options[:style_mode]
|
45
|
+
@output_file_format = options[:output_file_format]
|
46
|
+
@working_directory = options[:working_directory]&.expand_path
|
47
|
+
@destination_directory = options[:destination_directory]
|
48
|
+
@style_destination_directory = options[:style_destination_directory]
|
49
|
+
@preserve_structure = options[:preserve_structure] if options.key?(:preserve_structure)
|
50
|
+
@insert_title_heading = options[:insert_title_heading] if options.key?(:insert_title_heading)
|
51
|
+
@autodrop = options[:autodrop] if options.key?(:autodrop)
|
52
|
+
@navigation = options[:navigation] if options.key?(:navigation)
|
53
|
+
@navigation_depth = options[:navigation_depth] if options.key?(:navigation_depth)
|
54
|
+
@navigation_title = options[:navigation_title]
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_h
|
58
|
+
{
|
59
|
+
stdin_mode: @stdin_mode,
|
60
|
+
help: @help,
|
61
|
+
verbose: @verbose,
|
62
|
+
layout_name: @layout_name,
|
63
|
+
style_name: @style_name,
|
64
|
+
style_mode: @style_mode,
|
65
|
+
output_file_format: @output_file_format,
|
66
|
+
working_directory: @working_directory,
|
67
|
+
destination_directory: @destination_directory,
|
68
|
+
style_destination_directory: @style_destination_directory,
|
69
|
+
preserve_structure: @preserve_structure,
|
70
|
+
insert_title_heading: @insert_title_heading,
|
71
|
+
autodrop: @autodrop,
|
72
|
+
navigation: @navigation,
|
73
|
+
navigation_depth: @navigation_depth,
|
74
|
+
navigation_title: @navigation_title
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def merge(config = Config.new)
|
79
|
+
Config.new(to_h.merge(config.to_h.reject {|_, v| v.nil? }))
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.ensure_config(config)
|
83
|
+
case config
|
84
|
+
when Config
|
85
|
+
config
|
86
|
+
when Hash
|
87
|
+
Config.new(config)
|
88
|
+
else
|
89
|
+
raise ArgumentError, "config must be a Config object or Hash"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.load_file(file)
|
94
|
+
toml_config = TOML.load_file(file)
|
95
|
+
mapped_config = map_toml_keys_to_config(toml_config) if toml_config.is_a?(Hash)
|
96
|
+
Config.new(mapped_config || {})
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.defaults
|
100
|
+
dest_dir = DEFAULT_DESTINATION_DIRECTORY.call
|
101
|
+
Config.new({
|
102
|
+
stdin_mode: DEFAULT_STDIN_MODE,
|
103
|
+
verbose: DEFAULT_VERBOSE,
|
104
|
+
layout_name: DEFAULT_LAYOUT_NAME,
|
105
|
+
style_name: DEFAULT_STYLE_NAME,
|
106
|
+
style_mode: DEFAULT_STYLE_MODE,
|
107
|
+
output_file_format: DEFAULT_OUTPUT_FILE_FORMAT,
|
108
|
+
working_directory: DEFAULT_WORKING_DIRECTORY.call,
|
109
|
+
destination_directory: dest_dir,
|
110
|
+
style_destination_directory: Pathname.new('.'),
|
111
|
+
preserve_structure: DEFAULT_PRESERVE_STRUCTURE,
|
112
|
+
insert_title_heading: DEFAULT_INSERT_TITLE_HEADING,
|
113
|
+
autodrop: DEFAULT_AUTODROP,
|
114
|
+
navigation: DEFAULT_NAVIGATION,
|
115
|
+
navigation_depth: DEFAULT_NAVIGATION_DEPTH,
|
116
|
+
navigation_title: DEFAULT_NAVIGATION_TITLE
|
117
|
+
})
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.with_defaults(overrides = {})
|
121
|
+
defaults.merge(new(overrides))
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def self.map_toml_keys_to_config(toml_config)
|
127
|
+
mapping = {
|
128
|
+
'verbose' => :verbose,
|
129
|
+
'template' => :template_name,
|
130
|
+
'layout' => :layout_name,
|
131
|
+
'style' => :style_name,
|
132
|
+
'output-file' => :output_file_format,
|
133
|
+
'destination' => :destination_directory,
|
134
|
+
'style-mode' => :style_mode,
|
135
|
+
'style-destination' => :style_destination_directory,
|
136
|
+
'preserve-structure' => :preserve_structure,
|
137
|
+
'insert-title-heading' => :insert_title_heading,
|
138
|
+
'autodrop' => :autodrop,
|
139
|
+
'navigation' => :navigation,
|
140
|
+
'navigation-depth' => :navigation_depth,
|
141
|
+
'navigation-title' => :navigation_title
|
142
|
+
}
|
143
|
+
|
144
|
+
mapped = {}
|
145
|
+
toml_config.each do |key, value|
|
146
|
+
mapped_key = mapping[key] || key.to_sym
|
147
|
+
|
148
|
+
# Handle special conversions
|
149
|
+
case mapped_key
|
150
|
+
when :destination_directory
|
151
|
+
mapped[mapped_key] = Pathname.new(value) if value
|
152
|
+
when :style_mode
|
153
|
+
mapped[mapped_key] = value.to_sym if value
|
154
|
+
else
|
155
|
+
mapped[mapped_key] = value
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
mapped
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -2,18 +2,17 @@ require "sass-embedded"
|
|
2
2
|
|
3
3
|
module Mint
|
4
4
|
module CSS
|
5
|
-
|
6
|
-
"container"
|
7
|
-
end
|
5
|
+
CONTAINER = "container"
|
8
6
|
|
9
|
-
#
|
7
|
+
# Allows for a human-readable DSL to be used to generate CSS. Translates the following:
|
10
8
|
#
|
11
9
|
# ---
|
12
10
|
# Font: Helvetica
|
13
11
|
# Margin: 1in
|
14
12
|
# Orientation: Landscape
|
13
|
+
# ---
|
15
14
|
#
|
16
|
-
# ... into something like:
|
15
|
+
# ... into something like this:
|
17
16
|
#
|
18
17
|
# #container {
|
19
18
|
# font-family: Helvetica;
|
@@ -43,13 +42,14 @@ module Mint
|
|
43
42
|
indent: "p+p { text-indent: %s }",
|
44
43
|
bullet: "li { list-style-type: %s }",
|
45
44
|
bullet_image: "li { list-style-image: url(%s) }",
|
46
|
-
after_paragraph: "margin-bottom",
|
47
|
-
before_paragraph: "margin-top"
|
45
|
+
after_paragraph: "p { margin-bottom: %s }",
|
46
|
+
before_paragraph: "p { margin-top: %s }"
|
48
47
|
}
|
49
48
|
end
|
50
49
|
|
51
50
|
def self.stylify(key, value)
|
52
|
-
|
51
|
+
symbol_key = key.to_s.downcase.gsub(' ', '_').to_sym
|
52
|
+
selector = mappings[symbol_key]
|
53
53
|
|
54
54
|
if selector.nil?
|
55
55
|
""
|
@@ -62,7 +62,7 @@ module Mint
|
|
62
62
|
|
63
63
|
def self.parse(style)
|
64
64
|
css = style.map {|k,v| stylify(k, v) }.join("\n ")
|
65
|
-
container_scope = "##{
|
65
|
+
container_scope = "##{CONTAINER}\n #{css.strip}\n"
|
66
66
|
|
67
67
|
# Suppress warnings by capturing $stderr
|
68
68
|
original_stderr = $stderr
|
data/lib/mint/css_parser.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require "pathname"
|
2
|
+
require "set"
|
2
3
|
|
3
4
|
module Mint
|
4
5
|
# Parses CSS files to extract @import statements and calculate relative paths
|
5
6
|
class CssParser
|
6
|
-
|
7
7
|
# Extracts @import statements from CSS content
|
8
8
|
#
|
9
9
|
# @param [String] css_content the CSS content to parse
|
@@ -11,58 +11,78 @@ module Mint
|
|
11
11
|
def self.extract_imports(css_content)
|
12
12
|
imports = []
|
13
13
|
|
14
|
-
#
|
14
|
+
# Remove CSS comments first to avoid matching commented @import statements
|
15
|
+
# Remove /* ... */ style comments
|
16
|
+
css_without_comments = css_content.gsub(/\/\*.*?\*\//m, '')
|
17
|
+
|
18
|
+
# Matched formats include:
|
15
19
|
# @import "file.css";
|
16
20
|
# @import 'file.css';
|
17
21
|
# @import url("file.css");
|
18
22
|
# @import url('file.css');
|
19
|
-
|
23
|
+
css_without_comments.scan(/@import\s+(?:url\()?['"]([^'"]+)['"](?:\))?;?/i) do |match|
|
20
24
|
imports << match[0]
|
21
25
|
end
|
22
26
|
|
23
27
|
imports
|
24
28
|
end
|
25
29
|
|
26
|
-
#
|
30
|
+
# Recursively resolves a CSS file and its imports
|
27
31
|
#
|
28
|
-
# @param [
|
29
|
-
# @param [
|
32
|
+
# @param [Pathname] css_file absolute path to the CSS file as Pathname
|
33
|
+
# @param [Pathname] html_dir directory of the HTML output file as Pathname
|
34
|
+
# @param [Set] visited set of already processed files to prevent circular imports
|
30
35
|
# @return [Array<String>] array of relative paths from HTML to CSS files
|
31
|
-
def self.
|
36
|
+
def self.resolve_css_file_recursive(css_file, html_dir, visited = Set.new)
|
32
37
|
css_files = []
|
33
|
-
main_css_file = Pathname.new(main_css_path)
|
34
|
-
html_file = Pathname.new(html_output_path)
|
35
38
|
|
36
|
-
#
|
37
|
-
css_files
|
39
|
+
# Prevent circular imports
|
40
|
+
return css_files if visited.include?(css_file.to_s)
|
41
|
+
visited.add(css_file.to_s)
|
38
42
|
|
39
|
-
|
40
|
-
return css_files unless main_css_file.exist? && main_css_file.extname == '.css'
|
43
|
+
return css_files unless css_file.exist? && css_file.extname == '.css'
|
41
44
|
|
42
45
|
begin
|
43
|
-
css_content = File.read(
|
46
|
+
css_content = File.read(css_file)
|
44
47
|
imports = extract_imports(css_content)
|
45
48
|
|
49
|
+
# Recursively process imported files first (they should load before the file that imports them)
|
46
50
|
imports.each do |import_path|
|
47
|
-
|
48
|
-
import_file
|
49
|
-
|
50
|
-
# Only include if it's a .css file and exists
|
51
|
-
if import_file.exist? && import_file.extname == '.css'
|
52
|
-
# Calculate path relative to HTML output
|
53
|
-
relative_import = import_file.relative_path_from(html_file.dirname).to_s
|
54
|
-
css_files << relative_import
|
55
|
-
end
|
51
|
+
import_file = (css_file.dirname + import_path).expand_path
|
52
|
+
css_files.concat(resolve_css_file_recursive(import_file, html_dir, visited))
|
56
53
|
end
|
57
54
|
|
58
|
-
|
59
|
-
|
55
|
+
# Add this file after its imports
|
56
|
+
relative_path = css_file.relative_path_from(html_dir).to_s
|
57
|
+
css_files << relative_path unless css_files.include?(relative_path)
|
58
|
+
|
59
|
+
rescue => _
|
60
|
+
# If we can't read the CSS file, skip it
|
60
61
|
# This allows the system to gracefully handle missing or unreadable files
|
61
62
|
end
|
62
63
|
|
63
64
|
css_files
|
64
65
|
end
|
65
66
|
|
67
|
+
# Resolves all CSS files (main + imports) and calculates their paths relative to HTML output
|
68
|
+
#
|
69
|
+
# @param [String] main_css_path absolute path to the main CSS file
|
70
|
+
# @param [String] html_output_path absolute path to the HTML output file
|
71
|
+
# @return [Array<String>] array of relative paths from HTML to CSS files
|
72
|
+
def self.resolve_css_files(main_css_path, html_output_path)
|
73
|
+
main_css_file = Pathname.new(main_css_path).expand_path
|
74
|
+
html_file = Pathname.new(html_output_path).expand_path
|
75
|
+
html_dir = html_file.dirname
|
76
|
+
|
77
|
+
# If the file doesn't exist or isn't CSS, return just the relative path
|
78
|
+
unless main_css_file.exist? && main_css_file.extname == '.css'
|
79
|
+
return [main_css_file.relative_path_from(html_dir).to_s]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Use recursive resolution
|
83
|
+
resolve_css_file_recursive(main_css_file, html_dir)
|
84
|
+
end
|
85
|
+
|
66
86
|
# Generates HTML link tags for CSS files
|
67
87
|
#
|
68
88
|
# @param [Array<String>] css_file_paths array of relative paths to CSS files
|