zzot-semi-static 0.0.2

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.
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