silly 0.0.1 → 0.0.2
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.
- 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: []
|