ichiban 1.0.0 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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