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
@@ -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
- def self.container
6
- "container"
7
- end
5
+ CONTAINER = "container"
8
6
 
9
- # Maps a "DSL" onto actual CSS. Translates this ...
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
- selector = mappings[Helpers.symbolize key]
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 = "##{container}\n #{css.strip}\n"
65
+ container_scope = "##{CONTAINER}\n #{css.strip}\n"
66
66
 
67
67
  # Suppress warnings by capturing $stderr
68
68
  original_stderr = $stderr
@@ -0,0 +1,96 @@
1
+ require "pathname"
2
+ require "set"
3
+
4
+ module Mint
5
+ # Parses CSS files to extract @import statements and calculate relative paths
6
+ class CssParser
7
+ # Extracts @import statements from CSS content
8
+ #
9
+ # @param [String] css_content the CSS content to parse
10
+ # @return [Array<String>] array of imported file paths
11
+ def self.extract_imports(css_content)
12
+ imports = []
13
+
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:
19
+ # @import "file.css";
20
+ # @import 'file.css';
21
+ # @import url("file.css");
22
+ # @import url('file.css');
23
+ css_without_comments.scan(/@import\s+(?:url\()?['"]([^'"]+)['"](?:\))?;?/i) do |match|
24
+ imports << match[0]
25
+ end
26
+
27
+ imports
28
+ end
29
+
30
+ # Recursively resolves a CSS file and its imports
31
+ #
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
35
+ # @return [Array<String>] array of relative paths from HTML to CSS files
36
+ def self.resolve_css_file_recursive(css_file, html_dir, visited = Set.new)
37
+ css_files = []
38
+
39
+ # Prevent circular imports
40
+ return css_files if visited.include?(css_file.to_s)
41
+ visited.add(css_file.to_s)
42
+
43
+ return css_files unless css_file.exist? && css_file.extname == '.css'
44
+
45
+ begin
46
+ css_content = File.read(css_file)
47
+ imports = extract_imports(css_content)
48
+
49
+ # Recursively process imported files first (they should load before the file that imports them)
50
+ imports.each do |import_path|
51
+ import_file = (css_file.dirname + import_path).expand_path
52
+ css_files.concat(resolve_css_file_recursive(import_file, html_dir, visited))
53
+ end
54
+
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
61
+ # This allows the system to gracefully handle missing or unreadable files
62
+ end
63
+
64
+ css_files
65
+ end
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
+
86
+ # Generates HTML link tags for CSS files
87
+ #
88
+ # @param [Array<String>] css_file_paths array of relative paths to CSS files
89
+ # @return [String] HTML link tags
90
+ def self.generate_link_tags(css_file_paths)
91
+ css_file_paths.map do |css_path|
92
+ %Q{<link rel="stylesheet" href="#{css_path}">}
93
+ end.join("\n ")
94
+ end
95
+ end
96
+ end