serum 0.1.0

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