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.
- data/bin/ichiban +9 -0
- data/empty_project/Capfile +19 -0
- data/empty_project/assets/css/screen.scss +1 -0
- data/empty_project/assets/js/interaction.js +1 -0
- data/empty_project/assets/misc/readme.txt +6 -0
- data/empty_project/compiled/css/screen.css +1 -0
- data/empty_project/compiled/index.html +12 -0
- data/empty_project/compiled/js/interaction.js +1 -0
- data/empty_project/config.rb +3 -0
- data/empty_project/data/readme.txt +2 -0
- data/empty_project/helpers/readme.txt +10 -0
- data/empty_project/html/index.html +3 -0
- data/empty_project/layouts/default.html +12 -0
- data/empty_project/models/readme.txt +3 -0
- data/empty_project/scripts/readme.txt +6 -0
- data/empty_project/webserver/htaccess.txt +24 -0
- data/lib/ichiban/asset_compiler.rb +21 -0
- data/lib/ichiban/bundle.rb +7 -0
- data/lib/ichiban/command.rb +13 -3
- data/lib/ichiban/config.rb +2 -2
- data/lib/ichiban/deleter.rb +4 -7
- data/lib/ichiban/dependencies.rb +28 -7
- data/lib/ichiban/file.rb +122 -17
- data/lib/ichiban/helpers.rb +16 -47
- data/lib/ichiban/html_compiler.rb +62 -12
- data/lib/ichiban/loader.rb +53 -0
- data/lib/ichiban/logger.rb +37 -3
- data/lib/ichiban/markdown.rb +11 -2
- data/lib/ichiban/nav_helper.rb +175 -0
- data/lib/ichiban/project_generator.rb +16 -0
- data/lib/ichiban/scripts.rb +91 -0
- data/lib/ichiban/watcher.rb +36 -19
- data/lib/ichiban.rb +16 -4
- metadata +54 -4
@@ -1,43 +1,65 @@
|
|
1
1
|
module Ichiban
|
2
2
|
class HTMLCompiler
|
3
3
|
def compile
|
4
|
-
|
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
|
-
#
|
12
|
-
|
13
|
-
|
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
|
-
#
|
21
|
-
|
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 =
|
32
|
-
unless
|
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
|
-
|
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.
|
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
|
data/lib/ichiban/logger.rb
CHANGED
@@ -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
|
-
|
58
|
-
|
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
|
data/lib/ichiban/markdown.rb
CHANGED
@@ -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
|
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
|
data/lib/ichiban/watcher.rb
CHANGED
@@ -6,31 +6,48 @@ module Ichiban
|
|
6
6
|
}.merge(options)
|
7
7
|
end
|
8
8
|
|
9
|
-
def start
|
10
|
-
@
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
(
|
19
|
-
|
20
|
-
|
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
|
-
|
24
|
-
|
36
|
+
end
|
37
|
+
deleted.each do |path|
|
38
|
+
Ichiban::Deleter.new.delete_dest(path)
|
39
|
+
end
|
25
40
|
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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/
|
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
|