silly 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +3 -3
- data/Rakefile +1 -1
- data/lib/silly.rb +3 -2
- data/lib/silly/base_model.rb +6 -5
- data/lib/silly/data_model.rb +6 -5
- data/lib/silly/item.rb +3 -3
- data/lib/silly/page_model.rb +44 -23
- data/lib/silly/string_format.rb +51 -0
- data/lib/silly/url_slug.rb +157 -0
- data/lib/silly/version.rb +1 -1
- data/silly.gemspec +3 -3
- metadata +19 -25
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
Y2RiOTI2NmEyM2FlYmE2Y2U0NjhkOGYzZDAxZWQ1MzhiNjgxYjgxYQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NmU0ZmRlNzdjMWZmNTVhNjY5MWU1Y2QyOWM4MDhiNjQwZGVjM2IzYQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZGY0Y2I4MTI3NjVkNmU1OTdjNjM4ODkyNjFlZGRmMWFmMmNhOGNkYjUzMDYw
|
10
|
+
NjJjZTQ4MjU4ZDZiODgyNTc3YzI5MDdhMjM3MGIyYjYyYzg5MWQ1ODk1ZTVi
|
11
|
+
NzBiM2JkMjI3MDlkOWY0NDkwMGJmMjE1ODM2NWI2MGY4NjNlOTY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZWNkNDI4ZDM0NTAwMmMzMzA0OTg0NTBiYTFjNTFmYmRlNzRhMjhlZjhlOTlk
|
14
|
+
ZDRiMmIwYmIzMDYyNThkMDhjMTBhYzU3OTBmYzNhYzQ1ZDA1YTYwZGQyZTg5
|
15
|
+
NjM3MWMwM2IyMjYzYWQ5Zjg0NjE5OWM0MzZiODY0M2E3MGUyYjk=
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/lib/silly.rb
CHANGED
@@ -7,6 +7,7 @@ require 'fileutils'
|
|
7
7
|
require 'delegate'
|
8
8
|
require 'observer'
|
9
9
|
require 'set'
|
10
|
+
require 'cgi'
|
10
11
|
|
11
12
|
FileUtils.cd(path = File.join(File.dirname(__FILE__), 'silly')) do
|
12
13
|
Dir[File.join('**', '*.rb')].each { |f| require File.join(path, f) }
|
@@ -14,7 +15,7 @@ end
|
|
14
15
|
|
15
16
|
module Silly
|
16
17
|
FileSeparator = File::ALT_SEPARATOR ?
|
17
|
-
%r{#{ File::SEPARATOR }|#{ File::ALT_SEPARATOR }} :
|
18
|
+
%r{#{ File::SEPARATOR }|#{ File::ALT_SEPARATOR.gsub('\\', '\\\\\\\\') }} :
|
18
19
|
File::SEPARATOR
|
19
20
|
|
20
21
|
class Query
|
@@ -63,7 +64,7 @@ module Silly
|
|
63
64
|
|
64
65
|
def execute
|
65
66
|
@criteria["path"] ||= "*"
|
66
|
-
puts "EXECUTE:\n #{ @criteria.
|
67
|
+
puts "EXECUTE:\n #{ @criteria.inspect }"
|
67
68
|
data = files(@criteria["path"])
|
68
69
|
|
69
70
|
unless @criteria["where"].empty?
|
data/lib/silly/base_model.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module Silly
|
2
2
|
class BaseModel < SimpleDelegator
|
3
|
-
def
|
4
|
-
{
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
def data
|
4
|
+
{}
|
5
|
+
end
|
6
|
+
|
7
|
+
def content
|
8
|
+
File.open(realpath, 'r:UTF-8') { |f| f.read }
|
8
9
|
end
|
9
10
|
end
|
10
11
|
end
|
data/lib/silly/data_model.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
module Silly
|
2
2
|
class DataModel < SimpleDelegator
|
3
|
-
def
|
3
|
+
def data
|
4
4
|
data = {}
|
5
5
|
cascade.each do |path|
|
6
6
|
data = Silly::Utils.deep_merge(data, (Silly::Parse.data_file(path) || {}))
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
data
|
10
|
+
end
|
11
|
+
|
12
|
+
def content
|
13
|
+
Silly::Parse.page_file(realpath)
|
13
14
|
end
|
14
15
|
end
|
15
16
|
end
|
data/lib/silly/item.rb
CHANGED
@@ -2,7 +2,7 @@ module Silly
|
|
2
2
|
class Item
|
3
3
|
include Observable
|
4
4
|
attr_reader :pointer
|
5
|
-
attr_accessor :content, :collection
|
5
|
+
attr_accessor :data, :content, :collection
|
6
6
|
|
7
7
|
def initialize(hash)
|
8
8
|
@pointer = hash
|
@@ -48,12 +48,12 @@ module Silly
|
|
48
48
|
|
49
49
|
# @returns[Hash Object] Top page metadata
|
50
50
|
def data
|
51
|
-
@data ||= (_model.
|
51
|
+
@data ||= (_model.data || {})
|
52
52
|
end
|
53
53
|
|
54
54
|
# @returns[String] Raw page content
|
55
55
|
def content
|
56
|
-
@content ||= (_model.
|
56
|
+
@content ||= (_model.content || "")
|
57
57
|
end
|
58
58
|
|
59
59
|
private
|
data/lib/silly/page_model.rb
CHANGED
@@ -3,41 +3,62 @@ module Silly
|
|
3
3
|
DateMatcher = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
|
4
4
|
Matcher = /^(.+\/)*(.*)(\.[^.]+)$/
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def
|
6
|
+
class << self
|
7
|
+
attr_accessor :before_data, :after_data, :before_content, :after_content
|
8
|
+
end
|
9
|
+
|
10
|
+
def data
|
11
11
|
return {} unless file?
|
12
12
|
|
13
|
-
|
14
|
-
data
|
13
|
+
data = Silly::Parse.page_file(realpath)["data"]
|
14
|
+
data['id'] = id
|
15
|
+
|
16
|
+
if self.class.before_data.respond_to?(:call)
|
17
|
+
data = self.class.before_data.call(data)
|
18
|
+
end
|
15
19
|
|
16
20
|
filename_data = parse_page_filename(id)
|
21
|
+
data['title'] ||= filename_data['title']
|
22
|
+
data['date'] = parse_date(data['date'] || filename_data['date'])
|
23
|
+
data['_url'] = make_url(data)
|
17
24
|
|
18
|
-
|
19
|
-
|
25
|
+
self.class.after_data.respond_to?(:call) ?
|
26
|
+
self.class.after_data.call(data) :
|
27
|
+
data
|
28
|
+
end
|
20
29
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# Parse and store date as an object
|
25
|
-
begin
|
26
|
-
data['date'] = Time.parse(data['date']) unless data['date'].nil? || data['date'].is_a?(Time)
|
27
|
-
rescue
|
28
|
-
raise(
|
29
|
-
"ArgumentError: The date '#{data['date']}' specified in '#{ id }' is unparsable."
|
30
|
-
)
|
31
|
-
data['date'] = nil
|
32
|
-
end
|
30
|
+
def content
|
31
|
+
result = Silly::Parse.page_file(realpath)["content"]
|
33
32
|
|
34
|
-
|
33
|
+
if self.class.before_content.respond_to?(:call)
|
34
|
+
result = self.class.before_content.call(result)
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
+
self.class.before_content.respond_to?(:call) ?
|
38
|
+
self.class.before_content.call(result) :
|
39
|
+
result
|
37
40
|
end
|
38
41
|
|
39
42
|
private
|
40
43
|
|
44
|
+
def make_url(data)
|
45
|
+
page_data = data.dup
|
46
|
+
format = page_data['permalink'] || "/:path/:filename"
|
47
|
+
slug = Silly::UrlSlug.new(item: self, data: page_data, format: format)
|
48
|
+
slug.generate
|
49
|
+
end
|
50
|
+
|
51
|
+
# Parse and store date as an object
|
52
|
+
def parse_date(date)
|
53
|
+
return date if (date.nil? || date.is_a?(Time))
|
54
|
+
|
55
|
+
Time.parse(date)
|
56
|
+
rescue
|
57
|
+
raise(
|
58
|
+
"ArgumentError: The date '#{ date }' specified in '#{ id }' is unparsable."
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
41
62
|
# Is the item backed by a physical file in the filesystem?
|
42
63
|
# @return[Boolean]
|
43
64
|
def file?
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Silly
|
2
|
+
# StringFormat is meant to expose the common (public) interface
|
3
|
+
# for where strings (namely URLS) are formatted.
|
4
|
+
# Users are encouraged to reimplement these methods via plugins to enable
|
5
|
+
# custom-defined slug generation logic based on their tastes.
|
6
|
+
#
|
7
|
+
# TODO:
|
8
|
+
# - Natively support the most popular slug formats.
|
9
|
+
# - Better support for Internationalization.
|
10
|
+
module StringFormat
|
11
|
+
|
12
|
+
# Public interface for building 'clean slugs'
|
13
|
+
# Redefine this method to implement custom slug generation.
|
14
|
+
def self.clean_slug(string)
|
15
|
+
hyphenate(string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.clean_slug_and_escape(string)
|
19
|
+
CGI::escape(clean_slug(string))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Simple url slug normalization.
|
23
|
+
# Converts all non word characters into hyphens.
|
24
|
+
# This may not be what you want so feel free to overwite the public
|
25
|
+
# method in place of another formatter.
|
26
|
+
#
|
27
|
+
# Ex: My Post Title ===> my-post-title
|
28
|
+
def self.hyphenate(string)
|
29
|
+
string = string.to_s.downcase.strip.gsub(/[^\p{Word}+]/u, '-')
|
30
|
+
string.gsub(/^\-+/, '').gsub(/\-+$/, '').gsub(/\-+/, '-')
|
31
|
+
end
|
32
|
+
|
33
|
+
# TODO: Probably use ActiveSupport for this stuff
|
34
|
+
# Ex: my-post-title ===> My Post Title
|
35
|
+
def self.titleize(string)
|
36
|
+
string.gsub(/[^\p{Word}+]/u, ' ').gsub(/\b\w/){ $&.upcase }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Convert CamelCase to snake_case
|
40
|
+
# Thanks ActiveSupport: http://stackoverflow.com/a/1509939/101940
|
41
|
+
def self.snake_case(string)
|
42
|
+
string.
|
43
|
+
to_s.
|
44
|
+
gsub(/::/, '/').
|
45
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
46
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
47
|
+
tr("-", "_").
|
48
|
+
downcase
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module Silly
|
2
|
+
class UrlSlug
|
3
|
+
@convertable_extensions = %w{ .md .markdown .mustache .haml .erb }
|
4
|
+
class << self
|
5
|
+
attr_reader :convertable_extensions
|
6
|
+
|
7
|
+
def add_extensions(ext)
|
8
|
+
if ext.is_a?(Array)
|
9
|
+
@convertable_extensions += ext
|
10
|
+
else
|
11
|
+
@convertable_extensions << ext
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(opts)
|
17
|
+
@item = opts[:item]
|
18
|
+
@scope = @item.id.split('/').first
|
19
|
+
@data = opts[:data]
|
20
|
+
@format = opts[:format]
|
21
|
+
|
22
|
+
parts = @item.id.split('/')
|
23
|
+
parts.shift
|
24
|
+
@scoped_id = parts.join('/')
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return[String] URL Slug based on the given data and format.
|
28
|
+
# The url should always have a preceeding slash "/some-url"
|
29
|
+
def generate
|
30
|
+
url = @format.include?(':') ? dynamic : literal
|
31
|
+
url = process_url_extension(url)
|
32
|
+
|
33
|
+
url.to_s.start_with?('/') ? url : "/#{ url }"
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return[String] the literal URL without token substitution.
|
37
|
+
def literal
|
38
|
+
@format.gsub(/^\//, '').split('/').map {|p| CGI::escape(p) }.join('/')
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return[String] the dynamic URL with token substitution.
|
42
|
+
def dynamic
|
43
|
+
cache = data
|
44
|
+
result = @format
|
45
|
+
.gsub(/:[^\/\.]+/) { |a| cache[$&.gsub(':', '')] }
|
46
|
+
.gsub('//', '/')
|
47
|
+
.split('/')
|
48
|
+
|
49
|
+
# this is ugly but I'm out of ideas. Help!
|
50
|
+
last = result.pop
|
51
|
+
if uses_extension?
|
52
|
+
last = last
|
53
|
+
.split('.')
|
54
|
+
.map{ |a| Silly::StringFormat.clean_slug_and_escape(a) }
|
55
|
+
.join('.')
|
56
|
+
else
|
57
|
+
last = Silly::StringFormat.clean_slug_and_escape(last)
|
58
|
+
end
|
59
|
+
|
60
|
+
result
|
61
|
+
.map{ |a| Silly::StringFormat.clean_slug_and_escape(a) }
|
62
|
+
.join('/') +
|
63
|
+
"/#{ last }"
|
64
|
+
end
|
65
|
+
|
66
|
+
def data
|
67
|
+
result = @data
|
68
|
+
result = result.merge(date_data) if uses_date?
|
69
|
+
|
70
|
+
result.merge({
|
71
|
+
"filename" => filename,
|
72
|
+
"path" => path,
|
73
|
+
"relative_path" => relative_path,
|
74
|
+
"categories" => category,
|
75
|
+
})
|
76
|
+
end
|
77
|
+
|
78
|
+
def date_data
|
79
|
+
date = Time.parse(@data['date'].to_s)
|
80
|
+
|
81
|
+
{
|
82
|
+
"year" => date.strftime("%Y"),
|
83
|
+
"month" => date.strftime("%m"),
|
84
|
+
"day" => date.strftime("%d"),
|
85
|
+
"i_day" => date.strftime("%d").to_i.to_s,
|
86
|
+
"i_month" => date.strftime("%m").to_i.to_s,
|
87
|
+
}
|
88
|
+
rescue ArgumentError, TypeError
|
89
|
+
raise(
|
90
|
+
"ArgumentError:" +
|
91
|
+
" The file '#{ @item.realpath }' has a permalink '#{ @format }'" +
|
92
|
+
" which is date dependant but the date '#{ @data['date'] }' could not be parsed." +
|
93
|
+
" Ensure the date's format is: 'YYYY-MM-DD'"
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
def filename
|
98
|
+
File.basename(@scoped_id, ext)
|
99
|
+
end
|
100
|
+
|
101
|
+
def ext
|
102
|
+
@item.ext
|
103
|
+
end
|
104
|
+
|
105
|
+
# Category is only the first one if multiple categories exist.
|
106
|
+
def category
|
107
|
+
string = Array(@data['categories'])[0]
|
108
|
+
return '' if string.to_s.empty?
|
109
|
+
|
110
|
+
string.split('/').map { |c|
|
111
|
+
Silly::StringFormat.clean_slug_and_escape(c)
|
112
|
+
}.join('/')
|
113
|
+
end
|
114
|
+
|
115
|
+
def relative_path
|
116
|
+
string = File.dirname(@scoped_id)
|
117
|
+
(string == ".") ? "" : string
|
118
|
+
end
|
119
|
+
|
120
|
+
def path
|
121
|
+
File.join(@scope, relative_path)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def uses_date?
|
127
|
+
result = false
|
128
|
+
%w{ :year :month :day :i_day :i_month }.each do |token|
|
129
|
+
if @format.include?(token)
|
130
|
+
result = true
|
131
|
+
break
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
# Is an extension explicitly defined?
|
139
|
+
def uses_extension?
|
140
|
+
@format =~ /\.[^\.]+$/
|
141
|
+
end
|
142
|
+
|
143
|
+
# The url extension depends on multiple factors:
|
144
|
+
# user-config : preserve any extension set by the user in the format.
|
145
|
+
# converters : Automatically change convertable extensions to .html
|
146
|
+
# Non-convertable file-extensions should 'pass-through'
|
147
|
+
#
|
148
|
+
# @return[String]
|
149
|
+
def process_url_extension(url)
|
150
|
+
return url if uses_extension?
|
151
|
+
|
152
|
+
url += self.class.convertable_extensions.include?(ext) ? '.html' : ext
|
153
|
+
|
154
|
+
url.gsub(/index|index.html$/, '').gsub(/\.html$/, '')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/lib/silly/version.rb
CHANGED
data/silly.gemspec
CHANGED
@@ -13,9 +13,9 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = 'Silly is an ODM for parsing and querying a directory like you would a database -- useful for static websites.'
|
14
14
|
|
15
15
|
|
16
|
-
s.add_development_dependency 'cucumber'
|
17
|
-
s.add_development_dependency 'capybara'
|
18
|
-
s.add_development_dependency 'rspec'
|
16
|
+
s.add_development_dependency 'cucumber', '~> 1'
|
17
|
+
s.add_development_dependency 'capybara', '~> 2'
|
18
|
+
s.add_development_dependency 'rspec', '~> 2'
|
19
19
|
|
20
20
|
s.files = `git ls-files`.
|
21
21
|
split("\n").
|
metadata
CHANGED
@@ -1,64 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: silly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jade Dominguez
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-01-20 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: cucumber
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - ~>
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
19
|
+
version: '1'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - ~>
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
26
|
+
version: '1'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: capybara
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - ~>
|
36
32
|
- !ruby/object:Gem::Version
|
37
|
-
version: '
|
33
|
+
version: '2'
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - ~>
|
44
39
|
- !ruby/object:Gem::Version
|
45
|
-
version: '
|
40
|
+
version: '2'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rspec
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - ~>
|
52
46
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
47
|
+
version: '2'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - ~>
|
60
53
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
54
|
+
version: '2'
|
62
55
|
description: Silly is an ODM for parsing and querying a directory like you would a
|
63
56
|
database -- useful for static websites.
|
64
57
|
email: plusjade@gmail.com
|
@@ -86,32 +79,33 @@ files:
|
|
86
79
|
- lib/silly/page_model.rb
|
87
80
|
- lib/silly/parse.rb
|
88
81
|
- lib/silly/query_operators.rb
|
82
|
+
- lib/silly/string_format.rb
|
83
|
+
- lib/silly/url_slug.rb
|
89
84
|
- lib/silly/utils.rb
|
90
85
|
- lib/silly/version.rb
|
91
86
|
- silly.gemspec
|
92
87
|
homepage: http://github.com/ruhoh/silly
|
93
88
|
licenses:
|
94
89
|
- http://www.opensource.org/licenses/MIT
|
90
|
+
metadata: {}
|
95
91
|
post_install_message:
|
96
92
|
rdoc_options: []
|
97
93
|
require_paths:
|
98
94
|
- lib
|
99
95
|
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
-
none: false
|
101
96
|
requirements:
|
102
97
|
- - ! '>='
|
103
98
|
- !ruby/object:Gem::Version
|
104
99
|
version: '0'
|
105
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
101
|
requirements:
|
108
102
|
- - ! '>='
|
109
103
|
- !ruby/object:Gem::Version
|
110
104
|
version: '0'
|
111
105
|
requirements: []
|
112
106
|
rubyforge_project:
|
113
|
-
rubygems_version:
|
107
|
+
rubygems_version: 2.2.1
|
114
108
|
signing_key:
|
115
|
-
specification_version:
|
109
|
+
specification_version: 4
|
116
110
|
summary: Silly is a filesystem based Object Document Mapper.
|
117
111
|
test_files: []
|