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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1d705fe26af1339418f2018ebeed0af942e6b49e
4
- data.tar.gz: d0fbb9055d2b7214078eb1de963065e8f1949c9b
3
+ metadata.gz: 6be4b1751690e6c59370910168e633fa512d4179
4
+ data.tar.gz: 953289c76d1245c7e037a5a32ededad0321ca8cc
5
5
  SHA512:
6
- metadata.gz: 95f73264703dccec615c082cf1f071237160b94e967903842c7f889da8dae7617c3a5931539ea0be5fbb2fbd070959cfc17bfc800c67eca0c758976249e88f01
7
- data.tar.gz: ab29c4b7f17646864334e14007b1bf27feab5960c9e5fa88a805785dab23421b62727b8b12d813519105a50555c5f116c6a74a82d34e35bbc46786747b9ba457
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, "mascot/asset"
14
- autoload :Frontmatter, "mascot/frontmatter"
15
- autoload :ResourcesPipeline, "mascot/resources_pipeline"
16
- autoload :Resource, "mascot/resource"
17
- autoload :ResourcesNode, "mascot/resources_node"
18
- autoload :SafeRoot, "mascot/safe_root"
19
- autoload :Site, "mascot/site"
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(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(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, resources
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
@@ -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
- resources = block.call.map(&:resources)
72
+ nodes = block.call
74
73
 
75
74
  case type
76
75
  when :all
77
- resources
76
+ nodes.map(&:formats)
78
77
  when :same
79
- resources.flatten.select { |r| r.ext == ext }
78
+ nodes.map{ |n| n.formats.ext(ext) }.flatten.compact
80
79
  when String
81
- resources.flatten.select { |r| r.ext == type }
80
+ nodes.map{ |n| n.formats.ext(type) }.flatten.compact
82
81
  when MIME::Type
83
- resources.flatten.select { |r| r.mime_type == type }
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
- # TODO: Should it be called "formats" or "extensions"? Probably the latter, but that could conflict with Resource Manipulators.
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
- @children = Hash.new { |hash, key| hash[key] = ResourcesNode.new(parent: self, delimiter: delimiter, name: key) }
19
- @resources = Hash.new
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
- @children.values
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
- @children.empty?
48
+ child_nodes.empty?
57
49
  end
58
50
 
59
- def resources
60
- @resources.values
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 remove_resource(resource)
64
- @resources.delete resource.ext #if @resources[resource.ext] == resource
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
- @resources.clear
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
- add_format(asset: asset, ext: head)
87
+ formats.add(asset: asset, ext: head)
82
88
  else
83
- @children[head].add(path: path, asset: asset)
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.get_format(ext: ext)
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
- protected
106
- def get_format(ext: "")
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 @children.has_key?(head)
132
- @children[head].dig(*tail)
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
- attr_reader :root, :resources_pipeline
10
+ # Default root_path for site.
11
+ DEFAULT_ROOT_PATH = Pathname.new(".").freeze
13
12
 
14
- def initialize(root: DEFAULT_ROOT_PATH)
15
- self.root = root
16
- end
13
+ attr_reader :root_path, :resources_pipeline
17
14
 
18
- # Lazy stream of files that will be rendered by resources.
19
- def assets(glob = DEFAULT_GLOB)
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
- paths = safe_root.glob(root.join(glob))
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 resources
24
+ def root
32
25
  ResourcesNode.new.tap do |root_node|
33
- assets.each { |a| root_node.add path: asset_path_to_request_path(a), asset: a }
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
- resources.get_resource(request_path)
39
+ root.get_resource(request_path)
47
40
  end
48
41
 
49
- def root=(path)
50
- @root = Pathname.new(path)
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
- # Given a @file_path of `/hi`, this method changes `/hi/there/friend.html.erb`
59
- # to an absolute `/there/friend` format by removing the file extensions
60
- def asset_path_to_request_path(asset)
61
- # Relative path of resource to the file_path of this project.
62
- asset.path.dirname.join(asset.format_basename).relative_path_from(root).to_s
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
@@ -1,3 +1,3 @@
1
1
  module Mascot
2
- VERSION = "0.1.12"
2
+ VERSION = "0.1.14"
3
3
  end
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.12
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-08-18 00:00:00.000000000 Z
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
@@ -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