serum 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Gemfile +2 -0
  2. data/LICENSE +23 -0
  3. data/README.md +55 -0
  4. data/Rakefile +10 -0
  5. data/lib/serum.rb +71 -0
  6. data/lib/serum/core_ext.rb +68 -0
  7. data/lib/serum/errors.rb +4 -0
  8. data/lib/serum/mime.types +84 -0
  9. data/lib/serum/post.rb +177 -0
  10. data/lib/serum/site.rb +116 -0
  11. data/lib/serum/static_file.rb +53 -0
  12. data/serum.gemspec +85 -0
  13. data/test/helper.rb +26 -0
  14. data/test/source/2008-02-02-not-published.textile +8 -0
  15. data/test/source/2008-02-02-published.textile +8 -0
  16. data/test/source/2008-10-18-foo-bar.textile +8 -0
  17. data/test/source/2008-11-21-complex.textile +8 -0
  18. data/test/source/2008-12-03-permalinked-post.textile +9 -0
  19. data/test/source/2008-12-13-include.markdown +8 -0
  20. data/test/source/2009-01-27-array-categories.textile +10 -0
  21. data/test/source/2009-01-27-categories.textile +7 -0
  22. data/test/source/2009-01-27-category.textile +7 -0
  23. data/test/source/2009-01-27-empty-categories.textile +7 -0
  24. data/test/source/2009-01-27-empty-category.textile +7 -0
  25. data/test/source/2009-03-12-hash-#1.markdown +6 -0
  26. data/test/source/2009-05-18-empty-tag.textile +6 -0
  27. data/test/source/2009-05-18-empty-tags.textile +6 -0
  28. data/test/source/2009-05-18-tag.textile +6 -0
  29. data/test/source/2009-05-18-tags.textile +9 -0
  30. data/test/source/2009-05-24-yaml-linebreak.markdown +7 -0
  31. data/test/source/2009-06-22-empty-yaml.textile +3 -0
  32. data/test/source/2009-06-22-no-yaml.textile +1 -0
  33. data/test/source/2010-01-08-triple-dash.markdown +6 -0
  34. data/test/source/2010-01-09-date-override.textile +7 -0
  35. data/test/source/2010-01-09-time-override.textile +7 -0
  36. data/test/source/2010-01-09-timezone-override.textile +7 -0
  37. data/test/source/2010-01-16-override-data.textile +4 -0
  38. data/test/source/2011-04-12-md-extension.md +7 -0
  39. data/test/source/2011-04-12-text-extension.text +0 -0
  40. data/test/source/2013-01-02-post-excerpt.markdown +14 -0
  41. data/test/source/2013-01-12-nil-layout.textile +6 -0
  42. data/test/source/2013-01-12-no-layout.textile +5 -0
  43. data/test/suite.rb +11 -0
  44. data/test/test_core_ext.rb +88 -0
  45. data/test/test_post.rb +138 -0
  46. data/test/test_site.rb +68 -0
  47. metadata +194 -0
@@ -0,0 +1,116 @@
1
+ module Serum
2
+ class Site
3
+ attr_accessor :config, :posts, :static_files, :exclude, :include, :source
4
+ attr_accessor :time, :baseurl
5
+
6
+ # Public: Initialize a new Site.
7
+ #
8
+ # config - A Hash containing site configuration details.
9
+ def initialize(config)
10
+ self.config = config.clone
11
+
12
+ self.source = File.expand_path(config['source'])
13
+ self.baseurl = config['baseurl']
14
+ self.exclude = config['exclude'] || []
15
+ self.include = config['include'] || []
16
+
17
+ self.reset
18
+ self.read_directories
19
+ end
20
+
21
+ # Reset Site details.
22
+ #
23
+ # Returns nothing
24
+ def reset
25
+ self.time = if self.config['time']
26
+ Time.parse(self.config['time'].to_s)
27
+ else
28
+ Time.now
29
+ end
30
+ self.posts = []
31
+ self.static_files = []
32
+ end
33
+
34
+ # Recursively traverse directories to find posts and static files
35
+ # that will become part of the site according to the rules in
36
+ # filter_entries.
37
+ #
38
+ # dir - The String relative path of the directory to read. Default: ''.
39
+ #
40
+ # Returns nothing.
41
+ def read_directories(dir = '')
42
+ self.reset
43
+ base = File.join(self.source, dir)
44
+ entries = Dir.chdir(base) { filter_entries(Dir.entries('.')) }
45
+
46
+ self.read_posts(dir)
47
+ self.posts.sort!
48
+
49
+ entries.each do |f|
50
+ f_abs = File.join(base, f)
51
+ f_rel = File.join(dir, f)
52
+ if File.directory?(f_abs)
53
+ read_directories(f_rel)
54
+ elsif !File.symlink?(f_abs)
55
+ static_files << StaticFile.new(self, self.source, dir, f)
56
+ end
57
+ end
58
+ end
59
+
60
+ # Read all the files in <source>/<dir> and create a new Post
61
+ # object with each one.
62
+ #
63
+ # dir - The String relative path of the directory to read.
64
+ #
65
+ # Returns nothing.
66
+ def read_posts(dir)
67
+ entries = get_entries(dir, '')
68
+
69
+ # first pass processes, but does not yet render post content
70
+ entries.each do |f|
71
+ if Post.valid?(f)
72
+ post = Post.new(self, self.source, dir, f)
73
+
74
+ if post.published && post.date <= self.time
75
+ self.posts << post
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ # Filter out any files/directories that are hidden or backup files (start
82
+ # with "." or "#" or end with "~"), or contain site content (start with "_"),
83
+ # or are excluded in the site configuration, unless they are web server
84
+ # files such as '.htaccess'.
85
+ #
86
+ # entries - The Array of String file/directory entries to filter.
87
+ #
88
+ # Returns the Array of filtered entries.
89
+ def filter_entries(entries)
90
+ entries.reject do |e|
91
+ unless self.include.glob_include?(e)
92
+ ['.', '_', '#'].include?(e[0..0]) ||
93
+ e[-1..-1] == '~' ||
94
+ self.exclude.glob_include?(e) ||
95
+ (File.symlink?(e) && self.safe)
96
+ end
97
+ end
98
+ end
99
+
100
+ # Read the entries from a particular directory for processing
101
+ #
102
+ # dir - The String relative path of the directory to read
103
+ # subfolder - The String directory to read
104
+ #
105
+ # Returns the list of entries to process
106
+ def get_entries(dir, subfolder)
107
+ base = File.join(self.source, dir, subfolder)
108
+ return [] unless File.exists?(base)
109
+ entries = Dir.chdir(base) { filter_entries(Dir['**/*']) }
110
+ end
111
+
112
+ def inspect
113
+ "<Site: #{source}>"
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,53 @@
1
+ module Serum
2
+ class StaticFile
3
+ # The cache of last modification times [path] -> mtime.
4
+ @@mtimes = Hash.new
5
+
6
+ # Initialize a new StaticFile.
7
+ #
8
+ # site - The Site.
9
+ # base - The String path to the <source>.
10
+ # dir - The String path between <source> and the file.
11
+ # name - The String filename of the file.
12
+ def initialize(site, base, dir, name)
13
+ @site = site
14
+ @base = base
15
+ @dir = dir
16
+ @name = name
17
+ end
18
+
19
+ # Returns source file path.
20
+ def path
21
+ File.join(@base, @dir, @name)
22
+ end
23
+
24
+ # Obtain destination path.
25
+ #
26
+ # dest - The String path to the destination dir.
27
+ #
28
+ # Returns destination file path.
29
+ def destination(dest)
30
+ File.join(dest, @dir, @name)
31
+ end
32
+
33
+ # Returns last modification time for this file.
34
+ def mtime
35
+ File.stat(path).mtime.to_i
36
+ end
37
+
38
+ # Is source path modified?
39
+ #
40
+ # Returns true if modified since last write.
41
+ def modified?
42
+ @@mtimes[path] != mtime
43
+ end
44
+
45
+ # Reset the mtimes cache (for testing purposes).
46
+ #
47
+ # Returns nothing.
48
+ def self.reset_cache
49
+ @@mtimes = Hash.new
50
+ nil
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,85 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+ s.rubygems_version = '1.3.5'
5
+
6
+ s.name = 'serum'
7
+ s.version = '0.1.0'
8
+ s.license = 'MIT'
9
+ s.date = '2013-03-30'
10
+ s.rubyforge_project = 'serum'
11
+
12
+ s.summary = "A simple object model on static posts with YAML front matter."
13
+ s.description = "Serum is a simple object model on static posts with YAML front matter."
14
+
15
+ s.authors = ["Brad Fults"]
16
+ s.email = 'bfults@gmail.com'
17
+ s.homepage = 'http://github.com/h3h/serum'
18
+
19
+ s.require_paths = %w[lib]
20
+
21
+ s.executables = []
22
+
23
+ s.rdoc_options = ["--charset=UTF-8"]
24
+ s.extra_rdoc_files = %w[README.md LICENSE]
25
+
26
+ s.add_runtime_dependency('safe_yaml', "~> 0.7.0")
27
+
28
+ s.add_development_dependency('rake', "~> 10.0.3")
29
+ s.add_development_dependency('rdoc', "~> 3.11")
30
+ s.add_development_dependency('redgreen', "~> 1.2")
31
+ s.add_development_dependency('shoulda', "~> 3.3.2")
32
+ s.add_development_dependency('rr', "~> 1.0")
33
+
34
+ # = MANIFEST =
35
+ s.files = %w[
36
+ Gemfile
37
+ LICENSE
38
+ Rakefile
39
+ lib/serum.rb
40
+ lib/serum/core_ext.rb
41
+ lib/serum/errors.rb
42
+ lib/serum/mime.types
43
+ lib/serum/post.rb
44
+ lib/serum/site.rb
45
+ lib/serum/static_file.rb
46
+ serum.gemspec
47
+ test/helper.rb
48
+ test/source/2008-02-02-not-published.textile
49
+ test/source/2008-02-02-published.textile
50
+ test/source/2008-10-18-foo-bar.textile
51
+ test/source/2008-11-21-complex.textile
52
+ test/source/2008-12-03-permalinked-post.textile
53
+ test/source/2008-12-13-include.markdown
54
+ test/source/2009-01-27-array-categories.textile
55
+ test/source/2009-01-27-categories.textile
56
+ test/source/2009-01-27-category.textile
57
+ test/source/2009-01-27-empty-categories.textile
58
+ test/source/2009-01-27-empty-category.textile
59
+ test/source/2009-03-12-hash-#1.markdown
60
+ test/source/2009-05-18-empty-tag.textile
61
+ test/source/2009-05-18-empty-tags.textile
62
+ test/source/2009-05-18-tag.textile
63
+ test/source/2009-05-18-tags.textile
64
+ test/source/2009-05-24-yaml-linebreak.markdown
65
+ test/source/2009-06-22-empty-yaml.textile
66
+ test/source/2009-06-22-no-yaml.textile
67
+ test/source/2010-01-08-triple-dash.markdown
68
+ test/source/2010-01-09-date-override.textile
69
+ test/source/2010-01-09-time-override.textile
70
+ test/source/2010-01-09-timezone-override.textile
71
+ test/source/2010-01-16-override-data.textile
72
+ test/source/2011-04-12-md-extension.md
73
+ test/source/2011-04-12-text-extension.text
74
+ test/source/2013-01-02-post-excerpt.markdown
75
+ test/source/2013-01-12-nil-layout.textile
76
+ test/source/2013-01-12-no-layout.textile
77
+ test/suite.rb
78
+ test/test_core_ext.rb
79
+ test/test_post.rb
80
+ test/test_site.rb
81
+ ]
82
+ # = MANIFEST =
83
+
84
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
85
+ end
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ require 'serum'
5
+
6
+ require 'redgreen' if RUBY_VERSION < '1.9'
7
+ require 'shoulda'
8
+ require 'rr'
9
+
10
+ include Serum
11
+
12
+ # Send STDERR into the void to suppress program output messages
13
+ STDERR.reopen(test(?e, '/dev/null') ? '/dev/null' : 'NUL:')
14
+
15
+ class Test::Unit::TestCase
16
+ include RR::Adapters::TestUnit
17
+
18
+ def source_dir(*subdirs)
19
+ test_dir('source', *subdirs)
20
+ end
21
+
22
+ def test_dir(*subdirs)
23
+ File.join(File.dirname(__FILE__), *subdirs)
24
+ end
25
+
26
+ end
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Not published!
4
+ published: false
5
+ category: publish_test
6
+ ---
7
+
8
+ This should *not* be published!
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Publish
4
+ category: publish_test
5
+ ---
6
+
7
+ This should be published.
8
+
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Foo Bar
4
+ ---
5
+
6
+ h1. {{ page.title }}
7
+
8
+ Best *post* ever
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Complex
4
+ ---
5
+
6
+ url: {{ page.url }}
7
+ date: {{ page.date }}
8
+ id: {{ page.id }}
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Post with Permalink
3
+ permalink: my_category/permalinked-post
4
+ ---
5
+
6
+ h1. {{ page.title }}
7
+
8
+
9
+ <p>Best <strong>post</strong> ever</p>
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Include
4
+ ---
5
+
6
+ {% include sig.markdown %}
7
+
8
+ This _is_ cool
@@ -0,0 +1,10 @@
1
+ ---
2
+ layout: default
3
+ title: Array categories in YAML
4
+ categories:
5
+ - foo
6
+ - bar
7
+ - baz
8
+ ---
9
+
10
+ Best *post* ever
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: default
3
+ title: Categories in YAML
4
+ categories: foo bar baz
5
+ ---
6
+
7
+ Best *post* ever
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: default
3
+ title: Category in YAML
4
+ category: foo
5
+ ---
6
+
7
+ Best *post* ever
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: default
3
+ title: Category in YAML
4
+ categories:
5
+ ---
6
+
7
+ Best *post* ever
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: default
3
+ title: Category in YAML
4
+ category:
5
+ ---
6
+
7
+ Best *post* ever
@@ -0,0 +1,6 @@
1
+ ---
2
+ layout: default
3
+ title: Hash #1
4
+ ---
5
+
6
+ Hashes are nice
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: A Tag
3
+ tag:
4
+ ---
5
+
6
+ Whoa.
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: Some Tags
3
+ tags:
4
+ ---
5
+
6
+ Awesome!
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: A Tag
3
+ tag: code
4
+ ---
5
+
6
+ Whoa.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Some Tags
3
+ tags:
4
+ - food
5
+ - cooking
6
+ - pizza
7
+ ---
8
+
9
+ Awesome!
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: post
3
+ title: "Test title"
4
+ tag: "Ruby"
5
+ ---
6
+
7
+ This is the content
@@ -0,0 +1,3 @@
1
+ ---
2
+ ---
3
+ Empty YAML.