ichiban 1.0.0 → 1.0.6

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.
@@ -1,43 +1,65 @@
1
1
  module Ichiban
2
2
  class HTMLCompiler
3
3
  def compile
4
- ::File.open(@html_file.dest, 'w') do |f|
4
+ File.open(@html_file.dest, 'w') do |f|
5
5
  f << compile_to_str
6
6
  end
7
7
  Ichiban.logger.compilation(@html_file.abs, @html_file.dest)
8
8
  end
9
9
 
10
- def compile_to_str
11
- # Compile the HTML of the content page, but not the layouts (yet)
12
- ctx = Ichiban::HTMLCompiler::Context.new(:_current_path => @html_file.dest_rel_to_compiled)
13
- inner_html = Eruby.new(::File.read(@html_file.abs)).evaluate(ctx)
10
+ def compile_to_str
11
+ # @_template_path is a path relative to the html folder. It points to the current *complete*
12
+ # HTML file being rendered. I.e. if we're currently rendering a partial, @_template_path
13
+ # will *not* point to the partial file.
14
+ #
15
+ # _template_path may be overwritten later when we look at @ivars. This is good, because
16
+ # if we're in a partial, and the including template has already set _template_path, we want
17
+ # to inherit that value. (When you call partial from a template, all of the including template's
18
+ # instance variables, including @_template_path, will be put into @ivars.)
19
+ ivars_for_ctx = {:_template_path => @html_file.rel.slice('html/'.length..-1)}
20
+
21
+ if @html_file.is_a?(Ichiban::HTMLFile)
22
+ ivars_for_ctx[:_current_path] = @html_file.web_path
23
+ end
24
+ ivars_for_ctx.merge!(@ivars) if @ivars
25
+
26
+ ctx = Ichiban::HTMLCompiler::Context.new(ivars_for_ctx)
27
+
28
+ inner_html = Eruby.new(File.read(@html_file.abs)).evaluate(ctx)
14
29
 
15
30
  # Compile Markdown if necessary
16
31
  if (@html_file.abs.end_with?('.markdown') or @html_file.abs.end_with?('.md'))
17
32
  inner_html = Ichiban::Markdown.compile(inner_html) # Will look for installed Markdown gems
18
33
  end
19
34
 
20
- # Layouts
21
- wrap_in_layouts(ctx, inner_html)
35
+ # Do layouts if appropriate
36
+ if @html_file.is_a?(Ichiban::HTMLFile)
37
+ wrap_in_layouts(ctx, inner_html)
38
+ else
39
+ # It's a PartialHTMLFile
40
+ inner_html
41
+ end
22
42
  end
23
43
 
24
- # Takes an instance of Ichiban::HTMLFile
44
+ # Takes an instance of Ichiban::HTMLFile or Ichiban::PartialHTMLFile
25
45
  def initialize(html_file)
26
46
  @html_file = html_file
27
47
  end
28
48
 
49
+ attr_writer :ivars
50
+
29
51
  def wrap_in_layouts(ctx, inner_rhtml)
30
52
  ctx.layout_stack.reverse.inject(inner_rhtml) do |html, layout_name|
31
- layout_path = ::File.join(Ichiban.project_root, 'layouts', layout_name + '.html')
32
- unless ::File.exists?(layout_path)
53
+ layout_path = File.join(Ichiban.project_root, 'layouts', layout_name + '.html')
54
+ unless File.exists?(layout_path)
33
55
  raise "Layout does not exist: #{layout_path}"
34
56
  end
35
57
  eruby = Eruby.new(
36
- ::File.read(layout_path),
58
+ File.read(layout_path),
37
59
  :filename => layout_path
38
60
  )
39
61
  html = eruby.evaluate(ctx) { html }
40
- Ichiban::Dependencies.update('.layout_dependencies.json', layout_name, @html_file.abs)
62
+ Ichiban::Dependencies.update('.layout_dependencies.json', layout_name, @html_file.rel)
41
63
  html
42
64
  end
43
65
  end
@@ -50,12 +72,40 @@ module Ichiban
50
72
 
51
73
  class Context < Erubis::Context
52
74
  include Ichiban::Helpers
75
+ include Ichiban::NavHelper
53
76
  include Erubis::XmlHelper
54
77
  include ERB::Util # Give us #h
55
78
 
79
+ # An array of helper modules. Each Context instance will be extended with them on init.
80
+ # We could just include the modules in this class. But that would break reloading. Once a
81
+ # module has been included, deleting the module doesn't un-include it. So instead, we limit
82
+ # the damage to a particular instance of Context.
83
+ @user_defined_helpers = []
84
+
85
+ def self.add_user_defined_helper(mod)
86
+ unless @user_defined_helpers.include?(mod)
87
+ @user_defined_helpers << mod
88
+ end
89
+ end
90
+
91
+ def self.clear_user_defined_helpers
92
+ @user_defined_helpers = []
93
+ end
94
+
95
+ def initialize(vars)
96
+ super(vars)
97
+ self.class.user_defined_helpers.each do |mod|
98
+ extend(mod)
99
+ end
100
+ end
101
+
56
102
  def layout_stack
57
103
  @_layout_stack or ['default']
58
104
  end
105
+
106
+ def self.user_defined_helpers
107
+ @user_defined_helpers
108
+ end
59
109
  end
60
110
  end
61
111
  end
@@ -0,0 +1,53 @@
1
+ module Ichiban
2
+ class Loader
3
+ # Pass in an IchibanFile
4
+ def change(file)
5
+ if file.is_a?(Ichiban::HelperFile) or file.is_a?(Ichiban::ModelFile)
6
+ delete_all
7
+ load_all
8
+ Ichiban.logger.reload(file.abs)
9
+ end
10
+ end
11
+
12
+ # Load all models and helpers in Ichiban.project_root
13
+ def initialize
14
+ @loaded_constants = []
15
+ load_all
16
+ end
17
+
18
+ private
19
+
20
+ # Calls Object.remove_const on all tracked modules. Also clears the compiler's list of user-defined helpers.
21
+ def delete_all
22
+ @loaded_constants.each do |const_name|
23
+ Object.send(:remove_const, const_name)
24
+ end
25
+ Ichiban::HTMLCompiler::Context.clear_user_defined_helpers
26
+ end
27
+
28
+ def load_all
29
+ # Load all models
30
+ Dir.glob(File.join(Ichiban.project_root, 'models/**/*.rb')).each do |model_path|
31
+ load_file(model_path)
32
+ end
33
+
34
+ # Load all helpers, and pass them to HTMLCompiler::Context
35
+ Dir.glob(File.join(Ichiban.project_root, 'helpers/**/*.rb')).each do |helper_path|
36
+ const = load_file(helper_path)
37
+ Ichiban::HTMLCompiler::Context.add_user_defined_helper(const)
38
+ end
39
+ end
40
+
41
+ def load_file(path)
42
+ load path
43
+ const_name = File.basename(path, File.extname(path)).classify
44
+ begin
45
+ const = Object.const_get(const_name)
46
+ rescue NameError
47
+ raise "Expected #{path} to define #{const_name}"
48
+ end
49
+ @loaded_constants << const_name.to_sym
50
+ const
51
+ end
52
+ end
53
+ end
@@ -46,16 +46,50 @@ module Ichiban
46
46
  out msg
47
47
  end
48
48
 
49
+ def layout(path)
50
+ path = path.slice(Ichiban.project_root.length + 1..-1)
51
+ msg = "#{path} changed; recompiling affected pages"
52
+ if ansi?
53
+ msg = ANSI.color(msg, :magenta)
54
+ end
55
+ out msg
56
+ end
57
+
49
58
  def initialize
50
59
  @out = STDOUT
51
60
  end
52
61
 
62
+ def reload(path)
63
+ path = path.slice(Ichiban.project_root.length + 1..-1)
64
+ msg = "#{path} triggered a reload"
65
+ if ansi?
66
+ msg = ANSI.color(msg, :magenta)
67
+ end
68
+ out msg
69
+ end
70
+
53
71
  def out=(io)
54
72
  @out = io
55
73
  end
56
74
 
57
- def out(msg)
58
- @out.puts msg
75
+ # Overloaded. Pass in a string and it writes to the output stream.
76
+ # Pass in nothing and it returns the output stream.
77
+ def out(msg = nil)
78
+ if msg.nil?
79
+ @out
80
+ else
81
+ @out.puts msg
82
+ end
83
+ end
84
+
85
+ def script_run(ind_path, dep_path)
86
+ ind_path = ind_path.slice(Ichiban.project_root.length + 1..-1)
87
+ dep_path = dep_path.slice(Ichiban.project_root.length + 1..-1)
88
+ msg = "#{dep_path} running due to change in #{ind_path}"
89
+ if ansi?
90
+ msg = ANSI.color(msg, :magenta)
91
+ end
92
+ out msg
59
93
  end
60
94
 
61
95
  def warn(msg)
@@ -69,7 +103,7 @@ module Ichiban
69
103
  require 'ansi'
70
104
  @ansi = true
71
105
  rescue LoadError
72
- Ichiban.logger.out("Try `gem install ansi` for colorized output")
106
+ Ichiban.logger.out("Try `gem install ansi` for colorized output. If you're using a Gemfile, add it there too.")
73
107
  end
74
108
  end
75
109
  end
@@ -3,6 +3,10 @@ module Ichiban
3
3
  def self.compile(src)
4
4
  require_markdown
5
5
  case @strategy
6
+ when :kramdown
7
+ Kramdown::Document.new(src).to_html
8
+ when :multimarkdown
9
+ MultiMarkdown.new(src).to_html
6
10
  when :redcarpet
7
11
  @redcarpet.render(src)
8
12
  when :maruku
@@ -16,7 +20,11 @@ module Ichiban
16
20
 
17
21
  def self.require_markdown
18
22
  unless @markdown_loaded
19
- case Ichiban.try_require('redcarpet', 'maruku', 'rdiscount')
23
+ case Ichiban.try_require('kramdown', 'multimarkdown', 'redcarpet', 'maruku', 'rdiscount')
24
+ when 'kramdown'
25
+ @strategy = :kramdown
26
+ when 'multimarkdown'
27
+ @strategy = :multimarkdown
20
28
  when 'redcarpet'
21
29
  @redcarpet = Redcarpet::Markdown.new(Redcarpet::Render::XHTML.new)
22
30
  @strategy = :redcarpet
@@ -25,7 +33,8 @@ module Ichiban
25
33
  when 'rdiscount'
26
34
  @strategy = :rdiscount
27
35
  else
28
- raise "Your Ichiban project contains at least one Markdown file. To process it, you need to install either the redcarpet or maruku gem."
36
+ raise("Your Ichiban project contains at least one Markdown file. To process it, " +
37
+ "you need to gem install one of: rpeg-multimarkdown redcarpet maruku rdiscount.")
29
38
  end
30
39
  @markdown_loaded = true
31
40
  end
@@ -0,0 +1,175 @@
1
+ module Ichiban
2
+ module NavHelper
3
+
4
+ def nav(items, options = {})
5
+ Nav.new(items, options, self).to_html
6
+ end
7
+
8
+ class Nav
9
+ def initialize(items, options, context)
10
+ @items = items
11
+ @options = options
12
+ @ctx = context
13
+ end
14
+
15
+ def to_html
16
+ ul(@items, 0)
17
+ end
18
+
19
+ private
20
+
21
+ # Returns true if and only if the current path is *identical* to the passed-in path.
22
+ def current_path?(path)
23
+ @ctx.path_with_slashes(@ctx.current_path) == @ctx.path_with_slashes(path)
24
+ end
25
+
26
+ # Returns true if and only if the current path *starts with* the passed-in path.
27
+ # So sub-paths will match.
28
+ def current_path_starts_with?(path)
29
+ @ctx.path_with_slashes(@ctx.current_path).start_with?(@ctx.path_with_slashes(path))
30
+ end
31
+
32
+
33
+
34
+ #def merge_classes(current_classes, added_classes)
35
+ # current_classes = current_classes.split(/ +/)
36
+ # added_classes = added_classes.split(/ +/)
37
+ # (current_classes + added_classes).uniq.join(' ')
38
+ #end
39
+
40
+ # Recursive. Checks whether any item in the menu, or any item in any of its descendant
41
+ # menus, has a path that *starts with* the current path. E.g. if a menu or its descendants
42
+ # have a link to '/a/b/c/', and we're at '/a/b/c/d/e/f/', then this method returns true.
43
+ def menu_matches_current_path?(items)
44
+ !items.detect do |item|
45
+ if current_path_starts_with?(item[1])
46
+ # The current path matches this item, so we can stop looking.
47
+ # menu_matches_current_path? will return true.
48
+ true
49
+ elsif item[2].is_a?(Array)
50
+ # If an item has a sub-menu, then that menu must be the third element of the array.
51
+ # (The format is [text, path, sub_menu, li_options].) So we recursively search the
52
+ # descendant menu(s) of this item.
53
+ menu_matches_current_path?(items[2])
54
+ end
55
+ end.nil?
56
+ end
57
+
58
+ # Recursive
59
+ #def sub_menu_contains_current_path?(items)
60
+ # !items.detect do |item|
61
+ # if current_path?(item[1])
62
+ # true
63
+ # elsif item[2].is_a?(Array)
64
+ # sub_menu_contains_current_path?(item[2])
65
+ # elsif item[3].is_a?(Array)
66
+ # sub_menu_contains_current_path?(item[3])
67
+ # else
68
+ # false
69
+ # end
70
+ # end.nil?
71
+ #end
72
+
73
+ # Recursive
74
+ def ul(items, depth)
75
+ # If we're in the outermost menu, add any passed-in <ul> attributes
76
+ ul_options = (
77
+ depth == 0 ?
78
+ (@ctx._limit_options(@options, %w(id class)) do |key, value|
79
+ key.to_s.start_with?('data-')
80
+ end) :
81
+ {}
82
+ )
83
+
84
+ @ctx.content_tag('ul', ul_options) do
85
+ items.inject('') do |lis, item|
86
+ text = item.shift
87
+ path = item.shift
88
+
89
+ # After the text and path, there are two optional parameters: Sub-menu (an array)
90
+ # and <li> options (a hash). If both exist, they must come in that order. But either
91
+ # one can exist without the other.
92
+ #
93
+ # Initialiy, sub_menu and li_attrs are set to default values. These have a chance to
94
+ # be overwritten when we look at the third and fourth parameters.
95
+
96
+ sub_menu = nil
97
+ li_attrs = {}
98
+
99
+ third = item.shift
100
+ fourth = item.shift
101
+
102
+ case third
103
+ when Array
104
+ sub_menu = third
105
+ if fourth.is_a?(Hash)
106
+ li_attrs = fourth
107
+ end
108
+ when Hash
109
+ li_attrs.merge!(third)
110
+ end
111
+
112
+ # If the path has a leading slash, consider it absolute and prepend it with
113
+ # relative_url_root. Otherwise, it's a relative URL, so do nothing to it.
114
+ path = @ctx.normalize_path(path)
115
+
116
+ # Create the <li>, and recur for the sub-menu if applicable
117
+ lis << @ctx.content_tag('li', li_attrs) do
118
+ li_inner_html = ''
119
+
120
+ # Create the <a> or <span> tag for this item.
121
+ if current_path?(path)
122
+ li_inner_html << @ctx.content_tag('span', text, 'class' => 'selected')
123
+ else
124
+ if current_path_starts_with?(path)
125
+ a_attrs = {'class' => 'ancestor_of_selected'}
126
+ else
127
+ a_attrs = {}
128
+ end
129
+ li_inner_html << @ctx.link_to(text, path, a_attrs)
130
+ end
131
+
132
+ # This item's sub-menu should be open if and only if:
133
+ #
134
+ # 1. we are at or inside the item's path; or
135
+ # 2. we are at or inside a path included in any of this item's descendant menus.
136
+
137
+ if sub_menu
138
+ sub_menu_open = (
139
+ current_path_starts_with?(path) or
140
+ menu_matches_current_path?(sub_menu)
141
+ )
142
+ else
143
+ sub_menu_open = false
144
+ end
145
+
146
+ # If the sub-menu is open, then we recursively generate its HTML
147
+ # and append it to this <li>.
148
+ if sub_menu_open
149
+ li_inner_html << ul(sub_menu, depth + 1)
150
+ end
151
+
152
+ li_inner_html
153
+ end
154
+
155
+ #lis << @ctx.content_tag('li', li_attrs) do
156
+ # in_sub_path = (path != '/' and @options[:consider_sub_paths] and @ctx.path_with_slashes(@ctx.current_path).start_with?(@ctx.path_with_slashes(path)))
157
+ # if current_path?(path)
158
+ # li_inner_html = @ctx.content_tag('span', text, 'class' => 'selected')
159
+ # elsif (sub_menu and sub_menu_contains_current_path?(sub_menu)) or in_sub_path
160
+ # li_inner_html = @ctx.link_to(text, path, 'class' => 'ancestor_of_selected')
161
+ # else
162
+ # li_inner_html = @ctx.link_to(text, path)
163
+ # end
164
+ # if sub_menu and (current_path?(path) or sub_menu_contains_current_path?(sub_menu) or in_sub_path)
165
+ # li_inner_html << ul(sub_menu, depth + 1)
166
+ # end
167
+ # li_inner_html
168
+ #end
169
+ lis
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,16 @@
1
+ module Ichiban
2
+ class ProjectGenerator
3
+ # The path to the empty project template in the Ichiban gem directory
4
+ def empty_project_path
5
+ File.expand_path(File.join(File.dirname(__FILE__), '../../empty_project'))
6
+ end
7
+
8
+ def initialize(path)
9
+ @path = path
10
+ end
11
+
12
+ def generate
13
+ FileUtils.cp_r(empty_project_path, @path)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,91 @@
1
+ module Ichiban
2
+ def self.script_runner
3
+ @script_runner ||= Ichiban::ScriptRunner.new
4
+ end
5
+
6
+ class ScriptRunner
7
+ # Takes an absolute path. Consults the dependencies graph.
8
+ def data_file_changed(path)
9
+ # Add one to the length to remove the leading slash
10
+ rel_to_root = path.slice(Ichiban.project_root.length + 1..-1)
11
+ dep_graph = Ichiban::Dependencies.graph(self.class.dep_graph_path)
12
+ if deps = dep_graph[rel_to_root]
13
+ deps.each do |dep|
14
+ Ichiban.logger.script_run(path, File.join(Ichiban.project_root, dep))
15
+ Ichiban::Script.new(
16
+ File.join(Ichiban.project_root, dep)
17
+ ).run
18
+ end
19
+ end
20
+ end
21
+
22
+ def self.dep_graph_path
23
+ '.script_dependencies.json'
24
+ end
25
+
26
+ # Takes an absolute path
27
+ def script_file_changed(path)
28
+ Ichiban.logger.script_run(path, path)
29
+ script = Ichiban::Script.new(path).run
30
+ end
31
+ end
32
+
33
+ class Script
34
+ # Every file that the script depends on (e.g. a data file) should be declared with this method.
35
+ # This is how Ichiban knows to re-run the script when one of the files changes. Pass in a path
36
+ # relative to the project root.
37
+ #
38
+ # However, you don't need to declare dependencies for the templates the script uses. Those will\
39
+ # automatically be tracked.
40
+ def depends_on(ind_path)
41
+ if ind_path.start_with?('/')
42
+ raise(ArgumentError, 'depends_on must be passed a path relative to the project root, e.g. "data/employees.xml"')
43
+ end
44
+ # Format in dependency graph: 'data/employees.json' => 'scripts/generate_employees.rb'
45
+ Ichiban::Dependencies.update(
46
+ Ichiban::ScriptRunner.dep_graph_path,
47
+
48
+ # Path to independent file, relative to project root.
49
+ ind_path,
50
+
51
+ # Path to dependent file (i.e. this script), relative to project root.
52
+ # Add one to the length to remove the leading slash.
53
+ @path.slice(Ichiban.project_root.length + 1..-1)
54
+ )
55
+ end
56
+
57
+ # Automatically appends .html to dest_path
58
+ def generate(template_path, dest_path, ivars)
59
+ dest_path += '.html'
60
+ web_path = '/' + File.basename(dest_path, File.extname(dest_path)) + '/'
61
+ compiler = Ichiban::HTMLCompiler.new(
62
+ Ichiban::HTMLFile.new(
63
+ File.join('html', template_path)
64
+ )
65
+ )
66
+ compiler.ivars = {:_current_path => web_path}.merge(ivars)
67
+ html = compiler.compile_to_str
68
+ File.open(File.join(Ichiban.project_root, 'compiled', dest_path), 'w') do |f|
69
+ f << html
70
+ end
71
+ Ichiban.logger.compilation(
72
+ File.join(Ichiban.project_root, 'html', template_path),
73
+ File.join(Ichiban.project_root, 'compiled', dest_path)
74
+ )
75
+ end
76
+
77
+ # Takes an absolute path
78
+ def initialize(path)
79
+ @path = path
80
+ end
81
+
82
+ attr_reader :path
83
+
84
+ def run
85
+ instance_eval(
86
+ File.read(@path),
87
+ @path
88
+ )
89
+ end
90
+ end
91
+ end
@@ -6,31 +6,48 @@ module Ichiban
6
6
  }.merge(options)
7
7
  end
8
8
 
9
- def start
10
- @listener = Listen.to(
11
- ::File.join(Ichiban.project_root, 'html')#,
12
- #::File.join(Ichiban.project_root, 'assets')
13
- )
14
- .ignore(/.listen_test$/)
15
- .latency(@options[:latency])
16
- .change do |modified, added, deleted|
17
- begin
18
- (modified + added).each do |path|
19
- if file = Ichiban::File.from_abs(path)
20
- file.update
9
+ def start(blocking = true)
10
+ @loader = Ichiban::Loader.new
11
+
12
+ Ichiban.logger.out 'Starting watcher'
13
+ begin
14
+ @listener = Listen.to(
15
+ File.join(Ichiban.project_root, 'html'),
16
+ File.join(Ichiban.project_root, 'layouts'),
17
+ File.join(Ichiban.project_root, 'assets'),
18
+ File.join(Ichiban.project_root, 'models'),
19
+ File.join(Ichiban.project_root, 'helpers'),
20
+ File.join(Ichiban.project_root, 'scripts'),
21
+ File.join(Ichiban.project_root, 'data'),
22
+ File.join(Ichiban.project_root, 'webserver')
23
+ )
24
+ .ignore(/.listen_test$/)
25
+ .latency(@options[:latency])
26
+ .change do |modified, added, deleted|
27
+ (modified + added).uniq.each do |path|
28
+ if file = Ichiban::ProjectFile.from_abs(path)
29
+ @loader.change(file) # Tell the Loader that this file has changed
30
+ begin
31
+ file.update
32
+ rescue => exc
33
+ Ichiban.logger.exception(exc)
34
+ end
21
35
  end
22
- end
23
- rescue => exc
24
- Ichiban.logger.exception(exc)
36
+ end
37
+ deleted.each do |path|
38
+ Ichiban::Deleter.new.delete_dest(path)
39
+ end
25
40
  end
26
- deleted.each do |path|
27
- Ichiban::Deleter.new.delete(path)
28
- end
29
- end.start(false) # nonblocking
41
+ @listener.start(blocking)
42
+ rescue Interrupt
43
+ Ichiban.logger.out "Stopping watcher"
44
+ exit 0
45
+ end
30
46
  end
31
47
 
32
48
  def stop
33
49
  if @listener
50
+ Ichiban.logger.out "Stopping watcher"
34
51
  @listener.stop
35
52
  @listener = nil
36
53
  end
data/lib/ichiban.rb CHANGED
@@ -4,30 +4,42 @@ require 'json'
4
4
  require 'erb' # Just for the helpers
5
5
 
6
6
  # Gems
7
- require 'active_support/core_ext/class/attribute'
8
- require 'active_support/core_ext/object/blank'
7
+ #require 'active_support/core_ext/class/attribute'
8
+ #require 'active_support/core_ext/object/blank'
9
+ require 'active_support/core_ext/array/extract_options'
9
10
  require 'active_support/inflector'
10
11
  require 'sass'
11
12
  require 'listen'
12
13
  require 'erubis'
13
14
  require 'rake'
15
+ require 'bundler'
14
16
 
15
17
  # Ichiban files. Order matters!
18
+ require 'ichiban/bundle'
16
19
  require 'ichiban/config'
17
20
  require 'ichiban/logger'
18
21
  require 'ichiban/command'
22
+ require 'ichiban/project_generator'
23
+ require 'ichiban/dependencies'
24
+ require 'ichiban/loader'
19
25
  require 'ichiban/watcher'
20
26
  require 'ichiban/deleter'
21
27
  require 'ichiban/file'
22
28
  require 'ichiban/helpers'
29
+ require 'ichiban/nav_helper'
23
30
  require 'ichiban/html_compiler'
31
+ require 'ichiban/asset_compiler'
24
32
  require 'ichiban/markdown'
25
- require 'ichiban/dependencies'
26
- require 'ichiban/helpers'
33
+ require 'ichiban/scripts'
27
34
 
28
35
  module Ichiban
29
36
  # In addition to setting the variable, this loads the config file
30
37
  def self.project_root=(path)
38
+ unless @project_root == path
39
+ # If we're changing the project root, then we need to clear all dependency graphs from memory.
40
+ # This doesn't delete any files.
41
+ Ichiban::Dependencies.clear_graphs
42
+ end
31
43
  @project_root = path
32
44
  if path # It's valid to set project_root to nil, though this would likely only happen in tests
33
45
  Ichiban::Config.load_file