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 ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # for dev purposes:
4
+ #$:.unshift File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
5
+
6
+ require 'rubygems'
7
+ require 'ichiban'
8
+
9
+ Ichiban::Command.new(ARGV).run
@@ -0,0 +1,19 @@
1
+ load 'deploy'
2
+
3
+ set :scm, :git # Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`
4
+ set :repository, 'ssh://user@example.com/my_site.git'
5
+ set :branch, 'master'
6
+ set :keep_releases, 2
7
+ set :deploy_via, :remote_cache
8
+
9
+ role :web, 'example.com'
10
+ set :user, 'username'
11
+ set :use_sudo, false
12
+ set :deploy_to, '/home/username/my_site'
13
+
14
+ namespace :deploy do
15
+ task(:restart) {}
16
+ end
17
+
18
+ set :keep_releases, 2
19
+ after "deploy:update", "deploy:cleanup"
@@ -0,0 +1 @@
1
+ body {font-family: sans-serif;}
@@ -0,0 +1 @@
1
+ // Nothing here yet. This JS file is referenced from the default layout.
@@ -0,0 +1,6 @@
1
+ Put PDFs, PPTs, and whatever other miscellaneous asset files here. Paths in this folder map to paths
2
+ in the compiled folder. For example, assets/about_us.pdf would be copied to compiled/about_us.pdf.
3
+
4
+ Remember that everything you put here will be checked into git. If you're deploying with Capistrano,
5
+ a large repository will slow down your deployment. For this reason, and for better download times
6
+ for your users, it's often better to host your large asset files on a CDN instead.
@@ -0,0 +1 @@
1
+ body {font-family: sans-serif;}
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Home - Site Name Here</title>
5
+ <link rel="stylesheet" type="text/css" href="/css/screen.css"/>
6
+ <script type="text/javascript" src="/js/interaction.js"></script>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Home</h1>
11
+ </body>
12
+ </html>
@@ -0,0 +1 @@
1
+ // Nothing here yet. This JS file is referenced from the default layout.
@@ -0,0 +1,3 @@
1
+ Ichiban.config do |config|
2
+ config.relative_url_root = '/'
3
+ end
@@ -0,0 +1,2 @@
1
+ If you have some data sources that you use to auto-generate HTML, such as CSV or XML files,
2
+ you can put them in here.
@@ -0,0 +1,10 @@
1
+ You can write custom helper modules and put them in this directory. The methods they define
2
+ will be available in your templates. For example:
3
+
4
+ # helpers/my_helper.rb
5
+
6
+ module MyHelper
7
+ def double(num)
8
+ num * 2
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ <% @page_title = 'Home' %>
2
+
3
+ <h1>Home</h1>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= @page_title %> - Site Name Here</title>
5
+ <%= stylesheet_link_tag 'screen.css' %>
6
+ <%= javascript_include_tag 'interaction.js' %>
7
+ </head>
8
+
9
+ <body>
10
+ <%= yield %>
11
+ </body>
12
+ </html>
@@ -0,0 +1,3 @@
1
+ IF you're doing a lot with datasources, it's good to keep things tidy and put the logic in
2
+ separate model files. That's what this directory is for. Any Ruby files in this directory will be
3
+ automatically loaded.
@@ -0,0 +1,6 @@
1
+ This folder is for scripts that generate pages.
2
+
3
+ If you want some auto-generated code *within a single page*, it's easy: Just use some <% %> tags.
4
+ But suppose you have an XML file describing every employee in your company, and you want an
5
+ *individual* page for each one. That's where this folder coms in. You need to create a script
6
+ that generates each page based on a template. See the documentation for more details.
@@ -0,0 +1,24 @@
1
+ RewriteEngine On
2
+
3
+ # Force the trailing slash for all pages unless they end in .html
4
+ RewriteCond %{REQUEST_FILENAME} !-f
5
+ RewriteCond %{REQUEST_FILENAME} !-d
6
+ # Skipping pages with the .html extension is necessary to avoid an infinite chain of .html. See the note at the bottom.
7
+ RewriteCond %{REQUEST_URI} !\.html$
8
+ RewriteRule ^(.*[^/])$ /$1/ [R=301,L]
9
+
10
+ RewriteCond %{REQUEST_FILENAME} !-f
11
+ RewriteCond %{REQUEST_FILENAME} !-d
12
+ # If the HTML file doesn't exist don't attempt to rewrite, or we'll get an infinite loop
13
+ RewriteCond %{REQUEST_FILENAME}.html -f
14
+ # If we've already tried appending HTML and the file still doesn't exist, don't do it again. See the note at the bottom.
15
+ RewriteCond %{REQUEST_URI} !\.html$
16
+ RewriteRule ^(.*)/$ $1.html
17
+
18
+ ErrorDocument 404 /404.html
19
+ ErrorDocument 422 /422.html
20
+ ErrorDocument 500 /500.html
21
+
22
+ # Note on infinite .html appending loops: We have to take pains to ensure that if a certain file, say foo.html, exists,
23
+ # then a path like /foo/bar won't get .html appended an infinite number of times. This can happen because %{REQUEST_FILENAME}
24
+ # would match /foo/bar/ to foo.html. Our redirect to force trailing slashes then sends us through an infinite loop.
@@ -0,0 +1,21 @@
1
+ module Ichiban
2
+ class AssetCompiler
3
+ def initialize(file)
4
+ @file = file
5
+ end
6
+
7
+ def compile
8
+ dir = File.dirname @file.dest
9
+ unless File.directory? dir
10
+ FileUtils.mkdir_p dir
11
+ end
12
+ case @file
13
+ when Ichiban::SCSSFile
14
+ Sass.compile_file @file.abs, @file.dest
15
+ else
16
+ FileUtils.cp @file.abs, @file.dest
17
+ end
18
+ Ichiban.logger.compilation(@file.abs, @file.dest)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module Ichiban
2
+ def self.load_bundle
3
+ if Ichiban.project_root and File.exists?(File.join(Ichiban.project_root, 'Gemfile'))
4
+ Bundler.require
5
+ end
6
+ end
7
+ end
@@ -6,15 +6,25 @@ module Ichiban
6
6
  end
7
7
 
8
8
  def print_usage
9
- puts "Usage: ichiban <command>"
10
- puts " Available commands: watch"
9
+ puts(
10
+ "Usage: ichiban [command]\n" +
11
+ "Available commands: \n" +
12
+ " watch\n" +
13
+ " new [path]"
14
+ )
11
15
  end
12
16
 
13
17
  def run
14
- Ichiban.project_root = Dir.getwd
15
18
  case @task
16
19
  when 'watch'
20
+ Ichiban.project_root = Dir.getwd
21
+ Ichiban.load_bundle
17
22
  Ichiban::Watcher.new.start
23
+ when 'new'
24
+ Ichiban::ProjectGenerator.new(
25
+ File.expand_path(@args[0])
26
+ ).generate
27
+ puts "Initialized Ichiban project in #{@args[0]}"
18
28
  else
19
29
  print_usage
20
30
  end
@@ -9,8 +9,8 @@ module Ichiban
9
9
  attr_writer :relative_url_root
10
10
 
11
11
  def self.load_file
12
- config_file = ::File.join(Ichiban.project_root, 'config.rb')
13
- raise "#{config_file} must exist" unless ::File.exists?(config_file)
12
+ config_file = File.join(Ichiban.project_root, 'config.rb')
13
+ raise "#{config_file} must exist" unless File.exists?(config_file)
14
14
  load config_file
15
15
  end
16
16
 
@@ -2,19 +2,16 @@ module Ichiban
2
2
  class Deleter
3
3
  # Deletes a file's associated destination file, if any.
4
4
  def delete_dest(path)
5
- file = Ichiban::File.from_abs(path)
6
- # file will be nil if the path doesn't map to a known subclass of Ichiban::File. Furthermore,
7
- # even if file is not nil, it may be a kind of Ichiban::File that does not have a destination.
5
+ file = Ichiban::ProjectFile.from_abs(path)
6
+ # file will be nil if the path doesn't map to a known subclass of IchibanFile. Furthermore,
7
+ # even if file is not nil, it may be a kind of IchibanFile that does not have a destination.
8
8
  if file and file.has_dest?
9
9
  dest = file.dest
10
10
  else
11
11
  dest = nil
12
12
  end
13
- if dest and ::File.exists?(dest)
14
- puts 'yep'
13
+ if dest and File.exists?(dest)
15
14
  FileUtils.rm(dest)
16
- else
17
- puts 'nope'
18
15
  end
19
16
 
20
17
  # Log the deletion(s)
@@ -2,32 +2,53 @@ module Ichiban
2
2
  module Dependencies
3
3
  @graphs = {}
4
4
 
5
- # graph_file_path is an absolute path.
5
+ # Does not delete the files. Just clears the graphs from memory. This gets called whenever
6
+ # Ichiban.project_root is changed.
7
+ def self.clear_graphs
8
+ @graphs = {}
9
+ end
10
+
11
+ def self.delete_dep(graph_file_path, dep)
12
+ ensure_graph_initialized(graph_file_path)
13
+ graph = @graphs[graph_file_path]
14
+ graph.each do |ind, deps|
15
+ deps.delete dep
16
+ end
17
+ save_graph_file(graph_file_path, graph)
18
+ end
19
+
20
+ # graph_file_path is a relative path
6
21
  def self.graph(graph_file_path)
7
22
  ensure_graph_initialized(graph_file_path)
8
23
  @graphs[graph_file_path]
9
24
  end
10
25
 
26
+ # graph_file_path is a relative path
11
27
  def self.ensure_graph_initialized(graph_file_path)
12
28
  unless @graphs[graph_file_path]
13
- if ::File.exists?(graph_file_path)
14
- @graphs[graph_file_path] = JSON.parse(::File.read(graph_file_path))
29
+ abs = File.join(Ichiban.project_root, graph_file_path)
30
+ if File.exists?(abs)
31
+ @graphs[graph_file_path] = JSON.parse(File.read(abs))
15
32
  else
16
33
  @graphs[graph_file_path] = {}
17
34
  end
18
35
  end
19
36
  end
20
37
 
38
+ def self.save_graph_file(graph_file_path, graph)
39
+ File.open(File.join(Ichiban.project_root, graph_file_path), 'w') do |f|
40
+ f << JSON.generate(graph)
41
+ end
42
+ end
43
+
21
44
  # Loads the graph from disk if it's not already in memory. Updates the graph. Writes the new
22
- # graph to disk. graph_file_path is an absolute path.
45
+ # graph to disk. graph_file_path is a relative path.
23
46
  def self.update(graph_file_path, ind, dep)
24
47
  ensure_graph_initialized(graph_file_path)
25
48
  graph = @graphs[graph_file_path]
26
49
  graph[ind] ||= []
27
50
  graph[ind] << dep unless graph[ind].include?(dep)
28
- ::File.open(graph_file_path, 'w') do |f|
29
- f << JSON.generate(graph)
30
- end
51
+ save_graph_file(graph_file_path, graph)
31
52
  end
32
53
  end
33
54
  end
data/lib/ichiban/file.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  module Ichiban
2
- class File
2
+ class ProjectFile
3
3
  attr_reader :abs
4
4
 
5
5
  # Returns an absolute path in the compiled directory
6
6
  def dest
7
- ::File.join(Ichiban.project_root, 'compiled', dest_rel_to_compiled)
7
+ File.join(Ichiban.project_root, 'compiled', dest_rel_to_compiled)
8
8
  end
9
9
 
10
10
  # Returns a new instance based on an absolute path. Will automatically pick the right subclass.
@@ -12,10 +12,14 @@ module Ichiban
12
12
  def self.from_abs(abs)
13
13
  rel = abs.slice(Ichiban.project_root.length..-1) # Relative to project root
14
14
  rel.sub!(/^\//, '') # Remove leading slash
15
- if rel.start_with?('html') and rel.end_with?('.html')
16
- Ichiban::HTMLFile.new(rel)
15
+ if rel.start_with?('html') and (rel.end_with?('.html') or rel.end_with?('.md') or rel.end_with?('.markdown'))
16
+ if File.basename(rel).start_with?('_')
17
+ Ichiban::PartialHTMLFile.new(rel)
18
+ else
19
+ Ichiban::HTMLFile.new(rel)
20
+ end
17
21
  elsif rel.start_with?('layouts') and rel.end_with?('.html')
18
- Ichiban::LayoutFIle.new(rel)
22
+ Ichiban::LayoutFile.new(rel)
19
23
  elsif rel.start_with?('assets/js')
20
24
  Ichiban::JSFile.new(rel)
21
25
  elsif rel.start_with?('assets/css') and rel.end_with?('.css')
@@ -34,6 +38,8 @@ module Ichiban
34
38
  Ichiban::ScriptFile.new(rel)
35
39
  elsif rel.start_with?('helpers')
36
40
  Ichiban::HelperFile.new(rel)
41
+ elsif rel == 'webserver/htaccess.txt'
42
+ Ichiban::HtaccessFile.new(rel)
37
43
  else
38
44
  nil
39
45
  end
@@ -45,16 +51,18 @@ module Ichiban
45
51
 
46
52
  def initialize(rel)
47
53
  @rel = rel
48
- @abs = ::File.join(Ichiban.project_root, rel)
54
+ @abs = File.join(Ichiban.project_root, rel)
49
55
  end
50
56
 
57
+ attr_reader :rel
58
+
51
59
  # Returns a new path where the old extension is replaced with new_ext
52
60
  def replace_ext(path, new_ext)
53
61
  path.sub(/\..+$/, '.' + new_ext)
54
62
  end
55
63
  end
56
64
 
57
- class HTMLFile < File
65
+ class HTMLFile < ProjectFile
58
66
  def dest_rel_to_compiled
59
67
  d = @rel.slice('html/'.length..-1)
60
68
  (d.end_with?('.markdown') or d.end_with?('.md')) ? replace_ext(d, 'html') : d
@@ -63,53 +71,150 @@ module Ichiban
63
71
  def update
64
72
  Ichiban::HTMLCompiler.new(self).compile
65
73
  end
74
+
75
+ def web_path
76
+ d = dest_rel_to_compiled
77
+ '/' + File.basename(d, File.extname(d)) + '/'
78
+ end
79
+ end
80
+
81
+ class PartialHTMLFile < ProjectFile
82
+ # Returns something like 'foo/bar'
83
+ def partial_name
84
+ File.basename(
85
+ @abs.slice(Ichiban.project_root.length + 1..-1),
86
+ File.extname(@abs)
87
+ )
88
+ end
89
+
90
+ def update
91
+ # Normal HTML files that depend on this partial
92
+ if deps = Ichiban::Dependencies.graph('.partial_dependencies.json')[partial_name]
93
+ deps.each do |dep|
94
+ # dep will be a path relative to the html directory. It looks like this: 'folder/file.html'
95
+ Ichiban::HTMLFile.new(File.join('html', dep)).update
96
+ end
97
+ end
98
+
99
+ # Scripts that depend on this partial
100
+ dep_key = "html/#{partial_name}.html"
101
+ if deps = Ichiban::Dependencies.graph('.script_dependencies.json')[dep_key]
102
+ deps.each do |dep|
103
+ # dep will be a path relative to the html directory. It looks like this: 'folder/file.html'
104
+ script_path = File.join(Ichiban.project_root, dep)
105
+ Ichiban.logger.script_run(@abs, script_path)
106
+ script = Ichiban::Script.new(script_path).run
107
+ end
108
+ end
109
+ end
66
110
  end
67
111
 
68
- class LayoutFile < File
112
+ class LayoutFile < ProjectFile
113
+ def layout_name
114
+ File.basename(@abs, File.extname(@abs))
115
+ end
116
+
117
+ def update
118
+ Ichiban.logger.layout(@abs)
119
+ if deps = Ichiban::Dependencies.graph('.layout_dependencies.json')[layout_name]
120
+ deps.each do |dep|
121
+ # dep is a path relative to the project root
122
+ if File.exists?(File.join(Ichiban.project_root, dep))
123
+ Ichiban::HTMLFile.new(dep).update
124
+ else
125
+ Dependencies.delete_dep('.layout_dependencies.json', dep)
126
+ end
127
+ end
128
+ end
129
+ end
69
130
  end
70
131
 
71
- class JSFile < File
132
+ class JSFile < ProjectFile
72
133
  def dest_rel_to_compiled
73
134
  File.join('js', @rel.slice('assets/js/'.length..-1))
74
135
  end
136
+
137
+ def update
138
+ Ichiban::AssetCompiler.new(self).compile
139
+ end
75
140
  end
76
141
 
77
- class CSSFile < File
142
+ class CSSFile < ProjectFile
78
143
  def dest_rel_to_compiled
79
144
  File.join('css', @rel.slice('assets/css/'.length..-1))
80
145
  end
146
+
147
+ def update
148
+ Ichiban::AssetCompiler.new(self).compile
149
+ end
81
150
  end
82
151
 
83
- class SCSSFile < File
152
+ class SCSSFile < ProjectFile
84
153
  def dest_rel_to_compiled
85
154
  replace_ext(
86
155
  File.join('css', @rel.slice('assets/css/'.length..-1)),
87
156
  'css'
88
157
  )
89
158
  end
159
+
160
+ def update
161
+ Ichiban::AssetCompiler.new(self).compile
162
+ end
90
163
  end
91
164
 
92
- class ImageFile < File
165
+ class ImageFile < ProjectFile
93
166
  def dest_rel_to_compiled
94
167
  File.join('img', @rel.slice('assets/img/'.length..-1))
95
168
  end
169
+
170
+ def update
171
+ Ichiban::AssetCompiler.new(self).compile
172
+ end
96
173
  end
97
174
 
98
- class MiscAssetFile < File
175
+ class MiscAssetFile < ProjectFile
99
176
  def dest_rel_to_compiled
100
177
  @rel.slice('assets/misc/'.length..-1)
101
178
  end
179
+
180
+ def update
181
+ Ichiban::AssetCompiler.new(self).compile
182
+ end
102
183
  end
103
184
 
104
- class ModelFile < File
185
+ class HtaccessFile < ProjectFile
186
+ def dest_rel_to_compiled
187
+ '.htaccess'
188
+ end
189
+
190
+ def update
191
+ Ichiban::AssetCompiler.new(self).compile
192
+ end
105
193
  end
106
194
 
107
- class DataFile < File
195
+ class ModelFile < ProjectFile
196
+ def update
197
+ # No-op. The watcher hands the path to each changed model file to the Loader instance.
198
+ # So we don't have to worry about that here.
199
+ end
108
200
  end
109
201
 
110
- class ScriptFile < File
202
+ class HelperFile < ProjectFile
203
+ def update
204
+ # No-op. The watcher hands the path to each changed model file to the Loader instance.
205
+ # So we don't have to worry about that here.
206
+ end
111
207
  end
112
208
 
113
- class HelperFile < File
209
+ class DataFile < ProjectFile
210
+ def update
211
+ Ichiban.script_runner.data_file_changed(@abs)
212
+ end
213
+ end
214
+
215
+ class ScriptFile < ProjectFile
216
+ def update
217
+ Ichiban.script_runner.script_file_changed(@abs)
218
+ end
114
219
  end
115
220
  end
@@ -21,14 +21,14 @@ module Ichiban
21
21
  output << tag_attrs(options) << ">#{content}</#{name}>"
22
22
  end
23
23
 
24
- # Returns the path relative to site root
24
+ # Returns the path relative to site root. Includes leading and trailing slash.
25
25
  def current_path
26
26
  @_current_path
27
27
  end
28
28
 
29
29
  def javascript_include_tag(js_file)
30
30
  js_file = js_file + '.js' unless js_file.end_with?('.js')
31
- path = normalize_path(::File.join('/javascripts', js_file))
31
+ path = normalize_path(File.join('/js', js_file))
32
32
  content_tag 'script', 'type' => 'text/javascript', 'src' => path
33
33
  end
34
34
 
@@ -62,58 +62,16 @@ module Ichiban
62
62
  content_tag 'a', text, options.merge('href' => url)
63
63
  end
64
64
 
65
- # Pass in an array of this form:
66
- #
67
- # [
68
- # ['Link Text', '/path/from/relative/url/root/']
69
- # ['Link Text', '/path/from/relative/url/root/', {'id' => 'foo'}]
70
- # ]
71
- #
72
- # You can also do this, as a convenience:
73
- #
74
- # nav([
75
- # ['Link Text', 'path/from/section/root/']
76
- # ], :section => 'section-name')
77
- #
78
- # Which will generate this href:
79
- #
80
- # /section-name/path/from/section/root/
81
- #
82
- # If you don't specify a section, and your URLs don't have leading slashes,
83
- # the hrefs will use relative URLs.
84
- def nav(items, options = {})
85
- ul_options = _limit_options(options, %w(id class)) { |key, value| key.to_s.start_with?('data-') }
86
- content_tag('ul', ul_options) do
87
- items.inject('') do |lis, (text, path, attrs)|
88
- if options[:section]
89
- path = ::File.join(options[:section], path)
90
- end
91
- path = normalize_path(path)
92
- lis + content_tag('li', (attrs or {})) do
93
- if path_with_slashes(current_path) == path_with_slashes(path)
94
- content_tag('span', text, 'class' => 'selected')
95
- else
96
- link_to(text, path)
97
- end
98
- end
99
- end
100
- end
101
- end
102
-
103
- # If the path has a trailing slash, it will be made absolute using relative_url_root.
65
+ # If the path has a leading slash, it will be made absolute using relative_url_root.
104
66
  # Otherwise, it will remain relative.
105
67
  def normalize_path(path)
106
68
  if path.start_with?('/')
107
- ::File.join(relative_url_root, path)
69
+ File.join(relative_url_root, path)
108
70
  else
109
71
  path
110
72
  end
111
73
  end
112
74
 
113
- def page_title(title)
114
- @page_title = title
115
- end
116
-
117
75
  # Adds leading and trailing slashes if none are present
118
76
  def path_with_slashes(path)
119
77
  path = '/' + path unless path.start_with?('/')
@@ -121,13 +79,24 @@ module Ichiban
121
79
  path
122
80
  end
123
81
 
82
+ def partial(path)
83
+ file = Ichiban::PartialHTMLFile.new(
84
+ File.join('html', path)
85
+ )
86
+ # Record the dependency like this: 'folder/partial-name' => 'folder/included-file.html'
87
+ Ichiban::Dependencies.update('.partial_dependencies.json', file.partial_name, @_template_path)
88
+ compiler = Ichiban::HTMLCompiler.new(file)
89
+ compiler.ivars = to_hash # to_hash is inherited from Erubis::Context. It's a hash of the instance variables.
90
+ compiler.compile_to_str
91
+ end
92
+
124
93
  def relative_url_root
125
94
  Ichiban.config.relative_url_root
126
95
  end
127
96
 
128
97
  def stylesheet_link_tag(css_file, media = 'screen')
129
98
  css_file = css_file + '.css' unless css_file.end_with?('.css')
130
- href = normalize_path(::File.join('/stylesheets', css_file))
99
+ href = normalize_path(File.join('/css', css_file))
131
100
  tag 'link', 'href' => href, 'type' => 'text/css', 'rel' => 'stylesheet', 'media' => media
132
101
  end
133
102