spandex 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/Gemfile +3 -0
- data/README.md +85 -0
- data/Rakefile +7 -0
- data/lib/spandex.rb +21 -0
- data/lib/spandex/finder.rb +92 -0
- data/lib/spandex/page.rb +131 -0
- data/lib/spandex/version.rb +3 -0
- data/spandex.gemspec +25 -0
- data/spec/finder_spec.rb +215 -0
- data/spec/page_spec.rb +80 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/wrapper_spec.rb +5 -0
- metadata +18 -6
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
#Spandex is a simple content engine for Ruby, made chiefly from orangutan fur#
|
2
|
+
|
3
|
+
Spandex manages a bucket of text files written in your favorite markup (Markdown, Textile, or even Haml). You mark those files up with metadata (like date and tags), and Spandex gives you access to them. This is perfect for building a simple git-based blog or content site.
|
4
|
+
|
5
|
+
Spandex is largely extracted from [Nesta](http://nestacms.com/), a Ruby CMS. The markup is rendered using [Tilt](http://github.com/rtomayko/tilt).
|
6
|
+
|
7
|
+
##It's super freaking easy##
|
8
|
+
|
9
|
+
Make a directory called "content".
|
10
|
+
|
11
|
+
In that directory, create a file called `things.md`:
|
12
|
+
|
13
|
+
```
|
14
|
+
Title: I have an affinity for goats
|
15
|
+
Date: 2011/9/25
|
16
|
+
Tags: gerrymandering, hyperbolic geometry
|
17
|
+
|
18
|
+
No, really goats are *awesome*.
|
19
|
+
```
|
20
|
+
|
21
|
+
Then use Spandex to do the work:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
spandex = Spandex.new(File.expand_path('content', File.dirname(__FILE__))
|
25
|
+
|
26
|
+
page = spandex.get('things')
|
27
|
+
page.title #=> "I have an affinity for goats"
|
28
|
+
page.tags #=> ["gerrymandering", "hyperbolic geometry"]
|
29
|
+
page.body #=> <p>No, really goats are <em>awesome</em>.</p>\n
|
30
|
+
|
31
|
+
spandex.all_pages #=> all pages under content
|
32
|
+
spandex.all_articles #=> all pages with dates (e.g. blog posts)
|
33
|
+
spandex.tags #=> ["gerrymandering", "hyperbolic geometry"]
|
34
|
+
spandex.atom_feed #=> a bunch of XML, only finds posts with dates
|
35
|
+
|
36
|
+
spandex.find_articles(:tag => "gerrymandering")
|
37
|
+
```
|
38
|
+
|
39
|
+
The spandex object caches the pages and does all of the right last-mod-time checking. Your application should just keep the object around.
|
40
|
+
|
41
|
+
##Build things!##
|
42
|
+
|
43
|
+
Blog engines are great, and there are, like, three thousand of them. Sometimes, though, you just want to build a website with the tools you know. Spandex lets you do that while taking care of all the grody work of keeping track of posts. A barebones example of a Spandex-based blog is the [tinyblogofdoom](http://github.com/icambron/tinyblogofdoom).
|
44
|
+
|
45
|
+
But wait! There's more! Spandex can also implement the core functionality for a blog engine or CMS. Spandex brings the post rendering and you bring the themes, UI chrome, and plugins.
|
46
|
+
|
47
|
+
##Some more cool stuff##
|
48
|
+
|
49
|
+
Paths can be deep:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
page = spandex.get('subfolder/deeper/things')
|
53
|
+
```
|
54
|
+
|
55
|
+
You can pass options through to Tilt at initialization:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
spandex = Spandex.new(path, :fenced_code_blocks => true) #special option for Redcarpet
|
59
|
+
```
|
60
|
+
|
61
|
+
##The content is just Tilt##
|
62
|
+
|
63
|
+
The markup is processed using [Tilt](https://github.com/rtomayko/tilt). That means it can read a lot of different markup formats, and gives you access to all of Tilt's configuration options. You can change what extensions get bound to what template engines, and that sort of thing
|
64
|
+
|
65
|
+
You can also customize rendering by customizing Tilt. Here's how you might customize Spandex to hightlight code with Pygments:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
require 'redcarpet'
|
69
|
+
require 'pygments'
|
70
|
+
|
71
|
+
class Syntactical < Redcarpet::Render::HTML
|
72
|
+
include Pygments
|
73
|
+
def block_code(code, language)
|
74
|
+
highlight code, :lexer => lexer_name_for(:lexer => language)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class SyntacticalTemplate < Tilt::RedcarpetTemplate::Redcarpet2
|
79
|
+
def generate_renderer
|
80
|
+
Syntactical
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Tilt::register SyntacticalTemplate, 'some_file_extension'
|
85
|
+
```
|
data/Rakefile
ADDED
data/lib/spandex.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spandex/page'
|
2
|
+
require 'spandex/finder'
|
3
|
+
|
4
|
+
module Spandex
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def new(base_path, render_options = {})
|
8
|
+
Spandex::Finder.new(base_path, render_options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(method, *args, &block)
|
12
|
+
return super unless new.respond_to?(method)
|
13
|
+
new.send(method, *args, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to?(method, include_private = false)
|
17
|
+
new.respond_to?(method, include_private) || super(method, include_private)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'atom'
|
3
|
+
|
4
|
+
module Spandex
|
5
|
+
class Finder
|
6
|
+
|
7
|
+
class CaseInsensitiveHash < Hash
|
8
|
+
def [](key)
|
9
|
+
super(key.to_s.downcase)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(base_dir, render_options = {})
|
14
|
+
@base_dir = base_dir
|
15
|
+
@render_options = render_options
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(path)
|
19
|
+
load(Page.file_from_path(path, @base_dir), path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_by_filename(filename)
|
23
|
+
load(filename)
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_pages
|
27
|
+
roots = []
|
28
|
+
@pages ||= CaseInsensitiveHash.new
|
29
|
+
Dir.glob(File.join(@base_dir, "**/*"))
|
30
|
+
.map{|path| Pathname.new(path)}
|
31
|
+
.select{|path| Page.registered?(path)}
|
32
|
+
.each{|path| load(path)}
|
33
|
+
@pages.values
|
34
|
+
end
|
35
|
+
|
36
|
+
def all_articles
|
37
|
+
all_pages
|
38
|
+
.select{|page| page.date}
|
39
|
+
.sort {|x, y| y.date <=> x.date }
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_articles(conditions)
|
43
|
+
find_inside(all_articles, conditions)
|
44
|
+
end
|
45
|
+
|
46
|
+
def tags
|
47
|
+
@tags ||= all_pages.map{|p| p.tags}.flatten.uniq
|
48
|
+
end
|
49
|
+
|
50
|
+
def atom_feed(count, author, root, path_to_xml)
|
51
|
+
articles = all_articles.take(count)
|
52
|
+
Atom::Feed.new do |f|
|
53
|
+
f.id = root
|
54
|
+
f.links << Atom::Link.new(:href => "http://#{root}#{path_to_xml}", :rel => "self")
|
55
|
+
f.links << Atom::Link.new(:href => "http://#{root}", :rel => "alternate")
|
56
|
+
f.authors << Atom::Person.new(:name => author)
|
57
|
+
f.updated = articles[0].date if articles[0]
|
58
|
+
articles.each do |post|
|
59
|
+
f.entries << post.to_atom_entry(root)
|
60
|
+
end
|
61
|
+
end.to_xml
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def load(filename, key = Page.path_from_file(filename, @base_dir))
|
67
|
+
return nil unless filename && key && File.exists?(filename)
|
68
|
+
if @pages && @pages[key] && File.mtime(filename) < @pages[key].mtime
|
69
|
+
@pages[key]
|
70
|
+
else
|
71
|
+
@pages ||= CaseInsensitiveHash.new
|
72
|
+
page = Page.from_filename(filename, @base_dir, @render_options)
|
73
|
+
@pages[key] = page
|
74
|
+
page
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_inside(xs, conditions = {})
|
79
|
+
output = xs
|
80
|
+
conditions.each do |k, v|
|
81
|
+
next unless v
|
82
|
+
cond = case k
|
83
|
+
when :tag then lambda {|p| p.tags.include?(v) }
|
84
|
+
else lambda{|p| true}
|
85
|
+
end
|
86
|
+
output = output.select(&cond)
|
87
|
+
end
|
88
|
+
output
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
data/lib/spandex/page.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'pathname'
|
3
|
+
require 'tilt'
|
4
|
+
require 'atom'
|
5
|
+
|
6
|
+
module Spandex
|
7
|
+
class Page
|
8
|
+
attr_reader :path, :mtime, :extension, :render_options
|
9
|
+
|
10
|
+
def self.from_path(path, base_path, render_options = {})
|
11
|
+
path = fix_path path
|
12
|
+
filename = file_from_path(path, base_path)
|
13
|
+
if filename
|
14
|
+
metadata, content = parse_file(filename)
|
15
|
+
Page.new(path, content, filename.extname, metadata, render_options)
|
16
|
+
else nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.from_filename(filename, base_path, render_options = {})
|
21
|
+
pathname = Pathname.new(filename)
|
22
|
+
return nil unless pathname.exist?
|
23
|
+
|
24
|
+
path = path_from_file(pathname, base_path)
|
25
|
+
metadata, content = parse_file(filename)
|
26
|
+
|
27
|
+
Page.new(path, content, pathname.extname, metadata, render_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.mtime(path, base_path)
|
31
|
+
file = file_from_path(path, base_path)
|
32
|
+
if File.exists?(path)
|
33
|
+
File.mtime(file)
|
34
|
+
else nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.file_from_path(path, base_path)
|
39
|
+
path = fix_path path
|
40
|
+
paths = Pathname.glob(File.join(base_path, "#{path}.*"))
|
41
|
+
pathname = paths.select{|path| registered?(path)}.first
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.path_from_file(pathname, base_path)
|
45
|
+
pathname = pathify(pathname)
|
46
|
+
pathname.relative_path_from(pathify(base_path)).sub_ext('')
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.registered?(pathname)
|
50
|
+
pathname = pathify(pathname)
|
51
|
+
Tilt.registered?(pathname.extname.sub(/^./, ''))
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def title
|
56
|
+
metadata("title") || "(Unknown Title)"
|
57
|
+
end
|
58
|
+
|
59
|
+
def date
|
60
|
+
@date ||= metadata("date") ? DateTime.parse(metadata("date")) : nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def body
|
64
|
+
@rendered_body ||= Tilt[@extension].new(nil, 1, @render_options){@content}.render
|
65
|
+
end
|
66
|
+
|
67
|
+
def tags
|
68
|
+
@tags ||= metadata("tags", "categories") ? metadata("tags", "categories").split(",").map{|tag| tag.strip} : []
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_atom_entry(root)
|
72
|
+
unless date
|
73
|
+
raise "Must have a date"
|
74
|
+
end
|
75
|
+
|
76
|
+
Atom::Entry.new do |entry|
|
77
|
+
entry.title = title
|
78
|
+
entry.updated = date
|
79
|
+
entry.id = "#{root},#{@path}"
|
80
|
+
entry.links << Atom::Link.new(:href => "http://#{root}/#{@path}")
|
81
|
+
entry.content = Atom::Content::Html.new(body)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def initialize(path, content, extension, metadata, render_options = {})
|
88
|
+
@path = path
|
89
|
+
@content = content
|
90
|
+
@metadata = metadata
|
91
|
+
@extension = extension.sub(/^./, '')
|
92
|
+
@mtime = Time.now
|
93
|
+
@render_options = render_options
|
94
|
+
end
|
95
|
+
|
96
|
+
def metadata(*keys)
|
97
|
+
keys.each do |key|
|
98
|
+
return @metadata[key] if @metadata.has_key? key
|
99
|
+
end
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.pathify(path_or_string)
|
104
|
+
path_or_string.is_a?(String) ? Pathname.new(path_or_string) : path_or_string
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.parse_file(filename)
|
108
|
+
def self.metadata?(text)
|
109
|
+
text.split("\n").first =~ /^[\w ]+:/
|
110
|
+
end
|
111
|
+
|
112
|
+
contents = File.open(filename).read
|
113
|
+
|
114
|
+
first_paragraph, remaining = contents.split(/\r?\n\r?\n/, 2)
|
115
|
+
metadata = {}
|
116
|
+
if metadata?(first_paragraph)
|
117
|
+
first_paragraph.split("\n").each do |line|
|
118
|
+
key, value = line.split(/\s*:\s*/, 2)
|
119
|
+
metadata[key.downcase] = value.chomp
|
120
|
+
end
|
121
|
+
end
|
122
|
+
markup = metadata?(first_paragraph) ? remaining : contents
|
123
|
+
return metadata, markup
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.fix_path(path)
|
127
|
+
path.sub(/\/$/, '')
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
data/spandex.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "spandex/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "spandex"
|
7
|
+
s.version = Spandex::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Isaac Cambron"]
|
10
|
+
s.email = ["icambron@alum.mit.edu"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = "A simple content engine"
|
13
|
+
s.description = "Spandex manages a store of markup files and their metadata, useful in building blogs or blog engines"
|
14
|
+
|
15
|
+
s.add_dependency "tilt"
|
16
|
+
s.add_dependency "ratom"
|
17
|
+
|
18
|
+
s.add_development_dependency "rspec"
|
19
|
+
s.add_development_dependency "redcarpet", "2.0.0b5"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
end
|
data/spec/finder_spec.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
require 'spandex'
|
3
|
+
require 'atom'
|
4
|
+
|
5
|
+
describe Spandex::Finder do
|
6
|
+
include PageFactory
|
7
|
+
include TempFileHelper
|
8
|
+
|
9
|
+
context "when getting all pages" do
|
10
|
+
|
11
|
+
it "can find files" do
|
12
|
+
create_file("lasers.md", "You know it's 18 when you throw your dice.")
|
13
|
+
create_file("cheese.textile", "You've climbed that mountain not only once but twice.")
|
14
|
+
make_finder.all_pages.size.should == 2
|
15
|
+
end
|
16
|
+
|
17
|
+
it "won't find repeated files roots" do
|
18
|
+
create_file("stuff.md", "Some say you're a loner but I know your kind.")
|
19
|
+
create_file("stuff.textile", "It's sad to see that's why I fake that I'm blind.")
|
20
|
+
|
21
|
+
make_finder.all_pages.size.should == 1
|
22
|
+
end
|
23
|
+
|
24
|
+
it "finds thing with populated attributes" do
|
25
|
+
create_file("stuff.md", "Some say you're a loner but I know your kind.", :title => "The")
|
26
|
+
create_file("more_stuff.textile", "It's sad to see that's why I fake that I'm blind.", :title => "Sounds")
|
27
|
+
|
28
|
+
make_finder.all_pages.map{|p| p.title}.should == ["The", "Sounds"]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when getting all articles" do
|
34
|
+
|
35
|
+
it "works fine when there are no articles" do
|
36
|
+
make_finder.all_articles.should be_empty
|
37
|
+
end
|
38
|
+
|
39
|
+
it "only finds pages with dates" do
|
40
|
+
create_file("stuff.md", "You don't float like butterfly or fight like Ali.", :date => "2011/5/25")
|
41
|
+
create_file("more_stuff.md", "Dress like Prince but to the lowest degree.")
|
42
|
+
|
43
|
+
results = make_finder.all_articles
|
44
|
+
results.size.should == 1
|
45
|
+
results[0].date.should == Date.civil(2011, 5, 25)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "sorts by date descending" do
|
49
|
+
create_file("stuff.md", "I like that you can't slow down.", :date => "1986/5/25")
|
50
|
+
create_file("more_stuff.md", "Step back! 'Cause you ain't no one.", :date => "1982/5/25")
|
51
|
+
create_file("even_more_stuff.md", "You're living in a lie.", :date => "2011/5/25")
|
52
|
+
|
53
|
+
results = make_finder.all_articles
|
54
|
+
|
55
|
+
results.size.should == 3
|
56
|
+
[Date.civil(2011,5,25), Date.civil(1986,5,25), Date.civil(1982,5,25)].each_with_index do |date, i|
|
57
|
+
results[i].date.should == date
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "ignores stray files" do
|
62
|
+
create_file("stuff.md~", "I like that you can't slow down.", :date => "1986/5/25")
|
63
|
+
make_finder.all_articles.should be_empty
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when generating an atom feed" do
|
69
|
+
|
70
|
+
it "generates real atom content" do
|
71
|
+
create_file("stuff.md", "You ain't nothing but a BFI", :date => "1986/5/25", :title => "BFI")
|
72
|
+
create_file("more_stuff.md", "Leaving town on a two wheeler while your kids are home", :date => "1982/5/25")
|
73
|
+
|
74
|
+
#generate and then reparse
|
75
|
+
feed = make_finder.atom_feed(3, "The Sounds", "sounds.test.org", "articles.xml")
|
76
|
+
ratom = Atom::Feed.load_feed(feed)
|
77
|
+
|
78
|
+
ratom.entries.size.should == 2
|
79
|
+
ratom.authors.first.name.should == "The Sounds"
|
80
|
+
ratom.links.size.should == 2
|
81
|
+
|
82
|
+
e = ratom.entries.first
|
83
|
+
e.title.should == "BFI"
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should only show the top n articles" do
|
88
|
+
create_file("stuff.md", "Giving up your love and life for some silver chrome", :date => "1986/5/25")
|
89
|
+
create_file("more_stuff.md", "You're acting like a fool so take my advice.", :date => "1986/5/25")
|
90
|
+
create_file("even_more_stuff.md", "It's not so hard to keep your eyes on the price.", :date => "1986/5/25")
|
91
|
+
|
92
|
+
feed = make_finder.atom_feed(2, "The Sounds", "sounds.test.org", "articles.xml")
|
93
|
+
ratom = Atom::Feed.load_feed(feed)
|
94
|
+
|
95
|
+
ratom.entries.size.should == 2
|
96
|
+
end
|
97
|
+
|
98
|
+
it "only includes pages with dates" do
|
99
|
+
create_file("stuff.md", "You don't float like butterfly or fight like Ali.", :date => "2011/5/25")
|
100
|
+
create_file("more_stuff.md", "Dress like Prince but to the lowest degree.")
|
101
|
+
|
102
|
+
feed = make_finder.atom_feed(2, "The Sounds", "sounds.test.org", "articles.xml")
|
103
|
+
ratom = Atom::Feed.load_feed(feed)
|
104
|
+
|
105
|
+
ratom.entries.size.should == 1
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
context "when listing tags" do
|
111
|
+
|
112
|
+
it "can list tags" do
|
113
|
+
create_file("stuff.md", "Excuses are all I ever get from you", :tags => "Sweedish, New Wave")
|
114
|
+
create_file("more_stuff.md", "And we both know it's true", :tags => "Indie Rock")
|
115
|
+
|
116
|
+
tags = make_finder.tags
|
117
|
+
tags.should == ["Sweedish", "New Wave", "Indie Rock"]
|
118
|
+
end
|
119
|
+
|
120
|
+
it "has unique tags" do
|
121
|
+
create_file("stuff.md", "It's not me, it is you", :tags => "Sweedish, Indie Rock")
|
122
|
+
create_file("more_stuff.md", "And nothing matters at all", :tags => "Indie Rock")
|
123
|
+
|
124
|
+
tags = make_finder.tags
|
125
|
+
tags.should == ["Sweedish", "Indie Rock"]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "loading a specific page" do
|
130
|
+
|
131
|
+
it "does in fact load it" do
|
132
|
+
create_file("stuff.md", "It felt so right at the time")
|
133
|
+
page = make_finder.get("stuff")
|
134
|
+
page.should_not be_nil
|
135
|
+
end
|
136
|
+
|
137
|
+
it "finds the first file tilt knows" do
|
138
|
+
create_file("stuff.snoogledoobers", "But we lost it all")
|
139
|
+
create_file("stuff.md", "You committed a crime")
|
140
|
+
page = make_finder.get("stuff")
|
141
|
+
page.extension.should == "md"
|
142
|
+
end
|
143
|
+
|
144
|
+
it "doesn't have to exist" do
|
145
|
+
page = make_finder.get("this/is/not/a/real/file")
|
146
|
+
page.should be_nil
|
147
|
+
end
|
148
|
+
|
149
|
+
it "caches individual files" do
|
150
|
+
finder = make_finder
|
151
|
+
|
152
|
+
create_file("stuff.md", "And did it matter to you", :tags => "yeah")
|
153
|
+
finder.get("stuff").tags.should == ["yeah"]
|
154
|
+
|
155
|
+
create_file("stuff.md", "Now you lost it all", :tags => "nah")
|
156
|
+
finder.get("stuff").tags.should == ["yeah"]
|
157
|
+
end
|
158
|
+
|
159
|
+
it "ignores trailing slashes" do
|
160
|
+
create_file("stuff.md", "Well, you're not so bright")
|
161
|
+
make_finder.get("stuff/").should_not be_nil
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
context "when loading by filename" do
|
167
|
+
it "does in fact load something" do
|
168
|
+
create_file("stuff.md", "You don't have to say you're sorry")
|
169
|
+
page = make_finder.get_by_filename(File.join(TEMP_DIR, "stuff.md"))
|
170
|
+
page.should_not be_nil
|
171
|
+
end
|
172
|
+
|
173
|
+
it "doesn't have to exist" do
|
174
|
+
page = make_finder.get_by_filename(File.join(TEMP_DIR, "this_is_not_a_file.md"))
|
175
|
+
page.should be_nil
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
context "when finding articles" do
|
181
|
+
it "can find them by tag" do
|
182
|
+
create_file("no.md", "You never mean it when you say you're sorry", :tags => "nono", :date => "2011/5/25")
|
183
|
+
create_file("yeah.md", "No guts, no glory, no time to worry", :tags => "yeahyeah", :date => "2011/5/26", :title => "Yeah Yeah Yeah")
|
184
|
+
|
185
|
+
|
186
|
+
results = make_finder.find_articles(:tag => "yeahyeah")
|
187
|
+
results.size.should == 1
|
188
|
+
results.first.title == "Yeah Yeah Yeah"
|
189
|
+
end
|
190
|
+
|
191
|
+
it "only finds articles" do
|
192
|
+
create_file("no.md", "No happy ending to your story", :tags => "yeahyeah")
|
193
|
+
create_file("yeah.md", "In moments like this, don't give a fuck about you", :tags => "yeahyeah", :date => "2011/5/26", :title => "Yeah Yeah Yeah")
|
194
|
+
|
195
|
+
|
196
|
+
results = make_finder.find_articles(:tag => "yeahyeah")
|
197
|
+
results.size.should == 1
|
198
|
+
results.first.title == "Yeah Yeah Yeah"
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
before(:each) do
|
204
|
+
create_temp_directory
|
205
|
+
end
|
206
|
+
|
207
|
+
after(:each) do
|
208
|
+
remove_temp_directory
|
209
|
+
end
|
210
|
+
|
211
|
+
def make_finder
|
212
|
+
Spandex::Finder.new(TEMP_DIR)
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
data/spec/page_spec.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
require 'spandex'
|
3
|
+
require 'atom'
|
4
|
+
require 'redcarpet'
|
5
|
+
|
6
|
+
describe Spandex::Page do
|
7
|
+
include PageFactory
|
8
|
+
include TempFileHelper
|
9
|
+
|
10
|
+
context "when parsing a file" do
|
11
|
+
it "determines the extension" do
|
12
|
+
page = create_page("test.md", "Oh my love we gotta save ourselves")
|
13
|
+
page.extension.should=="md"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can have a title" do
|
17
|
+
page = create_page("test.md", "If we wanna safe the world", :title => "The Best of Me")
|
18
|
+
page.title.should == "The Best of Me"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can have a tag" do
|
22
|
+
page = create_page("test.md", "Don't give in to your hate", :tags => "sappy")
|
23
|
+
page.tags.should have(1).tags
|
24
|
+
page.tags.should == ["sappy"]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can have multiple tags" do
|
28
|
+
page = create_page("test.md", "When you know you can change", :tags => "sappy,slightly trite")
|
29
|
+
page.tags.should == ['sappy', 'slightly trite']
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can have multiple tags with weird spacing" do
|
33
|
+
page = create_page("test.md", "It's hitting home when you feel so strange", :tags => " sappy , slightly trite ")
|
34
|
+
page.tags.should == ['sappy', 'slightly trite']
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can also have tags called 'categories'" do
|
38
|
+
page = create_page("test.md", "I wanna say those words", :categories => "sappy,slightly trite")
|
39
|
+
page.tags.should have(2).tags
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can parse the body" do
|
43
|
+
page = create_page("test.md", "But it's not that easy")
|
44
|
+
page.body.should == Redcarpet::Markdown.new(Redcarpet::Render::HTML).render("But it's not that easy")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "pass in rendering options" do
|
48
|
+
text = "```\nI smile but it doesn't make things right```"
|
49
|
+
create_file("test.md", text)
|
50
|
+
page = Spandex::Page.from_filename(File.join(TEMP_DIR, "test.md"), TEMP_DIR, :fenced_code_blocks => true)
|
51
|
+
page.render_options.should have_key(:fenced_code_blocks)
|
52
|
+
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, :fenced_code_blocks => true)
|
53
|
+
page.body.should == markdown.render(text)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "can have a date" do
|
57
|
+
page = create_page("test.md", "test_content", :date => "2011/5/25")
|
58
|
+
page.date.should == Date.civil(2011, 5, 25)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "can omit the date" do
|
62
|
+
page = create_page("test.md", "test_content")
|
63
|
+
page.date.should be_nil
|
64
|
+
end
|
65
|
+
|
66
|
+
it "produces good atom output" do
|
67
|
+
page = create_page("test.md", "test_content", :title => "hello!", :date => "2011/5/25")
|
68
|
+
entry = page.to_atom_entry("http://test.org")
|
69
|
+
entry.title.should == "hello!"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
before(:each) do
|
74
|
+
create_temp_directory
|
75
|
+
end
|
76
|
+
|
77
|
+
after(:each) do
|
78
|
+
remove_temp_directory
|
79
|
+
end
|
80
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module TempFileHelper
|
2
|
+
TEMP_DIR = File.expand_path('content', File.dirname(__FILE__))
|
3
|
+
|
4
|
+
def create_temp_directory
|
5
|
+
FileUtils.mkdir_p(TempFileHelper::TEMP_DIR)
|
6
|
+
end
|
7
|
+
|
8
|
+
def remove_temp_directory
|
9
|
+
FileUtils.rm_r(TempFileHelper::TEMP_DIR, :force => true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def temp_path(base)
|
13
|
+
File.join(TempFileHelper::TEMP_DIR, base)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module PageFactory
|
18
|
+
include TempFileHelper
|
19
|
+
|
20
|
+
def create_file(name, content, metadata = {})
|
21
|
+
full_name = File.join(TEMP_DIR, name)
|
22
|
+
metatext = metadata.map { |key, value| "#{key}: #{value}" }.join("\n")
|
23
|
+
contents =<<-EOF
|
24
|
+
#{metatext}
|
25
|
+
|
26
|
+
#{content}
|
27
|
+
EOF
|
28
|
+
File.open(full_name, 'w') { |file| file.write(contents) }
|
29
|
+
full_name
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_page(name, content, metadata = {})
|
33
|
+
full_name = create_file(name, content, metadata)
|
34
|
+
Spandex::Page.from_filename(full_name, TEMP_DIR)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: spandex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Isaac Cambron
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-09-
|
13
|
+
date: 2011-09-27 00:00:00 -04:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -52,9 +52,9 @@ dependencies:
|
|
52
52
|
requirement: &id004 !ruby/object:Gem::Requirement
|
53
53
|
none: false
|
54
54
|
requirements:
|
55
|
-
- - "
|
55
|
+
- - "="
|
56
56
|
- !ruby/object:Gem::Version
|
57
|
-
version:
|
57
|
+
version: 2.0.0b5
|
58
58
|
type: :development
|
59
59
|
version_requirements: *id004
|
60
60
|
description: Spandex manages a store of markup files and their metadata, useful in building blogs or blog engines
|
@@ -66,8 +66,20 @@ extensions: []
|
|
66
66
|
|
67
67
|
extra_rdoc_files: []
|
68
68
|
|
69
|
-
files:
|
70
|
-
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- Gemfile
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/spandex.rb
|
75
|
+
- lib/spandex/finder.rb
|
76
|
+
- lib/spandex/page.rb
|
77
|
+
- lib/spandex/version.rb
|
78
|
+
- spandex.gemspec
|
79
|
+
- spec/finder_spec.rb
|
80
|
+
- spec/page_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
- spec/wrapper_spec.rb
|
71
83
|
has_rdoc: true
|
72
84
|
homepage: ""
|
73
85
|
licenses: []
|