zzot-zzot-semi-static 0.0.1

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,6 @@
1
+ ===
2
+ * Major Enhancements
3
+ * Switch from Hoe to Jeweler
4
+
5
+ === 0.0.0 / 2009-01-12
6
+ * New project
@@ -0,0 +1,30 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.markdown
4
+ Rakefile
5
+ bin/semi-static
6
+ lib/semi-static.rb
7
+ lib/semi-static/base.rb
8
+ lib/semi-static/categories.rb
9
+ lib/semi-static/convertable.rb
10
+ lib/semi-static/core_ext/hash.rb
11
+ lib/semi-static/layout.rb
12
+ lib/semi-static/page.rb
13
+ lib/semi-static/post.rb
14
+ lib/semi-static/site.rb
15
+ test/helper.rb
16
+ test/ref/test_layout/default_layout.html
17
+ test/ref/test_layout/post_layout.html
18
+ test/ref/test_page/about.html
19
+ test/ref/test_page/colophon.html
20
+ test/ref/test_post/lighting-up.html
21
+ test/source/_layouts/default.haml
22
+ test/source/_layouts/post.haml
23
+ test/source/_posts/2008-11-24-lighting-up.markdown
24
+ test/source/_posts/2008-11-26-impressions.md
25
+ test/source/_posts/2008-12-04-the-working-mans-typeface.html
26
+ test/source/about.md
27
+ test/source/colophon.md
28
+ test/test_layout.rb
29
+ test/test_page.rb
30
+ test/test_post.rb
@@ -0,0 +1,84 @@
1
+ Semi-Static
2
+ ===========
3
+
4
+ Semi-Static is yet another static site generator. I've been playing around
5
+ with similar systems off and on for years, but its [Jekyll][] that finally
6
+ convinced me to build (and finish) one. The main idea that I take from
7
+ Jekyll is using [Git][] as the “database”. The idea from Jekyll that I'm
8
+ ignoring completely is avoiding [Ruby][] code evaluation at all costs — I'm
9
+ not running the thing on a server like [GitHub][] [Pages][], and it
10
+ eliminates a few libraries that I want to use.
11
+
12
+ At some point Semi-Static had a sister project, Semi-Live, that would serve
13
+ up data from the data source dynamically, probably via [Rails][]. I stopped
14
+ working on it not long after I first started, so don't expect to see it
15
+ following its sister off to my [projects][] page.
16
+
17
+ Features
18
+ --------
19
+
20
+ * Layouts can be [Haml][] or [ERB][] — but not [Liquid][].
21
+ * Stylesheets can be [Sass][] or raw CSS.
22
+ * Pages and posts can be [Maruku][] or raw HTML.
23
+ * Code highlighting by [Pygments][].
24
+
25
+ Installation
26
+ ------------
27
+
28
+ The easiest way to install Semi-Static is via [RubyGems][]:
29
+
30
+ $ sudo gem install zzot-semi-static -s http://gems.github.com/
31
+
32
+ Semi-Static requires the \`Haml\` and \`Maruku\` gems and the \`Pygments\`
33
+ library to be installed.
34
+
35
+ Usage
36
+ -----
37
+
38
+ $ semi source-path \[output-path\]
39
+
40
+ License
41
+ -------
42
+
43
+ (The MIT License)
44
+
45
+ Copyright (c) 2009 Josh Dady
46
+
47
+ Permission is hereby granted, free of charge, to any person obtaining
48
+ a copy of this software and associated documentation files (the
49
+ 'Software'), to deal in the Software without restriction, including
50
+ without limitation the rights to use, copy, modify, merge, publish,
51
+ distribute, sublicense, and/or sell copies of the Software, and to
52
+ permit persons to whom the Software is furnished to do so, subject to
53
+ the following conditions:
54
+
55
+ The above copyright notice and this permission notice shall be
56
+ included in all copies or substantial portions of the Software.
57
+
58
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
59
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
60
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
61
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
62
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
63
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
64
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
65
+
66
+ [*CMS]: Content Management System
67
+ [*CSS]: Cascading Style Sheets
68
+ [*HTML]: HyperText Markup Language
69
+
70
+ [semi-static]: http://github.com/zzot/semi-static
71
+ [jekyll]: http://github.com/mojombo/jekyll
72
+ [git]: http://git-scm.com/
73
+ [github]: http://github.com/
74
+ [pages]: http://pages.github.com
75
+ [rails]: http://rubyonrails.org/
76
+ [projects]: http://github.com/zzot
77
+ [haml]: http://haml.hamptoncatlin.com/
78
+ [sass]: http://haml.hamptoncatlin.com/
79
+ [erb]: http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/
80
+ [liquid]: http://www.liquidmarkup.org/
81
+ [maruku]: http://maruku.rubyforge.org/
82
+ [pygments]: http://pygments.org/
83
+ [rubygems]: http://www.rubygems.org/
84
+ [ruby]: http://ruby-lang.org/
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 0
3
+ :patch: 1
4
+ :major: 0
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'semi-static')
4
+ require 'semi-static/cli'
5
+
6
+ SemiStatic::CLI.run
@@ -0,0 +1,30 @@
1
+ # Core requirements
2
+ require 'erb'
3
+ require 'tempfile'
4
+ require 'yaml'
5
+
6
+ # Gem requirements
7
+ require 'rubygems'
8
+ require 'haml'
9
+ require 'sass'
10
+ require 'maruku'
11
+
12
+ $LOAD_PATH.unshift File.dirname(__FILE__)
13
+
14
+ # Core extensions
15
+ require 'semi-static/core_ext/hash'
16
+
17
+ # My classes and modules
18
+ require 'semi-static/base'
19
+ require 'semi-static/pygmentize'
20
+ require 'semi-static/convertable'
21
+ require 'semi-static/layout'
22
+ require 'semi-static/stylesheet'
23
+ require 'semi-static/page'
24
+ require 'semi-static/post'
25
+ require 'semi-static/snippet'
26
+ require 'semi-static/posts'
27
+ require 'semi-static/index'
28
+ require 'semi-static/categories'
29
+ require 'semi-static/statistics'
30
+ require 'semi-static/site'
@@ -0,0 +1,74 @@
1
+ module SemiStatic
2
+ # Base represents a single source file and its associated metadata.
3
+ # Base loads the source file, and strips and parses any metadata set
4
+ # in the file's header. It stores an absolute path to the file so we
5
+ # can refer to it later.
6
+ class Base
7
+ ##
8
+ # The associated Site object
9
+ attr_reader :site
10
+
11
+ ##
12
+ # The relative path to the source file
13
+ attr_reader :source_path
14
+
15
+ ##
16
+ # The source file's extension
17
+ attr_reader :source_ext
18
+
19
+ ##
20
+ # The source file's contents
21
+ attr_reader :source_content
22
+
23
+ ##
24
+ # The metadata found in the file's header
25
+ attr_reader :source_metadata
26
+
27
+ ##
28
+ # The absolute path to the source file
29
+ attr_reader :full_source_path
30
+
31
+ ##
32
+ # Initializes a new Base
33
+ #
34
+ # +site+:: The Site object we belong to
35
+ # +path+:: The relative path to the source file
36
+ def initialize(site, path)
37
+ @site = site
38
+ @source_path = path
39
+ @source_ext = File.extname(source_path)
40
+ @full_source_path = File.expand_path(source_path)
41
+ @metadata = []
42
+
43
+ load
44
+ end
45
+
46
+ ##
47
+ # Get the modification time of the source file.
48
+ def source_mtime
49
+ File.mtime(@full_source_path)
50
+ end
51
+
52
+ protected
53
+ ##
54
+ # Read the source file and parse any metadata in the file header.
55
+ def load
56
+ contents = File.read File.join(full_source_path)
57
+ if contents =~ /^(---\s*\n.*?)\n---\s*\n/m
58
+ @source_content, @source_metadata = contents[($1.size + 5) .. -1], YAML.load($1)
59
+ else
60
+ @source_content, @source_metadata = contents, {}
61
+ end
62
+ @source_metadata.symbolize_keys
63
+ end
64
+
65
+ def method_missing(method, *args) #:nodoc:
66
+ name = method.to_sym
67
+ if @metadata.include?(name)
68
+ return source_metadata[name]
69
+ else
70
+ super
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,74 @@
1
+ module SemiStatic
2
+ class Categories < Hash
3
+ def self.slugize(name)
4
+ name.to_s.gsub(/ /, '-').downcase.to_sym
5
+ end
6
+
7
+ class Category < Array
8
+ attr_reader :slug, :name
9
+
10
+ def initialize(name, slug)
11
+ @name, @slug = name, slug
12
+ end
13
+ end
14
+
15
+ # def initialize
16
+ # super { |hash,key| hash[key] = Category.new(key) }
17
+ # end
18
+
19
+ def [](name)
20
+ slug = Categories.slugize name
21
+ category = super(slug)
22
+ if category.nil?
23
+ category = Category.new name.to_s, slug
24
+ self[slug] = category
25
+ end
26
+ return category
27
+ end
28
+
29
+ def slugs
30
+ keys.sort { |l,r| l.to_s <=> r.to_s }
31
+ end
32
+
33
+ def names
34
+ values.collect { |c| c.name }.sort { |l,r| l.to_s <=> r.to_s }
35
+ end
36
+
37
+ def nested_values
38
+ result = Hash.new { |hash,key| hash[key] = Hash.new }
39
+ values.each do |item|
40
+ parent, child = item.slug.to_s.split '/'
41
+ if child.nil?
42
+ result[parent.to_sym][nil] = item
43
+ else
44
+ result[parent.to_sym][child.to_sym] = item
45
+ end
46
+ end
47
+
48
+ result.collect do |parent_slug,items|
49
+ parent = items.delete(nil)
50
+ children = items.values.sort { |l,r| l.name.casecmp r.name }
51
+
52
+ class << parent
53
+ attr_accessor :slug, :children
54
+ end
55
+ parent.children = children
56
+ parent
57
+ end.sort { |l,r| l.name.casecmp r.name }
58
+ end
59
+
60
+ def each(options={}, &block)
61
+ list = case options[:order]
62
+ when :name, nil
63
+ values.sort { |l,r| l.name.casecmp r.name }
64
+ when :count
65
+ values.sort { |l,r| r.count <=> l.count }
66
+ when :tree
67
+ nested_values
68
+ else
69
+ raise ArgumentError, "Unknown order: #{options[:order]}"
70
+ end
71
+ list.each(&block)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,126 @@
1
+ require 'optparse'
2
+ require 'webrick'
3
+ require 'directory_watcher'
4
+
5
+ module SemiStatic
6
+ class CLI
7
+ include WEBrick
8
+
9
+ attr_accessor :source_dir, :output_dir
10
+ attr_accessor :clean_first
11
+ attr_accessor :start_server, :server_port
12
+ attr_accessor :show_statistics
13
+ attr_accessor :use_pygments
14
+ attr_accessor :quick_mode
15
+ attr_accessor :check_mtime
16
+ attr_accessor :start_updater
17
+
18
+ def initialize
19
+ self.clean_first = false
20
+ self.start_server = false
21
+ self.show_statistics = false
22
+ self.use_pygments = true
23
+ self.quick_mode = false
24
+ self.check_mtime = true
25
+ self.start_updater = false
26
+ end
27
+
28
+ def self.run
29
+ cli = CLI.new
30
+ opts = OptionParser.new do |opts|
31
+ opts.banner = 'Usage: semi [<options>] [[<source path>] <output path>]'
32
+ opts.separator ''
33
+ opts.separator 'Options:'
34
+
35
+ opts.on('-s', '--server [PORT]', Integer,
36
+ 'Start a web server (on port PORT)') do |port|
37
+ cli.start_server = true
38
+ cli.server_port = port || 4000
39
+ end
40
+
41
+ opts.on('-a', '--[no-]auto-update', 'Automatically update generated files') do |a|
42
+ cli.start_updater = a
43
+ end
44
+
45
+ opts.on('-c', '--[no-]clean', 'Clean the output dir first') do |d|
46
+ cli.clean_first = d
47
+ end
48
+
49
+ opts.on('-t', '--[no-]stats', 'Display conversion statistics') do |t|
50
+ cli.show_statistics = t
51
+ end
52
+
53
+ opts.on('-p', '--[no-]pygments', 'Use Pygments for code highlighting') do |p|
54
+ cli.use_pygments = p
55
+ end
56
+
57
+ opts.on('-q', '--[no-]quick-mode', 'Only convert a few posts (for testing)') do |q|
58
+ cli.quick_mode = q
59
+ end
60
+
61
+ opts.on('-m', '--[no-]mtime', 'Skip files that appear to be up-to-date') do |m|
62
+ cli.check_mtime = m
63
+ end
64
+
65
+ opts.on_tail('-h', '--help', 'Show this message') do
66
+ puts opts
67
+ exit
68
+ end
69
+
70
+ opts.on_tail('-V', '--version', 'Show version number') do
71
+ path = File.join File.dirname(__FILE__), '..', '..', 'VERSION.yml'
72
+ version = File.open(path) { |io| YAML.load io }
73
+ puts "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
74
+ end
75
+ end
76
+ opts.parse!
77
+
78
+ case ARGV.size
79
+ when 0
80
+ cli.source_dir = '.'
81
+ cli.output_dir = 'site'
82
+ when 1
83
+ cli.source_dir = '.'
84
+ cli.output_dir = ARGV[0]
85
+ when 2
86
+ cli.source_dir = ARGV[0]
87
+ cli.output_dir = ARGV[1]
88
+ else
89
+ puts 'Invalid options. Run `semi --help` for assistance.'
90
+ exit 1
91
+ end
92
+
93
+ cli.run
94
+ end
95
+
96
+ def run
97
+ Pygmentize.enabled = use_pygments
98
+ SemiStatic::Site.open(source_dir) do |site|
99
+ site.clean_first = clean_first
100
+ site.show_statistics = show_statistics
101
+ site.quick_mode = quick_mode
102
+ site.check_mtime = check_mtime
103
+
104
+ site.output output_dir
105
+ if start_updater
106
+ updater = DirectoryWatcher.new(source_dir)
107
+ updater.interval = 1
108
+ updater.glob = '{indices,layouts,pages,posts,snippets,stylesheets}/**/*'
109
+ updater.add_observer do |*args|
110
+ puts "[#{Time.now}] #{args.length} files changed."
111
+ site.output output_dir
112
+ end
113
+ updater.start
114
+ sleep 0 unless start_server
115
+ end
116
+ if start_server
117
+ server = HTTPServer.new :DocumentRoot => output_dir,
118
+ :Port => server_port
119
+ thread = Thread.new { server.start }
120
+ trap('INT') { server.shutdown }
121
+ thread.join
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -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