zzot-semi-static 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/History.txt +6 -0
  2. data/Manifest.txt +30 -0
  3. data/README.markdown +84 -0
  4. data/VERSION.yml +4 -0
  5. data/bin/semi +6 -0
  6. data/lib/semi-static.rb +30 -0
  7. data/lib/semi-static/base.rb +74 -0
  8. data/lib/semi-static/categories.rb +74 -0
  9. data/lib/semi-static/cli.rb +126 -0
  10. data/lib/semi-static/convertable.rb +94 -0
  11. data/lib/semi-static/core_ext/hash.rb +19 -0
  12. data/lib/semi-static/index.rb +12 -0
  13. data/lib/semi-static/layout.rb +14 -0
  14. data/lib/semi-static/page.rb +58 -0
  15. data/lib/semi-static/post.rb +133 -0
  16. data/lib/semi-static/posts.rb +110 -0
  17. data/lib/semi-static/pygmentize.rb +54 -0
  18. data/lib/semi-static/site.rb +232 -0
  19. data/lib/semi-static/snippet.rb +12 -0
  20. data/lib/semi-static/statistics.rb +45 -0
  21. data/lib/semi-static/stylesheet.rb +33 -0
  22. data/test/helper.rb +111 -0
  23. data/test/ref/test_layout/default_layout.html +35 -0
  24. data/test/ref/test_layout/post_layout.html +85 -0
  25. data/test/ref/test_output/2005-03-27.html +5 -0
  26. data/test/ref/test_output/2005-03.html +4 -0
  27. data/test/ref/test_output/2005.html +5 -0
  28. data/test/ref/test_output/2008-11-24.html +5 -0
  29. data/test/ref/test_output/2008-11-26.html +5 -0
  30. data/test/ref/test_output/2008-11.html +5 -0
  31. data/test/ref/test_output/2008-12-04.html +5 -0
  32. data/test/ref/test_output/2008-12.html +4 -0
  33. data/test/ref/test_output/2008.html +6 -0
  34. data/test/ref/test_page/about.html +47 -0
  35. data/test/ref/test_page/colophon.html +45 -0
  36. data/test/ref/test_post/impressions.html +102 -0
  37. data/test/ref/test_post/lighting-up.html +102 -0
  38. data/test/ref/test_post/the-working-mans-typeface.html +88 -0
  39. data/test/source/indices/day.erb +7 -0
  40. data/test/source/indices/month.erb +10 -0
  41. data/test/source/indices/year.erb +10 -0
  42. data/test/source/layouts/default.haml +25 -0
  43. data/test/source/layouts/post.erb +36 -0
  44. data/test/source/pages/about.md +38 -0
  45. data/test/source/pages/colophon.md +36 -0
  46. data/test/source/pages/feed.xml.erb +26 -0
  47. data/test/source/posts/2005-03-27-a-bash-script-to-mess-with-the-containing-terminalapp-window.markdown +38 -0
  48. data/test/source/posts/2008-11-24-lighting-up.markdown +41 -0
  49. data/test/source/posts/2008-11-26-impressions.md +42 -0
  50. data/test/source/posts/2008-12-04-the-working-mans-typeface.html +15 -0
  51. data/test/source/scripts/jquery-1.3.js +4241 -0
  52. data/test/source/scripts/jquery-1.3.min.js +19 -0
  53. data/test/source/semi.yml +5 -0
  54. data/test/source/snippets/comment-links.html +17 -0
  55. data/test/source/snippets/comments.html +4 -0
  56. data/test/source/stylesheets/layout.sass +115 -0
  57. data/test/source/stylesheets/post.sass +21 -0
  58. data/test/source/stylesheets/screen.sass +11 -0
  59. data/test/source/stylesheets/syntax.css +60 -0
  60. data/test/source/stylesheets/text.sass +25 -0
  61. data/test/test_layout.rb +51 -0
  62. data/test/test_output.rb +61 -0
  63. data/test/test_page.rb +68 -0
  64. data/test/test_post.rb +96 -0
  65. metadata +183 -0
@@ -0,0 +1,54 @@
1
+ module SemiStatic
2
+ module Pygmentize
3
+ LEXER_FORMAT = /^[a-z]+$/i
4
+
5
+ def pygmentize(code, lang)
6
+ Pygmentize.pygmentize code, lang
7
+ end
8
+
9
+ def self.pygmentize(code, lang)
10
+ unless lang =~ LEXER_FORMAT
11
+ raise ArgumentError, "invalid lexer: #{lang}"
12
+ end
13
+
14
+ Tempfile.open('semistatic-pygmentize') do |temp|
15
+ temp.write code
16
+ temp.close
17
+
18
+ cmd = "pygmentize -f html -l #{lang} #{temp.path}"
19
+ IO.popen(cmd) do |proc|
20
+ return proc.read
21
+ end
22
+ end
23
+ end
24
+
25
+ @@enabled = false
26
+ def self.enabled
27
+ @@enabled
28
+ end
29
+ def self.enabled=(value)
30
+ @@enabled = value
31
+ end
32
+ end
33
+ end
34
+
35
+ module MaRuKu #:nodoc:
36
+ module Out #:nodoc:
37
+ module HTML #:nodoc:
38
+ alias_method :to_html_code_without_pygments, :to_html_code
39
+ def to_html_code_with_pygments
40
+ if SemiStatic::Pygmentize.enabled
41
+ source = self.raw_code
42
+ lang = self.attributes[:lang] || 'text'
43
+ html = SemiStatic::Pygmentize.pygmentize source, lang
44
+ doc = Document.new html, :respect_whitespace => :all
45
+
46
+ add_ws doc.root
47
+ else
48
+ to_html_code_without_pygments
49
+ end
50
+ end
51
+ alias_method :to_html_code, :to_html_code_with_pygments
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,232 @@
1
+ module SemiStatic
2
+ class Site
3
+ attr_accessor :clean_first, :check_mtime, :quick_mode, :show_statistics
4
+
5
+ attr_reader :time
6
+ attr_reader :source_dir, :layouts, :pages, :posts, :snippets, :categories, :tags
7
+ attr_reader :year_index, :month_index, :day_index
8
+ attr_reader :metadata, :stylesheets
9
+
10
+ attr_reader :stats
11
+
12
+ def initialize(source_dir)
13
+ @clean_first = false
14
+ @first_pass = true
15
+ @quick_mode = false
16
+ @check_mtime = false
17
+ @show_statistics = false
18
+ @source_dir = source_dir
19
+ @time = Time.now
20
+ @stats = Statistics.new
21
+ stats.record(:site, :load) { load }
22
+ end
23
+
24
+ def self.open(source_dir)
25
+ raise ArugmentError, "block required" unless block_given?
26
+ site = SemiStatic::Site.new source_dir
27
+ yield site
28
+ end
29
+
30
+ def output(path)
31
+ if @first_pass
32
+ @first_pass = false
33
+ FileUtils.rm_rf path if clean_first
34
+ end
35
+ FileUtils.mkdir_p path
36
+
37
+ if quick_mode
38
+ posts.chop! 20
39
+ end
40
+ @stats.reset
41
+
42
+ unless metadata.nil? || !metadata['static'].is_a?(Array)
43
+ stats.record(:site, :static) do
44
+ for dir in metadata['static']
45
+ FileUtils.cp_r File.join(source_dir, dir), File.join(path, dir)
46
+ end
47
+ end
48
+ end
49
+
50
+ before = Time.now
51
+ Dir.chdir(path) do
52
+ stats.record(:site, :pages) do
53
+ pages.each do |name, page|
54
+ FileUtils.mkdir_p page.output_dir unless File.directory?(page.output_dir)
55
+ if check_mtime
56
+ if File.file?(page.output_path) && File.mtime(page.output_path) > page.source_mtime
57
+ next
58
+ end
59
+ page.load
60
+ end
61
+ File.open(page.output_path, 'w') { |f| f.write page.render }
62
+ end
63
+ end
64
+
65
+ stats.record(:site, :posts) do
66
+ posts.each do |post|
67
+ FileUtils.mkdir_p post.output_dir unless File.directory?(post.output_dir)
68
+ if check_mtime
69
+ if File.file?(post.output_path) && File.mtime(post.output_path) > post.source_mtime
70
+ next
71
+ end
72
+ post.load
73
+ end
74
+ File.open(post.output_path, 'w') { |f| f.write post.render }
75
+ end
76
+ end
77
+
78
+ stats.record(:site, :stylesheets) do
79
+ unless stylesheets.nil?
80
+ stylesheets.each do |name, stylesheet|
81
+ FileUtils.mkdir_p stylesheet.output_dir unless File.directory?(stylesheet.output_dir)
82
+ if check_mtime
83
+ if File.file?(stylesheet.output_path) && File.mtime(stylesheet.output_path) > stylesheet.source_mtime
84
+ next
85
+ end
86
+ stylesheet.load
87
+ end
88
+ File.open(stylesheet.output_path, 'w') { |f| f.write stylesheet.render }
89
+ end
90
+ end
91
+ end
92
+
93
+ stats.record(:site, :indices) do
94
+ unless year_index.nil? && month_index.nil? && day_index.nil?
95
+ posts.each_index do |dir|
96
+ posts = self.posts.from(dir)
97
+ Dir.chdir(dir) do
98
+ context = dir.split('/').collect { |c| c.to_i }
99
+ date_index = case context.length
100
+ when 1
101
+ year_index
102
+ when 2
103
+ month_index
104
+ when 3
105
+ day_index
106
+ else
107
+ nil
108
+ end
109
+ date_index.posts = posts
110
+ date_index.context = Time.local *context
111
+ File.open('index.html', 'w') { |f| f.write date_index.render }
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ self.stats.display if show_statistics
119
+ end
120
+
121
+ private
122
+ def with_source_files(subdir, pattern)
123
+ raise ArgumentError unless block_given?
124
+ Dir.chdir(File.join(source_dir, subdir)) do
125
+ Dir.glob(pattern) do |path|
126
+ yield path
127
+ end
128
+ end
129
+ end
130
+
131
+ def config_file_path
132
+ File.join source_dir, 'semi.yml'
133
+ end
134
+
135
+ def load
136
+ if File.file?(config_file_path)
137
+ @metadata = File.open(config_file_path) { |io| YAML.load io }
138
+ end
139
+ load_stylesheets
140
+ load_layouts
141
+ load_pages
142
+ load_posts
143
+ load_snippets
144
+ load_indices
145
+ end
146
+
147
+ def load_stylesheets
148
+ unless metadata.nil? || metadata['stylesheets'].nil?
149
+ Dir.chdir(File.join(source_dir, 'stylesheets')) do
150
+ config = metadata['stylesheets']
151
+ if config.is_a?(Array)
152
+ config = config.inject({}) { |hash,name| hash[name] = {}; hash }
153
+ end
154
+ @stylesheets = config.inject({}) do |hash,pair|
155
+ name, opts = pair
156
+ hash[name.to_sym] = Stylesheet.new self, name, opts
157
+ hash
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ def load_layouts
164
+ @layouts = Hash.new
165
+ with_source_files('layouts', '*.{haml,erb}') do |path|
166
+ next unless File.file?(path)
167
+
168
+ file = File.basename(path)
169
+ if file[0..0] != '_'
170
+ layout = Layout.new self, path
171
+ self.layouts[layout.name.to_sym] = layout
172
+ end
173
+ end
174
+ end
175
+
176
+ def load_pages
177
+ @pages = Hash.new
178
+ with_source_files('pages', '**/*.{html,haml,erb,txt,md,markdown}') do |path|
179
+ next if File.directory?(path)
180
+ next unless path.split('/').grep(/^_/).empty?
181
+
182
+ page = Page.new self, path
183
+ self.pages[page.name] = page
184
+ end
185
+ end
186
+
187
+ def load_posts
188
+ @posts = Posts.new(self)
189
+ @categories = Categories.new
190
+ @tags = Categories.new
191
+ with_source_files('posts', '*.{html,haml,erb,txt,md,markdown}') do |path|
192
+ posts << path
193
+ end
194
+ posts.posts.sort! { |l,r| l.created <=> r.created }
195
+ end
196
+
197
+ def load_snippets
198
+ return unless File.directory?(File.join(source_dir, 'snippets'))
199
+
200
+ @snippets = Hash.new
201
+ with_source_files('snippets', '*.{html,haml,erb,txt,md,markdown}') do |path|
202
+ next unless File.file?(path)
203
+ next unless ('a'..'z').include?(path[0..0].downcase)
204
+
205
+ snippet = Snippet.new self, path
206
+ self.snippets[snippet.name] = snippet
207
+ end
208
+ end
209
+
210
+ def load_indices
211
+ return unless File.directory?(File.join(source_dir, 'indices'))
212
+
213
+ with_source_files('indices', '{year,month,day}.{haml,erb}') do |path|
214
+ # puts path
215
+ next unless File.file?(path)
216
+
217
+ file = File.basename(path)
218
+ name = File.basename(file, File.extname(file))
219
+ case name
220
+ when 'year'
221
+ @year_index = Index.new self, path
222
+ when 'month'
223
+ @month_index = Index.new self, path
224
+ when 'day'
225
+ @day_index = Index.new self, path
226
+ else
227
+ raise ArgumentError, "Unexpected index file: #{path}"
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,12 @@
1
+ module SemiStatic
2
+ class Snippet < Base
3
+ include Convertable
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(site, path)
8
+ super
9
+ @name = File.basename(source_path, source_ext)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,45 @@
1
+ module SemiStatic
2
+ class Statistics
3
+ def initialize
4
+ self.reset
5
+ end
6
+
7
+ def reset
8
+ @data = Hash.new { |hash,key| hash[key] = Hash.new }
9
+ end
10
+
11
+ def record(category, item)
12
+ raise ArgumentError unless block_given?
13
+ before = Time.now
14
+ result = yield
15
+ @data[category][item] = Time.now - before
16
+ return result
17
+ end
18
+
19
+ def display
20
+ # details = {}
21
+ @data.each do |category,items|
22
+ next if category == :site
23
+ sum = 0; items.values.each { |time| sum += time }
24
+ list = items.sort { |l,r| l.last <=> r.last }
25
+
26
+ if list.length > 1
27
+ printf "%10s c:%-3d sum:%9.6f min:%.6f max:%.6f avg:%.6f\n",
28
+ category, items.length, sum, list.first.last,
29
+ list.last.last, sum / items.length
30
+ # details[category] = list.reverse.first(5).collect { |pair| { pair.first => pair.last } }
31
+ else
32
+ printf "%10s c:%-3d sum:%9.6f\n", category, items.length, sum
33
+ end
34
+ end
35
+ puts '---'
36
+ @data[:site].each { |cat,time| printf "%15s %9.6f\n", cat.to_s.capitalize, time }
37
+
38
+ # unless details.empty?
39
+ # puts '---'
40
+ # puts
41
+ # puts details.to_yaml
42
+ # end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ module SemiStatic
2
+ class Stylesheet < Base
3
+ attr_reader :name, :options, :output_dir, :output_path
4
+
5
+ def initialize(site, name, options={})
6
+ path = "#{name}.sass"
7
+ path = "#{name}.css" unless File.file?(path)
8
+ super(site, path)
9
+
10
+ @name, @options = name, options
11
+ @output_dir = 'css'
12
+ @output_path = "#{output_dir}/#{name}.css"
13
+ end
14
+
15
+ def load
16
+ super
17
+ Dir.chdir(File.dirname(full_source_path)) do
18
+ @content = case source_ext
19
+ when '.sass'
20
+ Sass::Engine.new(source_content, :filename => source_path).render
21
+ when '.css'
22
+ source_content
23
+ else
24
+ raise ArgumentError, "Unsupported format: #{self.source_path}"
25
+ end
26
+ end
27
+ end
28
+
29
+ def render
30
+ @content
31
+ end
32
+ end
33
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,111 @@
1
+ $VERBOSE = false # Haml creates a lot of noise with this set
2
+
3
+ require 'tempfile'
4
+ require 'test/unit'
5
+ require "#{File.dirname __FILE__}/../lib/semi-static"
6
+
7
+ class Test::Unit::TestCase
8
+ TEST_SOURCE_DIR = File.join(File.dirname(__FILE__), 'source')
9
+ TEST_OUTPUT_DIR = File.join(File.dirname(__FILE__), 'output')
10
+
11
+ def with_test_site
12
+ raise ArgumentError, "block required" unless block_given?
13
+ site = SemiStatic::Site.open(TEST_SOURCE_DIR) do |site|
14
+ assert_not_nil site
15
+ yield site
16
+ end
17
+ end
18
+
19
+ def with_test_cli
20
+ raise ArgumentError, "block required" unless block_given?
21
+ cli = SemiStatic::CLI.new
22
+ cli.source_dir = TEST_SOURCE_DIR
23
+ cli.output_dir = TEST_OUTPUT_DIR
24
+ yield cli
25
+ end
26
+
27
+ def with_test_site_page(page_name)
28
+ raise ArgumentError, "block required" unless block_given?
29
+ with_test_site do |site|
30
+ page = site.pages[page_name]
31
+ assert_not_nil page
32
+ yield site, page
33
+ end
34
+ end
35
+
36
+ def with_test_site_post(post_name)
37
+ raise ArgumentError, "block required" unless block_given?
38
+ with_test_site do |site|
39
+ post = site.posts[post_name]
40
+ assert_not_nil post
41
+ yield site, post
42
+ end
43
+ end
44
+
45
+ def ref(name)
46
+ path = File.join File.dirname(__FILE__), 'ref', name
47
+ return File.read(path)
48
+ end
49
+
50
+ def out(path)
51
+ path = File.join File.dirname(__FILE__), 'output', path
52
+ return File.read(path)
53
+ end
54
+
55
+ def diff(left, right, options={})
56
+ Tempfile.open('left') do |t1|
57
+ t1.write left
58
+ t1.close
59
+ Tempfile.open('right') do |t2|
60
+ t2.write right
61
+ t2.close
62
+
63
+ cmd = 'diff -ub -I \'^[[:space:]]*$\''
64
+
65
+ cmd << " --label '#{options[:left]}'" if options.include?(:left)
66
+ cmd << " #{t1.path}"
67
+
68
+ cmd << " --label '#{options[:right]}'" if options.include?(:right)
69
+ cmd << " #{t2.path}"
70
+
71
+ return `#{cmd}`
72
+ end
73
+ end
74
+ end
75
+
76
+ def assert_equal_diff(expected, actual, diff_options={})
77
+ msg = diff expected, actual, diff_options
78
+ assert_block(msg) { expected == actual }
79
+ end
80
+
81
+ def assert_render_equal_ref(ref_name, renderable, render_options={}, diff_options={})
82
+ expected = ref(ref_name)
83
+ actual = renderable.render(render_options)
84
+ diff_options = { :left => ref_name, :right => renderable.name }.merge diff_options
85
+
86
+ # diff_options[:left] = ref_name unless diff_options.include?(:left)
87
+ # diff_options[:right] = renderable.output_path unless diff_options.include?(:right)
88
+
89
+ msg = diff expected, actual, diff_options
90
+ assert_block(msg) { $?.success? }
91
+ end
92
+
93
+ def assert_directory(path, msg=nil)
94
+ msg = build_message msg, "Expected to be a directory: <?>", path
95
+ assert_block(msg) { File.directory? path }
96
+ end
97
+
98
+ def assert_file(path, msg=nil)
99
+ msg = build_message msg, "Expected to be a regular file: <?>", path
100
+ assert_block(msg) { File.file? path }
101
+ end
102
+
103
+ def assert_file_equal_ref(ref_name, out_path, diff_options={})
104
+ expected = ref(ref_name)
105
+ actual = out(out_path)
106
+ diff_options = { :left => ref_name, :right => out_path }.merge diff_options
107
+
108
+ msg = diff expected, actual, diff_options
109
+ assert_block(msg) { $?.success? }
110
+ end
111
+ end