yarrow 0.9.2 → 0.9.4

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.
@@ -53,14 +53,37 @@ module Yarrow
53
53
  accepts.key?(input.class)
54
54
  end
55
55
 
56
- def coerce(input)
56
+ # Coerce input into the defined format based on configured
57
+ # accept rules.
58
+ #
59
+ # Will use the unit type’s constructor by default unless
60
+ # an alternative factory method and constructor arguments are
61
+ # provided in the accept rule or passed in via context.
62
+ #
63
+ # Most object coercions use the defaults, but the context
64
+ # and options enable specific customisations, such as passing
65
+ # in an assigned attribute name or the hash key that the input
66
+ # is a value of.
67
+ #
68
+ # @param [Object] input
69
+ # @param [nil, Hash] context
70
+ #
71
+ # @return [Object] instance of unit type represented by this wrapper
72
+ def coerce(input, context=nil)
57
73
  constructor, options = accepts[input.class]
58
74
 
59
- # TODO: should we clone all input so copy is stored rather than ref?
60
- if options.nil?
75
+ context_args = if context.nil?
76
+ options.clone
77
+ elsif options.nil?
78
+ context.clone
79
+ else
80
+ options.merge(context)
81
+ end
82
+
83
+ if context_args.nil?
61
84
  unit.send(constructor, input)
62
85
  else
63
- unit.send(constructor, input, options.clone)
86
+ unit.send(constructor, input, context_args)
64
87
  end
65
88
  end
66
89
 
@@ -88,14 +111,14 @@ module Yarrow
88
111
  end
89
112
  end
90
113
 
91
- def cast(input)
92
- return coerce(input) if should_coerce?(input)
114
+ def cast(input, context=nil)
115
+ return coerce(input, context) if should_coerce?(input)
93
116
  check(input)
94
117
  end
95
118
  end
96
119
 
97
120
  class Any < TypeClass
98
- def cast(input)
121
+ def cast(input, context=nil)
99
122
  input
100
123
  end
101
124
  end
@@ -141,39 +164,40 @@ module Yarrow
141
164
  end
142
165
  end
143
166
 
144
- module CompoundType
145
- def instance(unit_type)
146
- @unit = Instance.of(unit_type)
147
- self
167
+ class List < TypeClass
168
+ def self.any
169
+ # Constraint: must be array instance
170
+ new(Instance.of(Array), Any.new)
148
171
  end
149
172
 
150
- def kind(unit_type)
151
- @unit = Kind.of(unit_type)
152
- self
173
+ def self.of(wrapped_type)
174
+ new(Instance.of(Array), Instance.of(wrapped_type))
153
175
  end
154
176
 
155
- def interface(*args)
156
- @unit = Interface.of(args)
157
- self
158
- end
159
- end
177
+ attr_reader :element_type
178
+ alias container_type unit
160
179
 
161
- class List < TypeClass
162
- include CompoundType
180
+ def initialize(unit_type, element_type)
181
+ @element_type = element_type
182
+ super(unit_type)
183
+ end
163
184
 
164
- def self.of(unit_type)
165
- new(Instance.of(unit_type))
185
+ def accept_elements(accept_type)
186
+ element_type.accept(accept_type)
187
+ self
166
188
  end
167
189
 
168
- def cast(input)
169
- input.map do |item|
170
- unit.cast(item)
190
+ def check(input)
191
+ converted = container_type.cast(input)
192
+
193
+ converted.map do |item|
194
+ element_type.cast(item)
171
195
  end
172
196
  end
173
197
  end
174
198
 
175
199
  class Map < TypeClass
176
- include CompoundType
200
+ #include CompoundType
177
201
 
178
202
  def self.of(map_spec)
179
203
  if map_spec.is_a?(Hash)
@@ -198,15 +222,21 @@ module Yarrow
198
222
  super(value_type)
199
223
  end
200
224
 
225
+ def accept_elements(accept_type, constructor=:new, options=nil)
226
+ value_type.accept(accept_type, constructor, options)
227
+ self
228
+ end
229
+
201
230
  def check(input)
202
- keys = input.keys.map do |key|
203
- key_type.cast(key)
204
- end
205
- values = input.values.map do |value|
206
- value_type.cast(value)
231
+ result = {}
232
+
233
+ input.each_pair do |key, value|
234
+ checked_key = key_type.cast(key)
235
+ checked_value = value_type.cast(value, { key: checked_key })
236
+ result[checked_key] = checked_value
207
237
  end
208
238
 
209
- [keys, values].transpose.to_h
239
+ result
210
240
  end
211
241
  end
212
242
 
@@ -1,15 +1,20 @@
1
1
  module Yarrow
2
2
  module Schema
3
- # class Structure < Struct
4
- # def self.inherited(subclass)
5
- # unless subclass.name
6
- # puts "CLASS"
7
- # p caller_locations[3]
8
- # else
9
- # p subclass.name.downcase.to_sym
10
- # end
11
- # end
12
- # end
3
+ class ValueType < Struct
4
+ def self.register(label)
5
+ class_type = Yarrow::Schema::Types::Instance.of(self).accept(Hash)
6
+ Yarrow::Schema::Definitions.register(label, class_type)
7
+ self
8
+ end
9
+
10
+ # Automatically register when struct is defined as a class extension
11
+ # rather than anonymous struct class.
12
+ def self.inherited(subclass)
13
+ if subclass.name
14
+ self.register(subclass.name.downcase.to_sym)
15
+ end
16
+ end
17
+ end
13
18
 
14
19
  # Value object (with comparison by value equality). This just chucks back a
15
20
  # Ruby struct but wraps the constructor with method advice that handles
@@ -33,12 +38,16 @@ module Yarrow
33
38
 
34
39
  validator = Dictionary.new(fields_spec)
35
40
 
36
- struct = Struct.new(*slots_spec, keyword_init: true, &block)
41
+ struct = ValueType.new(*slots_spec, keyword_init: true, &block)
37
42
 
38
43
  struct.define_method :initialize do |*args, **kwargs|
39
44
  attr_values = if args.any?
40
45
  raise ArgumentError.new("cannot mix slots and kwargs") if kwargs.any?
41
- Hash[slots.zip(args)]
46
+ if args.first.instance_of?(Hash) and args.count == 1
47
+ args.first
48
+ else
49
+ Hash[slots.zip(args)]
50
+ end
42
51
  else
43
52
  kwargs
44
53
  end
@@ -19,6 +19,20 @@ module Yarrow
19
19
  Object.const_get(Strings::Case.pascalcase(atom.to_s).to_sym)
20
20
  end
21
21
 
22
+ # Converts a string name of class const to a symbol atom
23
+ #
24
+ # @param [Class, String, #to_s] const_obj
25
+ # @return [Symbol]
26
+ def self.from_const(const_obj)
27
+ const_lookup = if const_obj.respond_to?(:name)
28
+ const_obj.name
29
+ else
30
+ const_obj.to_s
31
+ end
32
+
33
+ Strings::Case.underscore(const_lookup).to_sym
34
+ end
35
+
22
36
  # @param [Symbol, String] atom
23
37
  # @return [Symbol]
24
38
  def self.to_singular(atom)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module Yarrow
3
3
  APP_NAME = "Yarrow"
4
- VERSION = "0.9.2"
4
+ VERSION = "0.9.4"
5
5
  end
@@ -1,5 +1,26 @@
1
- module Yarrow
1
+ module Yarrow
2
2
  module Web
3
+ class Document
4
+ attr_reader :content_node
5
+
6
+ def initialize(node, manifest)
7
+ @content_node = node
8
+ #manifest.add_document(self)
9
+ @manifest = manifest
10
+ end
11
+
12
+ def resource
13
+ content_node.props[:resource]
14
+ end
15
+
16
+ def url
17
+ @url ||= URL.generate(resource)
18
+ end
19
+ end
20
+ end
21
+
22
+ # Deprecated
23
+ module LegacyWeb
3
24
  class BaseDocument
4
25
  def resource
5
26
  @resource
@@ -1,5 +1,27 @@
1
1
  module Yarrow
2
2
  module Web
3
+ class NewManifest
4
+ def initialize
5
+ @documents_index = {}
6
+ @documents = []
7
+ end
8
+
9
+ attr_reader :documents
10
+
11
+ def add_resource(resource)
12
+ add_document(Document.new(resource, self))
13
+ end
14
+
15
+ def add_document(document)
16
+ if @documents_index.key?(document.url)
17
+ raise "#{document.url} already exists in manifest"
18
+ end
19
+
20
+ @documents << document
21
+ @documents_index[document.url] = @documents.count - 1
22
+ end
23
+ end
24
+
3
25
  class Manifest
4
26
  def self.build(graph)
5
27
  manifest = new
@@ -0,0 +1,29 @@
1
+ module Yarrow
2
+ module Web
3
+ module URI
4
+ end
5
+
6
+ class URL
7
+ def self.generate(resource)
8
+ path = if resource.respond_to?(:url)
9
+ resource.url
10
+ elsif resource.respond_to?(:permalink)
11
+ resource.permalink
12
+ else
13
+ # TODO: URL generation strategy
14
+ "/one/two/three"
15
+ end
16
+
17
+ new(path)
18
+ end
19
+
20
+ def initialize(path)
21
+ @path = path
22
+ end
23
+
24
+ def to_s
25
+ @path
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/yarrow.rb CHANGED
@@ -4,6 +4,7 @@ require "kramdown"
4
4
  require "toml-rb"
5
5
  require "mustache"
6
6
  require "parallel"
7
+ require "addressable/template"
7
8
 
8
9
  require "extensions/mementus"
9
10
 
@@ -28,11 +29,11 @@ require "yarrow/content/expansion/directory_merge"
28
29
  require "yarrow/content/expansion/traversal"
29
30
  require "yarrow/content/resource"
30
31
  require "yarrow/web/manifest"
32
+ require "yarrow/web/url"
31
33
  require "yarrow/web/document"
32
34
  require "yarrow/web/generator"
33
35
  require "yarrow/web/template"
34
- require "yarrow/output/context"
35
- require "yarrow/output/web/indexed_file"
36
+ require "yarrow/output/build"
36
37
  require "yarrow/content_map"
37
38
  require "yarrow/server"
38
39
  require "yarrow/server/livereload"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yarrow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Rickerby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-06 00:00:00.000000000 Z
11
+ date: 2024-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -297,12 +297,10 @@ files:
297
297
  - bin/yarrow-server
298
298
  - lib/extensions/mementus.rb
299
299
  - lib/yarrow.rb
300
- - lib/yarrow/assets.rb
301
- - lib/yarrow/assets/manifest.rb
302
- - lib/yarrow/assets/pipeline.rb
303
300
  - lib/yarrow/config.rb
304
301
  - lib/yarrow/configuration.rb
305
302
  - lib/yarrow/console_runner.rb
303
+ - lib/yarrow/content/collection.rb
306
304
  - lib/yarrow/content/expansion/aggregator.rb
307
305
  - lib/yarrow/content/expansion/basename_merge.rb
308
306
  - lib/yarrow/content/expansion/directory_merge.rb
@@ -330,8 +328,7 @@ files:
330
328
  - lib/yarrow/generator.rb
331
329
  - lib/yarrow/help.txt
332
330
  - lib/yarrow/logging.rb
333
- - lib/yarrow/output/context.rb
334
- - lib/yarrow/output/web/indexed_file.rb
331
+ - lib/yarrow/output/build.rb
335
332
  - lib/yarrow/process/expand_content.rb
336
333
  - lib/yarrow/process/extract_source.rb
337
334
  - lib/yarrow/process/project_manifest.rb
@@ -348,15 +345,14 @@ files:
348
345
  - lib/yarrow/schema/value.rb
349
346
  - lib/yarrow/server.rb
350
347
  - lib/yarrow/server/livereload.rb
351
- - lib/yarrow/source/graph.rb
352
348
  - lib/yarrow/symbols.rb
353
- - lib/yarrow/tools/front_matter.rb
354
349
  - lib/yarrow/version.rb
355
350
  - lib/yarrow/web/document.rb
356
351
  - lib/yarrow/web/generator.rb
357
352
  - lib/yarrow/web/manifest.rb
358
353
  - lib/yarrow/web/static_asset.rb
359
354
  - lib/yarrow/web/template.rb
355
+ - lib/yarrow/web/url.rb
360
356
  - yarrow.gemspec
361
357
  homepage: http://rubygemspec.org/gems/yarrow
362
358
  licenses:
@@ -1,118 +0,0 @@
1
- # TODO: where else is this used?
2
- require 'json'
3
-
4
- module Yarrow
5
- module Assets
6
- ##
7
- # Provides access to the bundle of compiled CSS and JS assets.
8
- #
9
- # This is currently based on the output structure of the JSON manifest file
10
- # generated by Sprockets, but this class isn't coupled to the Sprockets API
11
- # so could be used as a generic manifest reader.
12
- #
13
- # - `logical_path` represents the core named path to an asset sans version, eg: `main.css`
14
- # - `digest_path` represents the versioned instance of an asset with associated digest,
15
- # eg: `main-4362eea15558e73d3663de653cdeb81e.css`
16
- class Manifest
17
- ##
18
- # Initializes the manifest from a Sprockets-style JSON file.
19
- #
20
- # If no assets directory is given, looks for a manifest in the main output directory.
21
- #
22
- # @param config [Yarrow::Configuration]
23
- def initialize(config)
24
- raise Yarrow::ConfigurationError if config.assets.nil?
25
-
26
- if config.assets.output_dir
27
- manifest_path = Pathname.new(config.assets.output_dir) + config.assets.manifest_file
28
- else
29
- manifest_path = Pathname.new(config.output_dir) + config.assets.manifest_file
30
- end
31
-
32
- if File.exists?(manifest_path)
33
- manifest_data = JSON.parse(File.read(manifest_path))
34
-
35
- @manifest_index = if manifest_data.key?('assets')
36
- manifest_data['assets']
37
- else
38
- manifest_data
39
- end
40
- else
41
- @manifest_index = {}
42
- end
43
- end
44
-
45
- ##
46
- # True if the named asset exists.
47
- #
48
- # @param logical_path [String]
49
- # @return [Boolean]
50
- def exists?(logical_path)
51
- @manifest_index.key? logical_path
52
- end
53
-
54
- ##
55
- # Returns the generated digest path to a named asset.
56
- #
57
- # @param logical_path [String]
58
- # @return [String]
59
- def digest_path(logical_path)
60
- @manifest_index[logical_path]
61
- end
62
-
63
- ##
64
- # Returns the list of named assets in the manifest.
65
- #
66
- # @return [Array<String>]
67
- def logical_paths
68
- @manifest_index.keys
69
- end
70
-
71
- ##
72
- # Returns the list of generated digest paths in the manifest.
73
- #
74
- # @return [Array<String>]
75
- def digest_paths
76
- @manifest_index.values
77
- end
78
-
79
- ##
80
- # Returns the list of named CSS assets in the manifest.
81
- #
82
- # @return [Array<String>]
83
- def css_logical_paths
84
- select_by_extension(logical_paths, '.css')
85
- end
86
-
87
- ##
88
- # Returns the list of named JS assets in the manifest.
89
- #
90
- # @return [Array<String>]
91
- def js_logical_paths
92
- select_by_extension(logical_paths, '.js')
93
- end
94
-
95
- ##
96
- # Returns the list of generated CSS assets in the manifest.
97
- #
98
- # @return [Array<String>]
99
- def css_digest_paths
100
- select_by_extension(digest_paths, '.css')
101
- end
102
-
103
- ##
104
- # Returns the list of generated JS assets in the manifest.
105
- #
106
- # @return [Array<String>]
107
- def js_digest_paths
108
- select_by_extension(digest_paths, '.js')
109
- end
110
-
111
- private
112
-
113
- def select_by_extension(collection, ext)
114
- collection.select { |asset| asset.end_with?(ext) }
115
- end
116
- end
117
- end
118
- end
@@ -1,126 +0,0 @@
1
- require 'pathname'
2
- require 'fileutils'
3
- require 'sprockets'
4
-
5
- module Yarrow
6
- module Assets
7
- ##
8
- # Processes static assets using Sprockets.
9
- class Pipeline
10
-
11
- include Loggable
12
-
13
- attr_reader :input_dir, :output_dir, :append_paths, :bundles, :assets
14
-
15
- ##
16
- # @param config [Yarrow::Configuration]
17
- def initialize(config)
18
- raise Yarrow::ConfigurationError if config.assets.nil?
19
-
20
- @input_dir = config.assets.input_dir || default_input_dir
21
-
22
- if config.assets.output_dir
23
- @output_dir = config.assets.output_dir
24
- else
25
- @output_dir = config.output_dir || default_output_dir
26
- end
27
-
28
- @append_paths = []
29
-
30
- case config.assets.append_paths
31
- when Array
32
- @append_paths = config.assets.append_paths
33
- when '*'
34
- @append_paths = Dir[@input_dir + '/*'].select do |path|
35
- File.directory?(path)
36
- end.map do |path|
37
- File.basename(path)
38
- end
39
- when String
40
- @append_paths << config.assets.append_paths
41
- end
42
- end
43
-
44
- ##
45
- # Compiles an asset manifest and processed output files from the given input bundles.
46
- # Also generates a manifest linking each output bundle to its given input name.
47
- #
48
- # @param bundles [Array<String>]
49
- def compile(bundles = [])
50
- bundles.each do |bundle|
51
- if bundle.include? '*'
52
- Dir["#{@input_dir}/#{bundle}"].each do |asset|
53
- logger.info "Compiling: #{asset}"
54
- manifest.compile(File.basename(asset))
55
- end
56
- else
57
- logger.info "Compiling: #{bundle}"
58
- manifest.compile(bundle)
59
- end
60
- end
61
- end
62
-
63
- ##
64
- # Copy the given files to the output path without processing or renaming.
65
- #
66
- # @param bundle [Array<String>]
67
- def copy(bundles = [])
68
- bundles.each do |bundle|
69
- FileUtils.cp_r "#{@input_dir}/#{bundle}", "#{@output_dir}/#{bundle}"
70
- end
71
- end
72
-
73
- ##
74
- # Purges redundant compiled assets from the output path.
75
- #
76
- # @example Purge all assets except those created in the last 10 minutes
77
- # pipeline.purge(0, )
78
- #
79
- # @param keep [Integer] Number of previous revisions to keep. Defaults to 2.
80
- # @param age [Integer] Purge all assets older than this date. Defaults to 1 hour.
81
- def purge(keep = 2, age = 3600)
82
- # TODO: upgrade to Sprockets 3.0 to support the age arg
83
- manifest.clean(keep)
84
- end
85
-
86
- ##
87
- # Access instance of the Sprockets environment.
88
- #
89
- # @return [Sprockets::Environment]
90
- def environment
91
- # TODO: decouple dependency on Sprockets
92
- @environment ||= create_environment
93
- end
94
-
95
- private
96
-
97
- def default_input_dir
98
- "#{Dir.pwd}/assets"
99
- end
100
-
101
- def default_output_dir
102
- "#{Dir.pwd}/public/assets"
103
- end
104
-
105
- def manifest_file_path
106
- "#{@output_dir}/manifest.json"
107
- end
108
-
109
- def manifest
110
- Sprockets::Manifest.new(environment, manifest_file_path)
111
- end
112
-
113
- def create_environment
114
- environment = Sprockets::Environment.new(@input_dir)
115
-
116
- @append_paths.each do |path|
117
- environment.append_path path
118
- end
119
-
120
- environment
121
- end
122
-
123
- end
124
-
125
- end
126
- end
data/lib/yarrow/assets.rb DELETED
@@ -1,2 +0,0 @@
1
- require_relative 'assets/pipeline'
2
- require_relative 'assets/manifest'
@@ -1,16 +0,0 @@
1
- module Yarrow
2
- module Output
3
- # Provides a data context for rendering a template.
4
- #
5
- # Methods provided by this class become available as named variables in
6
- # Mustache templates.
7
- class Context
8
- def initialize(attributes)
9
- metaclass = class << self; self; end
10
- attributes.each do |name, value|
11
- metaclass.send :define_method, name, lambda { value }
12
- end
13
- end
14
- end
15
- end
16
- end