silly 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
  gemspec
3
3
 
4
4
  group :test do
5
- gem 'cucumber'
6
- gem 'capybara'
7
- gem 'rspec'
5
+ gem 'cucumber', '~> 1'
6
+ gem 'capybara', '~> 2'
7
+ gem 'rspec', '~> 2'
8
8
  end
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w[lib]))
2
2
  require 'rubygems'
3
3
  require 'rake'
4
4
  require 'bundler'
5
- require 'ruhoh/version'
5
+ require 'silly/version'
6
6
 
7
7
  name = Dir['*.gemspec'].first.split('.').first
8
8
  gemspec_file = "#{name}.gemspec"
@@ -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.pretty_inspect }"
67
+ puts "EXECUTE:\n #{ @criteria.inspect }"
67
68
  data = files(@criteria["path"])
68
69
 
69
70
  unless @criteria["where"].empty?
@@ -1,10 +1,11 @@
1
1
  module Silly
2
2
  class BaseModel < SimpleDelegator
3
- def process
4
- {
5
- "data" => {},
6
- "content" => File.open(realpath, 'r:UTF-8') { |f| f.read }
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
@@ -1,15 +1,16 @@
1
1
  module Silly
2
2
  class DataModel < SimpleDelegator
3
- def process
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
- "data" => data,
11
- "content" => Silly::Parse.page_file(realpath)
12
- }
9
+ data
10
+ end
11
+
12
+ def content
13
+ Silly::Parse.page_file(realpath)
13
14
  end
14
15
  end
15
16
  end
@@ -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.process["data"] || {})
51
+ @data ||= (_model.data || {})
52
52
  end
53
53
 
54
54
  # @returns[String] Raw page content
55
55
  def content
56
- @content ||= (_model.process["content"] || "")
56
+ @content ||= (_model.content || "")
57
57
  end
58
58
 
59
59
  private
@@ -3,41 +3,62 @@ module Silly
3
3
  DateMatcher = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
4
4
  Matcher = /^(.+\/)*(.*)(\.[^.]+)$/
5
5
 
6
- # Process this file. See #parse_page_file
7
- # @return[Hash] the processed data from the file.
8
- # ex:
9
- # { "content" => "..", "data" => { "key" => "value" } }
10
- def process
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
- parsed_page = Silly::Parse.page_file(realpath)
14
- data = parsed_page['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
- data['pointer'] = pointer.dup
19
- data['id'] = id
25
+ self.class.after_data.respond_to?(:call) ?
26
+ self.class.after_data.call(data) :
27
+ data
28
+ end
20
29
 
21
- data['title'] = data['title'] || filename_data['title']
22
- data['date'] ||= filename_data['date']
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
- parsed_page['data'] = data
33
+ if self.class.before_content.respond_to?(:call)
34
+ result = self.class.before_content.call(result)
35
+ end
35
36
 
36
- parsed_page
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
@@ -1,3 +1,3 @@
1
1
  module Silly
2
- Version = VERSION = '0.0.1'
2
+ Version = VERSION = '0.0.2'
3
3
  end
@@ -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.1
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: 2013-12-30 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: 1.8.24
107
+ rubygems_version: 2.2.1
114
108
  signing_key:
115
- specification_version: 3
109
+ specification_version: 4
116
110
  summary: Silly is a filesystem based Object Document Mapper.
117
111
  test_files: []