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,94 @@
1
+ module SemiStatic
2
+ module Convertable
3
+ include ERB::Util
4
+
5
+ def load
6
+ @content = nil
7
+ if layout
8
+ layout.load
9
+ end
10
+ super
11
+ end
12
+
13
+ def content(options={})
14
+ return @content unless @content.nil?
15
+
16
+ options = { :page => self }.merge(options)
17
+ for name, value in options
18
+ eval "#{name.to_s} = options[name]"
19
+ end
20
+
21
+ site.stats.record(self.class.name.split('::').last, source_path) do
22
+ case self.source_ext
23
+ when '.md', '.markdown'
24
+ @content = Maruku.new(self.source_content).to_html
25
+ when '.haml'
26
+ Haml::Engine.new(self.source_content, :filename => source_path).render(binding)
27
+ when '.erb'
28
+ ERB.new(self.source_content, nil, '-').result(binding)
29
+ when '.html'
30
+ self.source_content
31
+ else
32
+ raise ArgumentError, "Unsupported format: #{self.source_path}"
33
+ end
34
+ end
35
+ end
36
+
37
+ def layout_name
38
+ unless source_metadata.nil? || !source_metadata.include?(:layout)
39
+ source_metadata[:layout].to_sym
40
+ end
41
+ end
42
+
43
+ def layout
44
+ if layout_name.nil?
45
+ return nil
46
+ else
47
+ return site.layouts[layout_name.to_sym]
48
+ end
49
+ end
50
+
51
+ def source_mtime
52
+ mtime = super
53
+ if layout && layout.source_mtime > mtime
54
+ mtime = layout.source_mtime
55
+ end
56
+ return mtime
57
+ end
58
+
59
+ def render(options={})
60
+ content = self.content(options)
61
+ if layout
62
+ options = { :page => self }.merge options
63
+ content = layout.render(options.merge( :content => content ))
64
+ end
65
+ return content
66
+ end
67
+
68
+ def snippet(name)
69
+ name = name.to_s
70
+ site.snippets[name].render :page => self
71
+ end
72
+
73
+ # This method is adapted from Haml::Buffer#parse_object_ref -- it's
74
+ # used to make it easier for Haml and ERB layouts to generate the
75
+ # same output so I can use the same test output for both.
76
+ def object_ref(object)
77
+ return '' if object.nil?
78
+ name = underscore(object.class)
79
+ id = "#{name}_#{object.id || 'new'}"
80
+ return "class='#{name}' id='#{id}'"
81
+ end
82
+
83
+ # Changes a word from camel case to underscores. Based on the method
84
+ # of the same name in Rails' Inflector, but copied here so it'll run
85
+ # properly without Rails.
86
+ def underscore(camel_cased_word)
87
+ camel_cased_word.to_s.gsub(/::/, '_').
88
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
89
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
90
+ tr("-", "_").
91
+ downcase
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,19 @@
1
+ module SemiStatic
2
+ module CoreExt #:nodoc:
3
+ module Hash
4
+ # Replace self with a hash whose keys have been converted to symbols.
5
+ def symbolize_keys
6
+ hash = to_a.inject({}) do |memo,item|
7
+ key, value = item
8
+ memo[key.to_sym] = value
9
+ memo
10
+ end
11
+ replace(hash)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ class Hash #:nodoc:
18
+ include SemiStatic::CoreExt::Hash
19
+ end
@@ -0,0 +1,12 @@
1
+ module SemiStatic
2
+ class Index < Base
3
+ include Convertable
4
+
5
+ attr_accessor :posts, :context
6
+
7
+ def initialize(site, path)
8
+ super
9
+ @metadata = [ :layout ]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module SemiStatic
2
+ class Layout < Base
3
+ include Convertable
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(site, path)
8
+ super
9
+ @metadata = [ :layout ]
10
+
11
+ @name = File.basename(source_path, source_ext)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,58 @@
1
+ module SemiStatic
2
+ # Page represents a static page in the site. Page itself only
3
+ # represents a single source file, but can have a Layout and any number
4
+ # of Snippets, each of which has its own source file. Page's modification
5
+ # time is the most recent of itself, its layout, and any Snippets used.
6
+ class Page < Base
7
+ include Convertable
8
+
9
+ ##
10
+ # The Page's output directory
11
+ attr_reader :output_dir
12
+
13
+ ##
14
+ # The Page's output path
15
+ attr_reader :output_path
16
+
17
+ ##
18
+ # The Page's name
19
+ attr_reader :name
20
+
21
+ ##
22
+ # The Page's URI
23
+ attr_reader :uri
24
+
25
+ ##
26
+ # Initializes a new Page
27
+ #
28
+ # +site+:: The Site object we belong to
29
+ # +path+:: The relative path to the source file
30
+ def initialize(site, path)
31
+ super
32
+ @metadata = [ :title, :layout ]
33
+
34
+ src_base = File.basename(source_path, source_ext)
35
+ output_ext = File.extname(src_base)
36
+ if output_ext.nil? || output_ext.empty?
37
+ output_ext = '.html'
38
+ else
39
+ src_base = File.basename(src_base, output_ext)
40
+ end
41
+ @output_dir, src_file = File.split(source_path)
42
+
43
+ if output_dir == '.'
44
+ prefix = ''
45
+ else
46
+ prefix = "#{output_dir}/"
47
+ end
48
+ @name = "/#{prefix}#{src_base}"
49
+ @output_path = "#{prefix}#{src_base}#{output_ext}"
50
+
51
+ if src_base == 'index'
52
+ @uri = "/#{output_dir}/"
53
+ else
54
+ @uri = "/#{output_path}"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,133 @@
1
+ module SemiStatic
2
+ # Post represents a single post in the site. Post itself only
3
+ # represents a single source file, but can have a Layout and any number
4
+ # of Snippets, each of which has its own source file. Post's modification
5
+ # time is the most recent of itself, its Layout, and any Snippets used.
6
+ class Post < Base
7
+ include Convertable
8
+
9
+ ##
10
+ # The Post's name
11
+ attr_reader :name
12
+
13
+ ##
14
+ # The Post's Category
15
+ attr_reader :category
16
+
17
+ ##
18
+ # The Post's Tags
19
+ attr_reader :tags
20
+
21
+ ##
22
+ # The Post's output directory
23
+ attr_reader :output_dir
24
+
25
+ ##
26
+ # The Post's output path
27
+ attr_reader :output_path
28
+
29
+ ##
30
+ # The Post's slug
31
+ attr_reader :slug
32
+
33
+ ##
34
+ # The Post's URI
35
+ attr_reader :uri
36
+
37
+ ##
38
+ # Date Post was created
39
+ attr_reader :created
40
+
41
+ ##
42
+ # Date the Post was last updated
43
+ attr_reader :updated
44
+
45
+ # attr_reader :year
46
+ # attr_reader :month
47
+ # attr_reader :day
48
+
49
+ ##
50
+ # Initializes a new Post
51
+ #
52
+ # +site+:: The Site object we belong to
53
+ # +path+:: The relative path to the source file
54
+ def initialize(site, path)
55
+ super
56
+ @metadata = [ :title, :layout, :author ]
57
+
58
+ @name = File.basename(source_path, source_ext)
59
+
60
+ @category = site.categories[source_metadata[:category]]
61
+ @category << self
62
+
63
+ @tags = []
64
+ unless source_metadata[:tags].nil?
65
+ for tag in source_metadata[:tags]
66
+ @tags << site.tags[tag]
67
+ @tags.last << self
68
+ end
69
+ end
70
+
71
+ if name =~ /^(\d+-\d+-\d+)-(.+)$/
72
+ @created = Time.parse $1
73
+ @updated ||= @created
74
+ @slug = $2
75
+ @output_dir = created.strftime('%Y/%m/%d')
76
+ @output_path = File.join output_dir, "#{slug}.html"
77
+ @uri = "/#{output_path}"
78
+ else
79
+ raise ArgumentError, "Bad file name: #{name}"
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Get the Post's unique identifier
85
+ def id
86
+ name.gsub /-/, '_'
87
+ end
88
+
89
+ ##
90
+ # Get the Post's permalink
91
+ def permalink
92
+ return "#{uri}"
93
+ end
94
+
95
+ # def comments_link
96
+ # "#{uri}#comments"
97
+ # end
98
+ #
99
+ # def comment_count
100
+ # 0
101
+ # end
102
+
103
+ ##
104
+ # Formatted date the Post was published (i.e., "March 15, 2009")
105
+ def date
106
+ created.strftime '%B %e, %Y'
107
+ end
108
+
109
+ ##
110
+ # Year the Post was published as a String (i.e., "2009")
111
+ def year
112
+ created.strftime '%Y'
113
+ end
114
+
115
+ ##
116
+ # Month the Post was published as a String (i.e., "03")
117
+ def month
118
+ created.strftime '%m'
119
+ end
120
+
121
+ ##
122
+ # Day the Post was published as a String (i.e., "15")
123
+ def day
124
+ created.strftime '%d'
125
+ end
126
+
127
+ ##
128
+ # Does the post have a Time set?
129
+ def time?
130
+ false
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,110 @@
1
+ module SemiStatic
2
+ class Posts
3
+ attr_reader :site, :posts, :names, :indices
4
+
5
+ def initialize(site)
6
+ @site = site
7
+ @posts = []
8
+ @names = {}
9
+ @indices = Set.new
10
+ end
11
+
12
+ NAME_RE = /^([0-9]{4})-([0-9]{2})-([0-9]{2})-(.*)/
13
+
14
+ def <<(source_path)
15
+ unless File.file?(source_path)
16
+ $stderr.puts "[#{source_path}]"
17
+ return
18
+ end
19
+ if source_path =~ NAME_RE
20
+ post = Post.new site, source_path
21
+ self.posts.push post
22
+ names[post.name] = post
23
+ indices << File.join(post.year, post.month, post.day)
24
+ indices << File.join(post.year, post.month)
25
+ indices << post.year
26
+ else
27
+ $stderr.puts "{#{source_path}}"
28
+ end
29
+ end
30
+
31
+ def chop!(count)
32
+ raise ArgumentError unless count > 0
33
+ posts = self.posts.first(count)
34
+ @posts = []
35
+ @names = {}
36
+ @indices = Set.new
37
+ for post in posts
38
+ self.posts << post
39
+ names[post.name] = post
40
+ indices << File.join(post.year, post.month, post.day)
41
+ indices << File.join(post.year, post.month)
42
+ indices << post.year
43
+ end
44
+ end
45
+
46
+ def length
47
+ self.posts.length
48
+ end
49
+
50
+ def first(n=nil)
51
+ if n.nil?
52
+ self.posts.first
53
+ else
54
+ self.posts.first(n)
55
+ end
56
+ end
57
+
58
+ def last(n=nil)
59
+ if n.nil?
60
+ self.posts.last
61
+ else
62
+ self.posts.last(n).reverse
63
+ end
64
+ end
65
+
66
+ def from(year, month=nil, day=nil)
67
+ if year.is_a?(String) && month.nil? && day.nil?
68
+ date = year.split('/', 3)
69
+ year, month, day = date.collect { |c| c.to_i }
70
+ end
71
+
72
+ if month.nil?
73
+ from = Time.local year
74
+ to = Time.local(year + 1) - 1
75
+ elsif day.nil?
76
+ from = Time.local year, month
77
+ if month == 12
78
+ to = Time.local(year + 1, 1) - 1
79
+ else
80
+ to = Time.local(year, month + 1) - 1
81
+ end
82
+ else
83
+ from = Time.local year, month, day
84
+ to = Time.local year, month, day, 23, 59, 59
85
+ end
86
+ range = from..to
87
+ result = self.posts.select { |p| range.include?(p.created) }
88
+ class << result
89
+ alias_method :last_without_reverse, :last
90
+ def last_with_reverse(n=nil)
91
+ last_without_reverse(n).reverse
92
+ end
93
+ alias_method :last, :last_with_reverse
94
+ end
95
+ return result
96
+ end
97
+
98
+ def [](name)
99
+ return self.names[name]
100
+ end
101
+
102
+ def each(&block)
103
+ self.posts.each(&block)
104
+ end
105
+
106
+ def each_index(&block)
107
+ indices.each(&block)
108
+ end
109
+ end
110
+ end