mascot 0.1.12 → 0.1.14
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 +4 -4
- data/lib/mascot.rb +8 -10
- data/lib/mascot/directory_collection.rb +24 -0
- data/lib/mascot/extensions/layouts.rb +2 -2
- data/lib/mascot/extensions/proc_manipulator.rb +3 -3
- data/lib/mascot/formats.rb +45 -0
- data/lib/mascot/resource.rb +5 -6
- data/lib/mascot/resources_node.rb +47 -46
- data/lib/mascot/site.rb +16 -27
- data/lib/mascot/version.rb +1 -1
- metadata +4 -3
- data/lib/mascot/safe_root.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6be4b1751690e6c59370910168e633fa512d4179
|
4
|
+
data.tar.gz: 953289c76d1245c7e037a5a32ededad0321ca8cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61471911d2ef4efb0c017e371d4fa563af64116e40360a0eff0062d14f2397343755c9de4930ea01a8470f66408ef29a136994a1d07236e715ec42b136612df9
|
7
|
+
data.tar.gz: 2a24c41a5618965f225570b912346fa0d2b9863076c2e1e1d876efe6ea78dc79ee65847297dfcd64dc45f86a26f2868e288bfcb91c5d6e6c6f9658c205eb57d9
|
data/lib/mascot.rb
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
require "mascot/version"
|
2
2
|
|
3
3
|
module Mascot
|
4
|
-
# Raised if a user attempts to access a resource outside of the site path.
|
5
|
-
UnsafePathAccessError = Class.new(SecurityError)
|
6
|
-
|
7
4
|
# Raised by Resources if a path is added that's not a valid path.
|
8
5
|
InvalidRequestPathError = Class.new(RuntimeError)
|
9
6
|
|
10
7
|
# Raised by Resources if a path is already in its index
|
11
8
|
ExistingRequestPathError = Class.new(InvalidRequestPathError)
|
12
9
|
|
13
|
-
autoload :Asset,
|
14
|
-
autoload :
|
15
|
-
autoload :
|
16
|
-
autoload :
|
17
|
-
autoload :
|
18
|
-
autoload :
|
19
|
-
autoload :
|
10
|
+
autoload :Asset, "mascot/asset"
|
11
|
+
autoload :DirectoryCollection, "mascot/directory_collection"
|
12
|
+
autoload :Formats, "mascot/formats"
|
13
|
+
autoload :Frontmatter, "mascot/frontmatter"
|
14
|
+
autoload :Resource, "mascot/resource"
|
15
|
+
autoload :ResourcesPipeline, "mascot/resources_pipeline"
|
16
|
+
autoload :ResourcesNode, "mascot/resources_node"
|
17
|
+
autoload :Site, "mascot/site"
|
20
18
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Mascot
|
2
|
+
# Maps a directory of assets into a set of routes that correspond with
|
3
|
+
# the `path` root.
|
4
|
+
class DirectoryCollection
|
5
|
+
attr_reader :assets, :path
|
6
|
+
|
7
|
+
def initialize(path: , assets:)
|
8
|
+
@path = path
|
9
|
+
@assets = assets
|
10
|
+
end
|
11
|
+
|
12
|
+
def mount(node)
|
13
|
+
assets.each { |a| node.add path: asset_path_to_request_path(a), asset: a }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
# Given a @file_path of `/hi`, this method changes `/hi/there/friend.html.erb`
|
18
|
+
# to an absolute `/there/friend` format by removing the file extensions
|
19
|
+
def asset_path_to_request_path(asset)
|
20
|
+
# Relative path of resource to the file_path of this project.
|
21
|
+
asset.path.dirname.join(asset.format_basename).relative_path_from(path).to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -13,8 +13,8 @@ module Mascot
|
|
13
13
|
@rules << Rule.new(layout, block)
|
14
14
|
end
|
15
15
|
|
16
|
-
def process_resources(
|
17
|
-
resources.each do |resource|
|
16
|
+
def process_resources(node)
|
17
|
+
node.resources.each do |resource|
|
18
18
|
@rules.each do |rule|
|
19
19
|
if rule.processor.call(resource)
|
20
20
|
resource.data["layout"] ||= rule.layout
|
@@ -5,12 +5,12 @@ module Mascot
|
|
5
5
|
@block = block
|
6
6
|
end
|
7
7
|
|
8
|
-
def process_resources(
|
9
|
-
resources.each do |resource|
|
8
|
+
def process_resources(node)
|
9
|
+
node.resources.each do |resource|
|
10
10
|
if @block.arity == 1
|
11
11
|
@block.call resource
|
12
12
|
else # This will blow up if 0 or greater than 2.
|
13
|
-
@block.call resource,
|
13
|
+
@block.call resource, node
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Mascot
|
2
|
+
# Manages collections of resources that share the same ResourceNode. Given the files `/a.html` and `/a.gif`,
|
3
|
+
# both of these assets would be stored in the `ResourceNode#name = "a"` under `ResourceNode#formats` with
|
4
|
+
# the extensions `.gif`, and `.html`.
|
5
|
+
class Formats
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
extend Forwardable
|
9
|
+
def_delegators :@formats, :size, :clear
|
10
|
+
|
11
|
+
def initialize(node: )
|
12
|
+
@node = node
|
13
|
+
@formats = Hash.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def each(&block)
|
17
|
+
@formats.values.each(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove(ext)
|
21
|
+
@formats.delete(ext)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ext(ext)
|
25
|
+
@formats[ext]
|
26
|
+
end
|
27
|
+
|
28
|
+
def mime_type(mime_type)
|
29
|
+
find { |f| f.mime_type == mime_type }
|
30
|
+
end
|
31
|
+
|
32
|
+
def add(asset: , ext: )
|
33
|
+
resource = Resource.new(asset: asset, node: @node, ext: ext)
|
34
|
+
if @formats.has_key? ext
|
35
|
+
raise Mascot::ExistingRequestPathError, "Resource at #{resource.request_path} already set"
|
36
|
+
else
|
37
|
+
@formats[ext] = resource
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
"<#{self.class}: resources=#{map(&:request_path)}>"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/mascot/resource.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require "forwardable"
|
2
|
-
require "observer"
|
3
2
|
|
4
3
|
module Mascot
|
5
4
|
# Represents the request path of an asset. There may be multiple
|
@@ -70,17 +69,17 @@ module Mascot
|
|
70
69
|
# below and select, we could call a single map and pull out a resources
|
71
70
|
def filter_resources(type: DEFAULT_FILTER_SCOPE, &block)
|
72
71
|
return [] unless node
|
73
|
-
|
72
|
+
nodes = block.call
|
74
73
|
|
75
74
|
case type
|
76
75
|
when :all
|
77
|
-
|
76
|
+
nodes.map(&:formats)
|
78
77
|
when :same
|
79
|
-
|
78
|
+
nodes.map{ |n| n.formats.ext(ext) }.flatten.compact
|
80
79
|
when String
|
81
|
-
|
80
|
+
nodes.map{ |n| n.formats.ext(type) }.flatten.compact
|
82
81
|
when MIME::Type
|
83
|
-
|
82
|
+
nodes.map{ |n| n.formats.mime_type(type) }.flatten.compact
|
84
83
|
else
|
85
84
|
raise ArgumentError, "Invalid type argument #{type}. Must be either :same, :all, an extension string, or a Mime::Type"
|
86
85
|
end
|
@@ -5,9 +5,7 @@ module Mascot
|
|
5
5
|
# a leaf node. The actual `buz.html` asset is then stored on the leaf node as a resource. This tree structure
|
6
6
|
# makes it possible to reason through path relationships from code to build out elements in a website like tree navigation.
|
7
7
|
class ResourcesNode
|
8
|
-
|
9
|
-
attr_reader :parent, :name, :formats
|
10
|
-
include Enumerable
|
8
|
+
attr_reader :parent, :name
|
11
9
|
|
12
10
|
DELIMITER = "/".freeze
|
13
11
|
|
@@ -15,13 +13,15 @@ module Mascot
|
|
15
13
|
@parent = parent
|
16
14
|
@name = name.freeze
|
17
15
|
@delimiter = delimiter.freeze
|
18
|
-
|
19
|
-
|
16
|
+
end
|
17
|
+
|
18
|
+
def formats
|
19
|
+
@formats ||= Formats.new(node: self)
|
20
20
|
end
|
21
21
|
|
22
22
|
# Returns the immediate children nodes.
|
23
23
|
def children
|
24
|
-
|
24
|
+
child_nodes.values
|
25
25
|
end
|
26
26
|
|
27
27
|
# Returns sibling nodes.
|
@@ -40,28 +40,34 @@ module Mascot
|
|
40
40
|
parents
|
41
41
|
end
|
42
42
|
|
43
|
-
# Iterates through resources in the current node and all child resources.
|
44
|
-
# TODO: Should this include all children? Perhaps I should move this method
|
45
|
-
# into an eumerator that more clearly states it returns an entire sub-tree.
|
46
|
-
def each(&block)
|
47
|
-
@resources.values.each { |resource| block.call(resource) }
|
48
|
-
children.each{ |c| c.each(&block) }
|
49
|
-
end
|
50
|
-
|
51
43
|
def root?
|
52
44
|
parent.nil?
|
53
45
|
end
|
54
46
|
|
55
47
|
def leaf?
|
56
|
-
|
48
|
+
child_nodes.empty?
|
57
49
|
end
|
58
50
|
|
59
|
-
|
60
|
-
|
51
|
+
class Resources
|
52
|
+
include Enumerable
|
53
|
+
|
54
|
+
def initialize(node: )
|
55
|
+
@node = node
|
56
|
+
end
|
57
|
+
|
58
|
+
def each(&block)
|
59
|
+
@node.formats.each(&block)
|
60
|
+
@node.children.each { |child| child.resources.each(&block) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def glob(pattern)
|
64
|
+
paths = Dir.glob(pattern)
|
65
|
+
select { |r| paths.include? r.asset.path.to_s }
|
66
|
+
end
|
61
67
|
end
|
62
68
|
|
63
|
-
def
|
64
|
-
|
69
|
+
def resources
|
70
|
+
Resources.new(node: self)
|
65
71
|
end
|
66
72
|
|
67
73
|
def remove
|
@@ -70,7 +76,7 @@ module Mascot
|
|
70
76
|
# this call orphans the tree up to a resource.
|
71
77
|
parent.remove_child(name)
|
72
78
|
else
|
73
|
-
|
79
|
+
formats.clear
|
74
80
|
end
|
75
81
|
end
|
76
82
|
|
@@ -78,9 +84,9 @@ module Mascot
|
|
78
84
|
head, *path = tokenize(path)
|
79
85
|
if path.empty?
|
80
86
|
# When there's no more paths, we're left with the format (e.g. ".html")
|
81
|
-
|
87
|
+
formats.add(asset: asset, ext: head)
|
82
88
|
else
|
83
|
-
|
89
|
+
child_nodes[head].add(path: path, asset: asset)
|
84
90
|
end
|
85
91
|
end
|
86
92
|
alias :[]= :add
|
@@ -88,37 +94,18 @@ module Mascot
|
|
88
94
|
def get_resource(path)
|
89
95
|
*path, ext = tokenize(path)
|
90
96
|
if node = dig(*path)
|
91
|
-
node.
|
97
|
+
node.formats.ext(ext)
|
92
98
|
end
|
93
99
|
end
|
94
100
|
|
95
|
-
def inspect
|
96
|
-
"<#{self.class}: resources=#{resources.map(&:request_path)} children=#{children.map(&:name).inspect}>"
|
97
|
-
end
|
98
|
-
|
99
101
|
def get(path)
|
100
102
|
*path, ext = tokenize(path)
|
101
103
|
dig(*path)
|
102
104
|
end
|
103
105
|
alias :[] :get
|
104
106
|
|
105
|
-
|
106
|
-
|
107
|
-
@resources[ext]
|
108
|
-
end
|
109
|
-
|
110
|
-
def remove_child(path)
|
111
|
-
*_, segment, _ = tokenize(path)
|
112
|
-
@children.delete(segment)
|
113
|
-
end
|
114
|
-
|
115
|
-
def add_format(asset: , ext: )
|
116
|
-
resource = Resource.new(asset: asset, node: self, ext: ext)
|
117
|
-
if @resources.has_key? ext
|
118
|
-
raise Mascot::ExistingRequestPathError, "Resource at #{resource.request_path} already set"
|
119
|
-
else
|
120
|
-
@resources[ext] = resource
|
121
|
-
end
|
107
|
+
def inspect
|
108
|
+
"<#{self.class}: formats=#{formats.map(&:request_path)} children=#{children.map(&:name).inspect}>"
|
122
109
|
end
|
123
110
|
|
124
111
|
# TODO: I don't really like how the path is broken up with the "ext" at the end.
|
@@ -128,14 +115,28 @@ module Mascot
|
|
128
115
|
head, *tail = args
|
129
116
|
if head.nil? and tail.empty?
|
130
117
|
self
|
131
|
-
elsif
|
132
|
-
|
118
|
+
elsif child_nodes.has_key?(head)
|
119
|
+
child_nodes[head].dig(*tail)
|
133
120
|
else
|
134
121
|
nil
|
135
122
|
end
|
136
123
|
end
|
137
124
|
|
125
|
+
protected
|
126
|
+
def remove_child(path)
|
127
|
+
*_, segment, _ = tokenize(path)
|
128
|
+
child_nodes.delete(segment)
|
129
|
+
end
|
130
|
+
|
138
131
|
private
|
132
|
+
def add_child_node(name)
|
133
|
+
ResourcesNode.new(parent: self, delimiter: @delimiter, name: name)
|
134
|
+
end
|
135
|
+
|
136
|
+
def child_nodes
|
137
|
+
@child_nodes ||= Hash.new { |hash, key| hash[key] = add_child_node(key) }
|
138
|
+
end
|
139
|
+
|
139
140
|
# Returns all of the names for the path along with the format, if set.
|
140
141
|
def tokenize(path)
|
141
142
|
return path if path.respond_to? :to_a
|
data/lib/mascot/site.rb
CHANGED
@@ -6,31 +6,24 @@ module Mascot
|
|
6
6
|
class Site
|
7
7
|
# Default file pattern to pick up in site
|
8
8
|
DEFAULT_GLOB = "**/**".freeze
|
9
|
-
# Default root path for site.
|
10
|
-
DEFAULT_ROOT_PATH = Pathname.new(".").freeze
|
11
9
|
|
12
|
-
|
10
|
+
# Default root_path for site.
|
11
|
+
DEFAULT_ROOT_PATH = Pathname.new(".").freeze
|
13
12
|
|
14
|
-
|
15
|
-
self.root = root
|
16
|
-
end
|
13
|
+
attr_reader :root_path, :resources_pipeline
|
17
14
|
|
18
|
-
|
19
|
-
|
20
|
-
safe_root.glob(root.join(glob)).select(&File.method(:file?)).lazy.map do |path|
|
21
|
-
Asset.new(path: path)
|
22
|
-
end
|
15
|
+
def initialize(root_path: DEFAULT_ROOT_PATH)
|
16
|
+
self.root_path = root_path
|
23
17
|
end
|
24
18
|
|
25
19
|
def glob(glob)
|
26
|
-
|
27
|
-
resources.select{ |r| paths.include? r.asset.path.to_s }
|
20
|
+
root.resources.glob(root_path.join(glob))
|
28
21
|
end
|
29
22
|
|
30
23
|
# Returns a list of resources.
|
31
|
-
def
|
24
|
+
def root
|
32
25
|
ResourcesNode.new.tap do |root_node|
|
33
|
-
assets
|
26
|
+
DirectoryCollection.new(assets: assets, path: root_path).mount(root_node)
|
34
27
|
resources_pipeline.process root_node
|
35
28
|
end
|
36
29
|
end
|
@@ -43,11 +36,11 @@ module Mascot
|
|
43
36
|
|
44
37
|
# Find the page with a path.
|
45
38
|
def get(request_path)
|
46
|
-
|
39
|
+
root.get_resource(request_path)
|
47
40
|
end
|
48
41
|
|
49
|
-
def
|
50
|
-
@
|
42
|
+
def root_path=(path)
|
43
|
+
@root_path = Pathname.new(path)
|
51
44
|
end
|
52
45
|
|
53
46
|
def resources_pipeline
|
@@ -55,15 +48,11 @@ module Mascot
|
|
55
48
|
end
|
56
49
|
|
57
50
|
private
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
def safe_root
|
66
|
-
SafeRoot.new(path: root)
|
51
|
+
# Lazy stream of files that will be rendered by resources.
|
52
|
+
def assets(glob = DEFAULT_GLOB)
|
53
|
+
Dir.glob(root_path.join(glob)).select(&File.method(:file?)).lazy.map do |path|
|
54
|
+
Asset.new(path: path)
|
55
|
+
end
|
67
56
|
end
|
68
57
|
end
|
69
58
|
end
|
data/lib/mascot/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mascot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brad Gessler
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -75,13 +75,14 @@ extra_rdoc_files: []
|
|
75
75
|
files:
|
76
76
|
- lib/mascot.rb
|
77
77
|
- lib/mascot/asset.rb
|
78
|
+
- lib/mascot/directory_collection.rb
|
78
79
|
- lib/mascot/extensions/layouts.rb
|
79
80
|
- lib/mascot/extensions/proc_manipulator.rb
|
81
|
+
- lib/mascot/formats.rb
|
80
82
|
- lib/mascot/frontmatter.rb
|
81
83
|
- lib/mascot/resource.rb
|
82
84
|
- lib/mascot/resources_node.rb
|
83
85
|
- lib/mascot/resources_pipeline.rb
|
84
|
-
- lib/mascot/safe_root.rb
|
85
86
|
- lib/mascot/site.rb
|
86
87
|
- lib/mascot/version.rb
|
87
88
|
- mascot.gemspec
|
data/lib/mascot/safe_root.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
require "pathname"
|
2
|
-
|
3
|
-
module Mascot
|
4
|
-
# Validates if a path is within another path. This prevents
|
5
|
-
# users from accidentally selecting a file outside of their site,
|
6
|
-
# which could be insured.
|
7
|
-
class SafeRoot
|
8
|
-
def initialize(path: )
|
9
|
-
@path = Pathname.new(path)
|
10
|
-
end
|
11
|
-
|
12
|
-
# Validates if a path is safe by checking if its within a folder.
|
13
|
-
def safe?(path)
|
14
|
-
root_path = File.expand_path(@path)
|
15
|
-
resource_path = File.expand_path(path)
|
16
|
-
|
17
|
-
if resource_path.start_with? root_path
|
18
|
-
path
|
19
|
-
else
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def glob(pattern)
|
24
|
-
Dir[validate(pattern)]
|
25
|
-
end
|
26
|
-
|
27
|
-
def unsafe?(path)
|
28
|
-
not safe? path
|
29
|
-
end
|
30
|
-
|
31
|
-
def path
|
32
|
-
end
|
33
|
-
|
34
|
-
def validate(path)
|
35
|
-
if unsafe? path
|
36
|
-
raise Mascot::UnsafePathAccessError, "Unsafe attempt to access #{path} outside of #{@path}"
|
37
|
-
else
|
38
|
-
path
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|