mascot 0.1.11 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d49c4a14896b46d0fdcd7aedd1250a104e9b5048
4
- data.tar.gz: 101a6a075f65556c4dc0a86ca69e7a5c39459a4d
3
+ metadata.gz: 1d705fe26af1339418f2018ebeed0af942e6b49e
4
+ data.tar.gz: d0fbb9055d2b7214078eb1de963065e8f1949c9b
5
5
  SHA512:
6
- metadata.gz: 65db308403d25699fd58e498e09a7c390b885894709ebb60be396eeca3f1faed0d65c574dfc75164325272462bd7672e230f505694c6e1b15a7e2f5ba1b6addc
7
- data.tar.gz: eec51f4ec3bd95bf28831e097a646acd3faeba341529f75eb3bb93a844ffdff87c44d3aa9d9d78d29d703897bde3fb7b1960d0bda74abf8f96d73e3dfe127f61
6
+ metadata.gz: 95f73264703dccec615c082cf1f071237160b94e967903842c7f889da8dae7617c3a5931539ea0be5fbb2fbd070959cfc17bfc800c67eca0c758976249e88f01
7
+ data.tar.gz: ab29c4b7f17646864334e14007b1bf27feab5960c9e5fa88a805785dab23421b62727b8b12d813519105a50555c5f116c6a74a82d34e35bbc46786747b9ba457
data/lib/mascot/asset.rb CHANGED
@@ -30,15 +30,25 @@ module Mascot
30
30
  path.basename.to_s.split(".").drop(1)
31
31
  end
32
32
 
33
+ # TODO: This is really a "key" or "leafname".
33
34
  def basename
34
35
  path.basename.to_s.split(".").first
35
36
  end
36
37
 
38
+ def format_basename
39
+ [basename, format_extension].join(".")
40
+ end
41
+
37
42
  # Returns the format extension.
38
43
  def format_extension
39
44
  extensions.first
40
45
  end
41
46
 
47
+ # The base name with the format extension.
48
+ def format_name
49
+ [basename, format_extension].join(".")
50
+ end
51
+
42
52
  # Returns a list of the rendering extensions.
43
53
  def template_extensions
44
54
  extensions.drop(1)
@@ -6,51 +6,84 @@ module Mascot
6
6
  # resources that point to the same asset. Resources are immutable
7
7
  # and may be altered by the resource proxy.
8
8
  class Resource
9
- include Observable
10
-
11
9
  extend Forwardable
12
10
  def_delegators :asset, :mime_type
13
11
 
14
- attr_accessor :request_path, :asset
15
12
  attr_writer :body, :data
13
+ attr_reader :node, :asset, :ext
14
+
15
+ # Default scope for querying parent/child/sibling resources.
16
+ DEFAULT_FILTER_SCOPE = :same
16
17
 
17
- def initialize(asset: , request_path: nil)
18
- self.request_path = request_path || asset.to_request_path
18
+ def initialize(asset: , node: , ext: "")
19
19
  @asset = asset
20
+ @node = node
21
+ @ext = ext # TODO: Meh, feels dirty but I suppose the thingy has to drop it in.
20
22
  end
21
23
 
22
- # When #dup or #clone is copied, the Resource
23
- # collection observer must be removed so there's
24
- # not duplicate resources.
25
- def initialize_copy(instance)
26
- instance.delete_observers
27
- super instance
24
+ def request_path
25
+ return unless node
26
+ # TODO: This `compact` makes me nervous. How can we handle this better?
27
+ lineage = node.parents.reverse.map(&:name).compact
28
+ file_name = [node.name, @ext].join
29
+ File.join("/", *lineage, file_name)
28
30
  end
29
31
 
30
- def request_path=(request_path)
31
- old_request_path = @request_path
32
- # We freeze the value to ensure users can't modify
33
- # the request_path string in place (e.g. Resource#request_path.capitalize!)
34
- # and throw the resource out of sync with the Resources collection.
35
- @request_path = request_path.to_s.freeze
36
- changed
37
- notify_observers self, old_request_path
32
+ def data
33
+ @data ||= asset.data
34
+ end
35
+
36
+ def body
37
+ @body ||= asset.body
38
38
  end
39
39
 
40
40
  def inspect
41
- "#<#{self.class}:0x#{(object_id << 1).to_s(16)} @request_path=#{@request_path.inspect} @asset=#{@asset.inspect}>"
41
+ "<#{self.class}:#{object_id} request_path=#{request_path.inspect} asset_path=#{@asset.path.to_s.inspect}>"
42
42
  end
43
43
 
44
- def data
45
- @data ||= asset.data
44
+ def parents(**args)
45
+ filter_resources(**args){ node.parents }
46
46
  end
47
47
 
48
- def body
49
- @body ||= asset.body
48
+ def siblings(**args)
49
+ filter_resources(**args){ node.siblings }
50
+ end
51
+
52
+ def children(**args)
53
+ filter_resources(**args){ node.children }
54
+ end
55
+
56
+ def ==(resource)
57
+ resource.request_path == request_path
50
58
  end
51
59
 
52
- def ==(asset)
53
- request_path == asset.request_path
60
+ private
61
+ # Filters parent/child/sibling resources by a type. The default behavior is to only return
62
+ # resources of the same type. For example given the pages `/a.html`, `/a.gif`, `/a/b.html`,
63
+ # if you query the parent from page `/a/b.html` you'd only get `/a.html` by default. If you
64
+ # query the parents via `parents(type: :all)` you'd get get [`/a.html`, `/a.gif`]
65
+ #
66
+ # TODO: When `type: :all` is scoped, some queries will mistakenly return single resources.
67
+ # :all should return an array of arrays to accurately represention levels.
68
+ #
69
+ # TODO: Put a better extension/mime_type handler into resource tree, then instead of faltening
70
+ # below and select, we could call a single map and pull out a resources
71
+ def filter_resources(type: DEFAULT_FILTER_SCOPE, &block)
72
+ return [] unless node
73
+ resources = block.call.map(&:resources)
74
+
75
+ case type
76
+ when :all
77
+ resources
78
+ when :same
79
+ resources.flatten.select { |r| r.ext == ext }
80
+ when String
81
+ resources.flatten.select { |r| r.ext == type }
82
+ when MIME::Type
83
+ resources.flatten.select { |r| r.mime_type == type }
84
+ else
85
+ raise ArgumentError, "Invalid type argument #{type}. Must be either :same, :all, an extension string, or a Mime::Type"
86
+ end
54
87
  end
55
88
  end
56
89
  end
@@ -0,0 +1,148 @@
1
+ module Mascot
2
+ # Resource nodes give resources their parent/sibling/child relationships. The relationship are determined
3
+ # by the `request_path` given to an asset when its added to a node. Given the `request_path` `/foo/bar/biz/buz.html`,
4
+ # a tree of resource nodes would be built named `foo`, `bar`, `biz`, `buz`. `foo` would be the "root" node and `buz`
5
+ # a leaf node. The actual `buz.html` asset is then stored on the leaf node as a resource. This tree structure
6
+ # makes it possible to reason through path relationships from code to build out elements in a website like tree navigation.
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
11
+
12
+ DELIMITER = "/".freeze
13
+
14
+ def initialize(parent: nil, delimiter: ResourcesNode::DELIMITER, name: nil)
15
+ @parent = parent
16
+ @name = name.freeze
17
+ @delimiter = delimiter.freeze
18
+ @children = Hash.new { |hash, key| hash[key] = ResourcesNode.new(parent: self, delimiter: delimiter, name: key) }
19
+ @resources = Hash.new
20
+ end
21
+
22
+ # Returns the immediate children nodes.
23
+ def children
24
+ @children.values
25
+ end
26
+
27
+ # Returns sibling nodes.
28
+ def siblings
29
+ parent ? parent.children.reject{ |c| c == self } : []
30
+ end
31
+
32
+ # Returns all parents up to the root node.
33
+ def parents
34
+ parents = []
35
+ node = parent
36
+ while node do
37
+ parents << node
38
+ node = node.parent
39
+ end
40
+ parents
41
+ end
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
+ def root?
52
+ parent.nil?
53
+ end
54
+
55
+ def leaf?
56
+ @children.empty?
57
+ end
58
+
59
+ def resources
60
+ @resources.values
61
+ end
62
+
63
+ def remove_resource(resource)
64
+ @resources.delete resource.ext #if @resources[resource.ext] == resource
65
+ end
66
+
67
+ def remove
68
+ if leaf?
69
+ # TODO: Check the parents to see if they also need to be removed if
70
+ # this call orphans the tree up to a resource.
71
+ parent.remove_child(name)
72
+ else
73
+ @resources.clear
74
+ end
75
+ end
76
+
77
+ def add(path: , asset: )
78
+ head, *path = tokenize(path)
79
+ if path.empty?
80
+ # When there's no more paths, we're left with the format (e.g. ".html")
81
+ add_format(asset: asset, ext: head)
82
+ else
83
+ @children[head].add(path: path, asset: asset)
84
+ end
85
+ end
86
+ alias :[]= :add
87
+
88
+ def get_resource(path)
89
+ *path, ext = tokenize(path)
90
+ if node = dig(*path)
91
+ node.get_format(ext: ext)
92
+ end
93
+ end
94
+
95
+ def inspect
96
+ "<#{self.class}: resources=#{resources.map(&:request_path)} children=#{children.map(&:name).inspect}>"
97
+ end
98
+
99
+ def get(path)
100
+ *path, ext = tokenize(path)
101
+ dig(*path)
102
+ end
103
+ alias :[] :get
104
+
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
122
+ end
123
+
124
+ # TODO: I don't really like how the path is broken up with the "ext" at the end.
125
+ # It feels inconsistent. Either make an object/struct that encaspulates this or
126
+ # just pass `index.html` through to the end.
127
+ def dig(*args)
128
+ head, *tail = args
129
+ if head.nil? and tail.empty?
130
+ self
131
+ elsif @children.has_key?(head)
132
+ @children[head].dig(*tail)
133
+ else
134
+ nil
135
+ end
136
+ end
137
+
138
+ private
139
+ # Returns all of the names for the path along with the format, if set.
140
+ def tokenize(path)
141
+ return path if path.respond_to? :to_a
142
+ path, _, file = path.gsub(/^\//, "").rpartition(@delimiter)
143
+ ext = File.extname(file)
144
+ file = File.basename(file, ext)
145
+ path.split(@delimiter).push(file).push(ext)
146
+ end
147
+ end
148
+ end
data/lib/mascot/site.rb CHANGED
@@ -22,11 +22,16 @@ module Mascot
22
22
  end
23
23
  end
24
24
 
25
+ def glob(glob)
26
+ paths = safe_root.glob(root.join(glob))
27
+ resources.select{ |r| paths.include? r.asset.path.to_s }
28
+ end
29
+
25
30
  # Returns a list of resources.
26
31
  def resources
27
- Resources.new(root_file_path: root).tap do |resources|
28
- assets.each { |a| resources.add_asset a }
29
- resources_pipeline.process resources
32
+ ResourcesNode.new.tap do |root_node|
33
+ assets.each { |a| root_node.add path: asset_path_to_request_path(a), asset: a }
34
+ resources_pipeline.process root_node
30
35
  end
31
36
  end
32
37
 
@@ -38,7 +43,7 @@ module Mascot
38
43
 
39
44
  # Find the page with a path.
40
45
  def get(request_path)
41
- resources.get(request_path)
46
+ resources.get_resource(request_path)
42
47
  end
43
48
 
44
49
  def root=(path)
@@ -50,6 +55,13 @@ module Mascot
50
55
  end
51
56
 
52
57
  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
+
53
65
  def safe_root
54
66
  SafeRoot.new(path: root)
55
67
  end
@@ -1,3 +1,3 @@
1
1
  module Mascot
2
- VERSION = "0.1.11"
2
+ VERSION = "0.1.12"
3
3
  end
data/lib/mascot.rb CHANGED
@@ -13,8 +13,8 @@ module Mascot
13
13
  autoload :Asset, "mascot/asset"
14
14
  autoload :Frontmatter, "mascot/frontmatter"
15
15
  autoload :ResourcesPipeline, "mascot/resources_pipeline"
16
- autoload :Resources, "mascot/resources"
17
16
  autoload :Resource, "mascot/resource"
17
+ autoload :ResourcesNode, "mascot/resources_node"
18
18
  autoload :SafeRoot, "mascot/safe_root"
19
- autoload :Site, "mascot/site"
19
+ autoload :Site, "mascot/site"
20
20
  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.11
4
+ version: 0.1.12
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-10 00:00:00.000000000 Z
11
+ date: 2016-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -79,7 +79,7 @@ files:
79
79
  - lib/mascot/extensions/proc_manipulator.rb
80
80
  - lib/mascot/frontmatter.rb
81
81
  - lib/mascot/resource.rb
82
- - lib/mascot/resources.rb
82
+ - lib/mascot/resources_node.rb
83
83
  - lib/mascot/resources_pipeline.rb
84
84
  - lib/mascot/safe_root.rb
85
85
  - lib/mascot/site.rb
@@ -1,103 +0,0 @@
1
- require "forwardable"
2
- require "pathname"
3
-
4
- module Mascot
5
- class Resources
6
- include Enumerable
7
-
8
- extend Forwardable
9
- def_delegators :@routes, :size, :empty?, :any?, :clear
10
-
11
- def initialize(root_file_path: )
12
- @routes = Hash.new
13
- @root_file_path = Pathname.new(root_file_path)
14
- end
15
-
16
- def each(&block)
17
- @routes.values.each(&block)
18
- end
19
-
20
- def last
21
- @routes.values.last
22
- end
23
-
24
- def request_paths
25
- @routes.keys
26
- end
27
-
28
- def glob(pattern = "**/**")
29
- paths = safe_root.glob @root_file_path.join(pattern)
30
- select { |r| paths.include? r.asset.path.to_s}
31
- end
32
-
33
- def get(request_path)
34
- return if request_path.nil?
35
- @routes[key(request_path)]
36
- end
37
-
38
- def add(resource)
39
- validate_request_path resource
40
- validate_uniqueness resource
41
-
42
- resource.add_observer self
43
- @routes[key(resource)] = resource
44
- end
45
-
46
- def update(resource, old_request_path)
47
- validate_request_path old_request_path
48
- validate_request_path resource
49
- validate_uniqueness resource
50
-
51
- @routes.delete key(old_request_path)
52
- @routes[key(resource)] = resource
53
- end
54
-
55
- def remove(resource)
56
- validate_request_path resource
57
- resource.delete_observer self
58
- @routes.delete key(resource)
59
- resource
60
- end
61
-
62
- def add_asset(asset, request_path: nil)
63
- add Resource.new asset: asset, request_path: asset_path_to_request_path(request_path || asset.to_request_path)
64
- end
65
-
66
- private
67
- def key(path)
68
- # TODO: Conslidate this into SafeRoot.
69
- File.join "/", validate_request_path(coerce_request_path(path))
70
- end
71
-
72
- def coerce_request_path(resource)
73
- resource.respond_to?(:request_path) ? resource.request_path : resource
74
- end
75
-
76
- def validate_request_path(path)
77
- path = coerce_request_path(path)
78
- raise InvalidRequestPathError, "path can't be nil" if path.nil?
79
- path
80
- end
81
-
82
- # Raise an exception if the user tries to add a Resource with an existing request path.
83
- def validate_uniqueness(resource)
84
- path = coerce_request_path(resource)
85
- if existing_resource = get(path)
86
- raise ExistingRequestPathError, "Resource #{existing_resource} already exists at #{path}"
87
- else
88
- resource
89
- end
90
- end
91
-
92
- # Given a @file_path of `/hi`, this method changes `/hi/there/friend.html.erb`
93
- # to an absolute `/there/friend` format by removing the file extensions
94
- def asset_path_to_request_path(path)
95
- # Relative path of resource to the file_path of this project.
96
- relative_path = Pathname.new(path).relative_path_from(@root_file_path)
97
- end
98
-
99
- def safe_root
100
- @safe_root ||= SafeRoot.new(path: @root_file_path)
101
- end
102
- end
103
- end