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