fontcustom 1.2.0 → 1.3.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/CONTRIBUTING.md +35 -15
  4. data/TODO.md +20 -0
  5. data/lib/fontcustom.rb +11 -24
  6. data/lib/fontcustom/base.rb +56 -0
  7. data/lib/fontcustom/cli.rb +13 -12
  8. data/lib/fontcustom/generator/font.rb +63 -65
  9. data/lib/fontcustom/generator/template.rb +81 -52
  10. data/lib/fontcustom/manifest.rb +42 -0
  11. data/lib/fontcustom/options.rb +93 -80
  12. data/lib/fontcustom/scripts/generate.py +116 -127
  13. data/lib/fontcustom/templates/_fontcustom-rails.scss +13 -18
  14. data/lib/fontcustom/templates/_fontcustom.scss +10 -18
  15. data/lib/fontcustom/templates/fontcustom-preview.html +17 -24
  16. data/lib/fontcustom/templates/fontcustom.css +10 -18
  17. data/lib/fontcustom/templates/fontcustom.yml +3 -3
  18. data/lib/fontcustom/utility.rb +145 -0
  19. data/lib/fontcustom/version.rb +1 -1
  20. data/lib/fontcustom/watcher.rb +1 -1
  21. data/spec/fixtures/generators/.fontcustom-manifest-corrupted.json +12 -5
  22. data/spec/fixtures/generators/.fontcustom-manifest-empty.json +0 -0
  23. data/spec/fixtures/generators/.fontcustom-manifest.json +49 -14
  24. data/spec/fixtures/generators/mixed-output/fontcustom_82a59e769bc60192484f2620570bbb59.eot +0 -0
  25. data/spec/fixtures/generators/mixed-output/fontcustom_82a59e769bc60192484f2620570bbb59.svg +56 -0
  26. data/spec/fixtures/generators/mixed-output/fontcustom_82a59e769bc60192484f2620570bbb59.ttf +0 -0
  27. data/spec/fixtures/generators/mixed-output/fontcustom_82a59e769bc60192484f2620570bbb59.woff +0 -0
  28. data/spec/fixtures/sandbox/.gitkeep +0 -0
  29. data/spec/fontcustom/base_spec.rb +58 -0
  30. data/spec/fontcustom/cli_spec.rb +15 -0
  31. data/spec/fontcustom/generator/font_spec.rb +50 -220
  32. data/spec/fontcustom/generator/template_spec.rb +30 -194
  33. data/spec/fontcustom/generator/template_spec.rb.off +215 -0
  34. data/spec/fontcustom/manifest_spec.rb +16 -0
  35. data/spec/fontcustom/options_spec.rb +206 -208
  36. data/spec/fontcustom/utility_spec.rb +163 -0
  37. data/spec/fontcustom/{watcher_spec.rb → watcher_spec.rb.off} +0 -0
  38. data/spec/spec_helper.rb +77 -19
  39. metadata +44 -54
  40. data/lib/fontcustom/templates/_fontcustom-bootstrap-ie7.scss +0 -22
  41. data/lib/fontcustom/templates/_fontcustom-bootstrap.scss +0 -63
  42. data/lib/fontcustom/templates/fontcustom-bootstrap-ie7.css +0 -22
  43. data/lib/fontcustom/templates/fontcustom-bootstrap.css +0 -63
  44. data/lib/fontcustom/util.rb +0 -62
  45. data/spec/fixtures/generators/mixed-output/fontcustom_cc5ce52f2ae4f9ce2e7ee8131bbfee1e.eot +0 -0
  46. data/spec/fixtures/generators/mixed-output/fontcustom_cc5ce52f2ae4f9ce2e7ee8131bbfee1e.svg +0 -102
  47. data/spec/fixtures/generators/mixed-output/fontcustom_cc5ce52f2ae4f9ce2e7ee8131bbfee1e.ttf +0 -0
  48. data/spec/fixtures/generators/mixed-output/fontcustom_cc5ce52f2ae4f9ce2e7ee8131bbfee1e.woff +0 -0
  49. data/spec/fontcustom/util_spec.rb +0 -82
@@ -1,95 +1,124 @@
1
1
  require "json"
2
2
  require "pathname"
3
- require "thor"
4
- require "thor/group"
5
- require "thor/actions"
6
3
 
7
4
  module Fontcustom
8
5
  module Generator
9
- class Template < Thor::Group
10
- include Util
11
- include Thor::Actions
6
+ class Template
7
+ include Utility
12
8
 
13
- # Instead of passing each option individually we're passing the entire options hash as an argument.
14
- # This is DRYier, easier to maintain.
15
- argument :opts
9
+ attr_reader :manifest, :options
16
10
 
17
- # Required for Thor::Actions#template
18
- def self.source_root
19
- File.join Fontcustom.gem_lib, "templates"
11
+ def initialize(manifest)
12
+ @manifest = get_manifest(manifest)
13
+ @options = @manifest[:options]
20
14
  end
21
15
 
22
- def get_manifest
23
- if File.exists? opts.manifest
24
- @data = JSON.parse File.read(opts.manifest), :symbolize_names => true
16
+ def generate
17
+ if ! @manifest[:fonts].empty?
18
+ delete_old_templates
19
+ set_relative_paths
20
+ create_files
25
21
  else
26
- raise Fontcustom::Error, "`#{relative_to_root(opts.manifest)}` is missing. This file is required to generate templates."
22
+ raise Fontcustom::Error, "No generated fonts were detected - aborting template generation."
27
23
  end
28
- rescue
29
- raise Fontcustom::Error, "Couldn't parse `#{relative_to_root(opts.manifest)}`. Delete it to start from scratch. Any previously generated files will need to be deleted manually."
30
24
  end
31
25
 
32
- def reset_output
33
- return if @data[:templates].empty?
34
- begin
35
- deleted = []
36
- @data[:templates].each do |file|
37
- remove_file file, :verbose => false
38
- deleted << file
39
- end
40
- ensure
41
- @data[:templates] = @data[:templates] - deleted
42
- json = JSON.pretty_generate @data
43
- overwrite_file opts.manifest, json
44
- say_changed :delete, deleted
45
- end
26
+ private
27
+
28
+ def delete_old_templates
29
+ delete_from_manifest(:templates)
46
30
  end
47
31
 
48
- def make_relative_paths
49
- name = File.basename @data[:fonts].first, File.extname(@data[:fonts].first)
50
- fonts = Pathname.new opts.output[:fonts]
51
- css = Pathname.new opts.output[:css]
52
- preview = Pathname.new opts.output[:preview]
32
+ def set_relative_paths
33
+ name = File.basename @manifest[:fonts].first, File.extname(@manifest[:fonts].first)
34
+ fonts = Pathname.new @options[:output][:fonts]
35
+ css = Pathname.new @options[:output][:css]
36
+ preview = Pathname.new @options[:output][:preview]
53
37
  @font_path = File.join fonts.relative_path_from(css).to_s, name
54
- @font_path_alt = opts.preprocessor_path.nil? ? @font_path : File.join(opts.preprocessor_path, name)
38
+ @font_path_alt = @options[:preprocessor_path].nil? ? @font_path : File.join(@options[:preprocessor_path], name)
55
39
  @font_path_preview = File.join fonts.relative_path_from(preview).to_s, name
56
40
  end
57
41
 
58
- def generate
59
- @glyphs = @data[:glyphs]
42
+ def create_files
43
+ @glyphs = @manifest[:glyphs]
60
44
  created = []
61
45
  packaged = %w|fontcustom-bootstrap-ie7.css fontcustom.css _fontcustom-bootstrap-ie7.scss _fontcustom-rails.scss
62
46
  fontcustom-bootstrap.css fontcustom-preview.html _fontcustom-bootstrap.scss _fontcustom.scss|
63
47
  css_exts = %w|.css .scss .sass .less .stylus|
64
48
  begin
65
- opts.templates.each do |source|
49
+ @options[:templates].each do |source|
66
50
  name = File.basename source
67
51
  ext = File.extname source
68
52
  target = name.dup
69
53
 
70
- if packaged.include?(name) && opts.font_name != DEFAULT_OPTIONS[:font_name]
71
- target.sub! DEFAULT_OPTIONS[:font_name], opts.font_name
54
+ if packaged.include?(name) && @options[:font_name] != DEFAULT_OPTIONS[:font_name]
55
+ target.sub! DEFAULT_OPTIONS[:font_name], @options[:font_name]
72
56
  end
73
57
 
74
- target = if opts.output.keys.include? name.to_sym
75
- File.join opts.output[name.to_sym], target
58
+ target = if @options[:output].keys.include? name.to_sym
59
+ File.join @options[:output][name.to_sym], target
76
60
  elsif css_exts.include? ext
77
- File.join opts.output[:css], target
61
+ File.join @options[:output][:css], target
78
62
  elsif name == "fontcustom-preview.html"
79
- File.join opts.output[:preview], target
63
+ File.join @options[:output][:preview], target
80
64
  else
81
- File.join opts.output[:fonts], target
65
+ File.join @options[:output][:fonts], target
82
66
  end
83
67
 
84
- template source, target, :verbose => false
68
+ template source, target, :verbose => false, :force => true
85
69
  created << target
86
70
  end
87
71
  ensure
88
72
  say_changed :create, created
89
- @data[:templates] = (@data[:templates] + created).uniq
90
- json = JSON.pretty_generate @data
91
- overwrite_file opts.manifest, json
73
+ @manifest[:templates] = (@manifest[:templates] + created).uniq
74
+ save_manifest
75
+ end
76
+ end
77
+
78
+ #
79
+ # Template Helpers
80
+ #
81
+
82
+ def font_name
83
+ @options[:font_name]
84
+ end
85
+
86
+ def font_face(style = :normal)
87
+ case style
88
+ when :rails
89
+ url = "font-url"
90
+ path = @font_path_alt
91
+ when :preview
92
+ url = "url"
93
+ path = @font_path_preview
94
+ else
95
+ url = "url"
96
+ path = @font_path
97
+ end
98
+ %Q|@font-face {
99
+ font-family: "#{font_name}";
100
+ src: #{url}("#{path}.eot");
101
+ src: #{url}("#{path}.eot?#iefix") format("embedded-opentype"),
102
+ #{url}("#{path}.woff") format("woff"),
103
+ #{url}("#{path}.ttf") format("truetype"),
104
+ #{url}("#{path}.svg##{font_name}") format("svg");
105
+ font-weight: normal;
106
+ font-style: normal;
107
+ }|
108
+ end
109
+
110
+ def glyph_selectors
111
+ output = @glyphs.map do |name, value|
112
+ @options[:css_selector].sub("{{glyph}}", name.to_s) + ":before"
113
+ end
114
+ output.join ",\n"
115
+ end
116
+
117
+ def glyphs
118
+ output = @glyphs.map do |name, value|
119
+ %Q|#{@options[:css_selector].sub('{{glyph}}', name.to_s)}:before { content: "\\#{value[:codepoint].to_s(16)}\" }|
92
120
  end
121
+ output.join "\n"
93
122
  end
94
123
  end
95
124
  end
@@ -0,0 +1,42 @@
1
+ module Fontcustom
2
+ class Manifest
3
+ include Utility
4
+
5
+ attr_accessor :manifest
6
+
7
+ def initialize(options)
8
+ @options = options
9
+ update_or_create_manifest
10
+ end
11
+
12
+ def update_or_create_manifest
13
+ if File.exists? @options[:manifest]
14
+ update_manifest
15
+ else
16
+ create_manifest
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def update_manifest
23
+ @manifest = get_manifest
24
+ if @manifest[:options] != @options
25
+ @manifest[:options] = @options
26
+ save_manifest
27
+ end
28
+ end
29
+
30
+ def create_manifest
31
+ @manifest = {
32
+ :checksum => { :current => "", :previous => "" },
33
+ :fonts => [],
34
+ :glyphs => {},
35
+ :options => @options,
36
+ :templates => []
37
+ }
38
+ json = JSON.pretty_generate @manifest
39
+ write_file @options[:manifest], json, :create
40
+ end
41
+ end
42
+ end
@@ -1,43 +1,43 @@
1
1
  require "yaml"
2
- require "thor/shell"
3
- require "thor/shell/basic"
4
- require "thor/shell/color"
5
- require "fontcustom/util"
6
2
 
7
3
  module Fontcustom
8
4
  class Options
9
- include Util
5
+ include Utility
10
6
 
11
- attr_reader :project_root, :input, :output, :config, :templates, :font_name, :css_prefix, :manifest, :preprocessor_path, :autowidth, :no_hash, :debug, :quiet, :skip_first
7
+ attr_accessor :options
12
8
 
13
- def initialize(options = {})
14
- check_fontforge
15
- options = symbolize_hash(options)
16
-
17
- # Overwrite example defaults (used in Thor's help) with real defaults, if unchanged
18
- EXAMPLE_OPTIONS.keys.each do |key|
19
- options.delete(key) if options[key] == EXAMPLE_OPTIONS[key]
20
- end
21
- @cli_options = DEFAULT_OPTIONS.dup.merge options
22
-
23
- @shell = Thor::Shell::Color.new
24
- set_options
9
+ def initialize(cli_options = {})
10
+ @cli_options = symbolize_hash(cli_options)
11
+ parse_options
25
12
  end
26
13
 
27
14
  private
28
15
 
29
- def set_options
16
+ def parse_options
17
+ overwrite_examples
30
18
  set_config_path
31
19
  load_config
32
20
  merge_options
33
- set_data_path
21
+ clean_font_name
22
+ set_manifest_path
34
23
  set_input_paths
35
24
  set_output_paths
36
25
  set_template_paths
37
26
  end
38
27
 
28
+ # We give Thor fake defaults to generate more useful help messages.
29
+ # Here, we delete any CLI options that match those examples.
30
+ # TODO There's *got* a be a cleaner way to customize Thor help messages.
31
+ def overwrite_examples
32
+ EXAMPLE_OPTIONS.keys.each do |key|
33
+ @cli_options.delete(key) if @cli_options[key] == EXAMPLE_OPTIONS[key]
34
+ end
35
+ @cli_options = DEFAULT_OPTIONS.dup.merge @cli_options
36
+ @cli_options[:project_root] ||= Dir.pwd
37
+ end
38
+
39
39
  def set_config_path
40
- @config = if @cli_options[:config]
40
+ @cli_options[:config] = if @cli_options[:config]
41
41
  path = expand_path @cli_options[:config]
42
42
 
43
43
  # :config is the path to fontcustom.yml
@@ -49,7 +49,7 @@ module Fontcustom
49
49
  File.join path, "fontcustom.yml"
50
50
 
51
51
  else
52
- raise Fontcustom::Error, "The configuration file wasn't found. Check `#{relative_to_root(path)}` and try again."
52
+ raise Fontcustom::Error, "No configuration file found at `#{relative_to_root(path)}`."
53
53
  end
54
54
  else
55
55
  # fontcustom.yml is in the project_root
@@ -68,108 +68,116 @@ module Fontcustom
68
68
 
69
69
  def load_config
70
70
  @config_options = {}
71
- if @config
72
- say_message :status, "Loading configuration file at `#{relative_to_root(@config)}`."
71
+ if @cli_options[:config]
72
+ say_message :debug, "Using settings from `#{relative_to_root(@cli_options[:config])}`." if @cli_options[:debug]
73
73
  begin
74
- config = YAML.load File.open(@config)
74
+ config = YAML.load File.open(@cli_options[:config])
75
75
  if config # empty YAML returns false
76
76
  @config_options = symbolize_hash(config)
77
77
  else
78
- say_message :status, "Configuration file was empty. Using defaults."
78
+ say_message :warn, "`#{relative_to_root(@cli_options[:config])}` was empty. Using defaults."
79
79
  end
80
80
  rescue Exception => e
81
- raise Fontcustom::Error, "The configuration file failed to load. Message: #{e.message}"
81
+ raise Fontcustom::Error, "Error parsing `#{relative_to_root(@cli_options[:config])}`:\n#{e.message}"
82
82
  end
83
- else
84
- say_message :status, "No configuration file set. Generate one with `fontcustom config` to save your settings."
85
83
  end
86
84
  end
87
85
 
88
86
  def merge_options
89
87
  @cli_options.delete_if { |key, val| val == DEFAULT_OPTIONS[key] }
88
+ @options = DEFAULT_OPTIONS.merge(@config_options).merge(@cli_options)
89
+ end
90
90
 
91
- options = DEFAULT_OPTIONS.dup
92
- options = options.merge @config_options
93
- options = options.merge symbolize_hash(@cli_options)
94
- send :remove_instance_variable, :@config_options
95
- send :remove_instance_variable, :@cli_options
96
-
97
- # :config is excluded since it's already been set
98
- keys = %w|project_root input output manifest templates font_name css_prefix preprocessor_path skip_first autowidth no_hash debug quiet|
99
- keys.each { |key| instance_variable_set("@#{key}", options[key.to_sym]) }
100
-
101
- @font_name = @font_name.strip.gsub(/\W/, "-")
91
+ def clean_font_name
92
+ @options[:font_name] = @options[:font_name].strip.gsub(/\W/, "-")
102
93
  end
103
94
 
104
- def set_data_path
105
- @manifest = if ! @manifest.nil?
106
- expand_path @manifest
107
- elsif @config
108
- File.join File.dirname(@config), ".fontcustom-manifest.json"
95
+ def set_manifest_path
96
+ @options[:manifest] = if ! @options[:manifest].nil?
97
+ expand_path @options[:manifest]
98
+ elsif @options[:config]
99
+ File.join File.dirname(@options[:config]), ".fontcustom-manifest.json"
109
100
  else
110
- File.join @project_root, ".fontcustom-manifest.json"
101
+ File.join @options[:project_root], ".fontcustom-manifest.json"
111
102
  end
112
103
  end
113
104
 
114
105
  def set_input_paths
115
- if @input.is_a? Hash
116
- @input = symbolize_hash(@input)
117
- if @input.has_key? :vectors
118
- @input[:vectors] = expand_path @input[:vectors]
119
- unless File.directory? @input[:vectors]
120
- raise Fontcustom::Error, "INPUT[:vectors] should be a directory. Check `#{relative_to_root(@input[:vectors])}` and try again."
106
+ if @options[:input].is_a? Hash
107
+ @options[:input] = symbolize_hash(@options[:input])
108
+ if @options[:input].has_key? :vectors
109
+ @options[:input][:vectors] = expand_path @options[:input][:vectors]
110
+ unless File.directory? @options[:input][:vectors]
111
+ raise Fontcustom::Error,
112
+ "INPUT[:vectors] (`#{relative_to_root(@options[:input][:vectors])}`) should be "\
113
+ "a directory. Check `#{relative_to_root(@options[:config])}` or your CLI options."
121
114
  end
122
115
  else
123
- raise Fontcustom::Error, "INPUT (as a hash) should contain a :vectors key."
116
+ raise Fontcustom::Error,
117
+ "INPUT should have a :vectors key. Check `#{relative_to_root(@options[:config])}` "\
118
+ "or your CLI options."
124
119
  end
125
120
 
126
- if @input.has_key? :templates
127
- @input[:templates] = expand_path @input[:templates]
128
- unless File.directory? @input[:templates]
129
- raise Fontcustom::Error, "INPUT[:templates] should be a directory. Check `#{relative_to_root(@input[:templates])}` and try again."
121
+ if @options[:input].has_key? :templates
122
+ @options[:input][:templates] = expand_path @options[:input][:templates]
123
+ unless File.directory? @options[:input][:templates]
124
+ raise Fontcustom::Error,
125
+ "INPUT[:templates] (`#{relative_to_root(@options[:input][:templates])}`) "\
126
+ "should be a directory. Check `#{relative_to_root(@options[:config])}` or "\
127
+ "your CLI options."
130
128
  end
131
129
  else
132
- @input[:templates] = @input[:vectors]
130
+ @options[:input][:templates] = @options[:input][:vectors]
133
131
  end
134
132
  else
135
- input = @input ? expand_path(@input) : @project_root
133
+ input = @options[:input] ? expand_path(@options[:input]) : @options[:project_root]
136
134
  unless File.directory? input
137
- raise Fontcustom::Error, "INPUT (as a string) should be a directory. Check `#{relative_to_root(input)}` and try again."
135
+ raise Fontcustom::Error,
136
+ "INPUT (`#{relative_to_root(input)}`) should be a directory. Check "\
137
+ "`#{relative_to_root(@options[:config])}` or your CLI options."
138
138
  end
139
- @input = { :vectors => input, :templates => input }
139
+ @options[:input] = { :vectors => input, :templates => input }
140
140
  end
141
141
 
142
- if Dir[File.join(@input[:vectors], "*.svg")].empty?
143
- raise Fontcustom::Error, "`#{relative_to_root(@input[:vectors])}` doesn't contain any SVGs."
142
+ if Dir[File.join(@options[:input][:vectors], "*.svg")].empty?
143
+ raise Fontcustom::Error, "`#{relative_to_root(@options[:input][:vectors])}` doesn't contain any SVGs."
144
144
  end
145
145
  end
146
146
 
147
147
  def set_output_paths
148
- if @output.is_a? Hash
149
- @output = symbolize_hash(@output)
150
- raise Fontcustom::Error, "OUTPUT (as a hash) should contain a :fonts key." unless @output.has_key? :fonts
148
+ if @options[:output].is_a? Hash
149
+ @options[:output] = symbolize_hash(@options[:output])
150
+ unless @options[:output].has_key? :fonts
151
+ raise Fontcustom::Error,
152
+ "OUTPUT should have a :fonts key. Check `#{relative_to_root(@options[:config])}` "\
153
+ "or your CLI options."
154
+ end
151
155
 
152
- @output.each do |key, val|
153
- @output[key] = expand_path val
156
+ @options[:output].each do |key, val|
157
+ @options[:output][key] = expand_path val
154
158
  if File.exists?(val) && ! File.directory?(val)
155
- raise Fontcustom::Error, "OUTPUT[:#{key.to_s}] should be a directory, not a file. Check `#{relative_to_root(val)}` and try again."
159
+ raise Fontcustom::Error,
160
+ "OUTPUT[:#{key.to_s}] (`#{relative_to_root(@options[:output][key])}`) should be "\
161
+ "a directory. Check `#{relative_to_root(@options[:config])}` or your CLI options."
156
162
  end
157
163
  end
158
164
 
159
- @output[:css] ||= @output[:fonts]
160
- @output[:preview] ||= @output[:fonts]
165
+ @options[:output][:css] ||= @options[:output][:fonts]
166
+ @options[:output][:preview] ||= @options[:output][:fonts]
161
167
  else
162
- if @output.is_a? String
163
- output = expand_path @output
168
+ if @options[:output].is_a? String
169
+ output = expand_path @options[:output]
164
170
  if File.exists?(output) && ! File.directory?(output)
165
- raise Fontcustom::Error, "OUTPUT should be a directory, not a file. Check `#{relative_to_root(output)}` and try again."
171
+ raise Fontcustom::Error,
172
+ "OUTPUT (`#{relative_to_root(output)}`) should be a directory. Check "\
173
+ "`#{relative_to_root(@options[:config])}` or your CLI options."
166
174
  end
167
175
  else
168
- output = File.join @project_root, @font_name
169
- say_message :status, "All generated files will be saved to `#{relative_to_root(output)}/`."
176
+ output = File.join @options[:project_root], @options[:font_name]
177
+ say_message :debug, "Generated files will be saved to `#{relative_to_root(output)}/`." if @options[:debug]
170
178
  end
171
179
 
172
- @output = {
180
+ @options[:output] = {
173
181
  :fonts => output,
174
182
  :css => output,
175
183
  :preview => output
@@ -185,7 +193,7 @@ module Fontcustom
185
193
  def set_template_paths
186
194
  template_path = File.join Fontcustom.gem_lib, "templates"
187
195
 
188
- @templates = @templates.map do |template|
196
+ @options[:templates] = @options[:templates].map do |template|
189
197
  case template
190
198
  when "preview"
191
199
  File.join template_path, "fontcustom-preview.html"
@@ -204,8 +212,13 @@ module Fontcustom
204
212
  when "bootstrap-ie7-scss"
205
213
  File.join template_path, "_fontcustom-bootstrap-ie7.scss"
206
214
  else
207
- template = File.expand_path File.join(@input[:templates], template) unless template[0] == "/"
208
- raise Fontcustom::Error, "The custom template at `#{relative_to_root(template)}` does not exist." unless File.exists? template
215
+ template = File.expand_path File.join(@options[:input][:templates], template) unless template[0] == "/"
216
+ unless File.exists? template
217
+ config = @options[:config] ? " `#{relative_to_root(@options[:config])}` or" : ""
218
+ raise Fontcustom::Error,
219
+ "Custom template `#{relative_to_root(template)}` doesn't exist. "\
220
+ "Check#{config} your CLI options."
221
+ end
209
222
  template
210
223
  end
211
224
  end