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.
@@ -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: []