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