yarrow 0.9.3 → 0.9.4

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
  SHA256:
3
- metadata.gz: 2a2ebdee5113cc218a9744d5da88b01cb0e7604b4ecfe0d133f776d48d2b4b97
4
- data.tar.gz: 967ba36fec57253eea8069f903828b8529f43802578c8be009413e4a5373e1f1
3
+ metadata.gz: 9312b361a125e332c4283433f1b26d29eda2a66c19b08023778eb77ea4b7e297
4
+ data.tar.gz: 04d6809bb61575cb508b0abb87de095e5bc8c861261177558eb602c95f6913a1
5
5
  SHA512:
6
- metadata.gz: 982f7c783ed933419504e6722e9ffbacaf513468a16cdb07ca4ec7ffc859d5eaed1470948d8c1ca7ce5aa00f5e0fc05b9c29a38f0f0e2972aa6d2f348aec2c7b
7
- data.tar.gz: 4debe486afb56d7f664a0dd730db05429b2d7256a5f2789f1987339b394f404ac0f59f67d9e2f25126ad946675a46b7f910a91881d447afed148cb56eb4b5317
6
+ metadata.gz: 2c9869456f8255277347e442693d68e894b4af657f82ecba0024a9b3d624cfd4393e83f30f4e98f8cb3897bbbc36c15fcd564060fc940a0de727c193f38ceb3b
7
+ data.tar.gz: 6ed205652f8a844e87d6c9c5c447d34a65e7e26bfcdc661b742ff26079796082b8b1fcf7fd7d521d37885f79ea83342dcf07e605e64ee5d84d1a8a447d556266
data/README.md CHANGED
@@ -29,7 +29,7 @@ gem 'yarrow'
29
29
  Status
30
30
  ------
31
31
 
32
- Yarrow is an extraction from several existing private documentation projects. This repo is in alpha state, which means that many of the useful features are not yet folded into this codebase.
32
+ Yarrow is an extraction from several existing private documentation projects. ~~This repo is in alpha state, which means that many of the useful features are not yet folded into this codebase.~~ This repo is in flux as it is being used on several live projects, but lacks documentation and a unified command line tool.
33
33
 
34
34
  Yarrow is being slowly developed as a part-time project to scratch a few itches. New features and bugfixes are pushed straight to `main`, and releases of the Gem are kept more or less in sync with the planned roadmap.
35
35
 
@@ -40,12 +40,15 @@ A rough sketch of the project direction.
40
40
 
41
41
  | Version | Features |
42
42
  |---------|-------------------------------------------------|
43
- | `0.9` | Support standard text formats and linked assets |
44
- | `0.10` | Custom Markdown components |
45
- | `0.11` | Publishing support for S3 and GitHub/Netlify |
46
- | `0.12` | Clean up local web server and watcher |
47
- | `0.13` | Content structure transformations |
48
- | `1.0` | Reintroduce generic command line runner |
43
+ | `0.10` | Filename map expansion strategy |
44
+ | `0.11` | Directory merge expansion strategy |
45
+ | `0.12` | Basename merge expansion strategy |
46
+ | `0.13` | Resources and Assets vocabulary |
47
+ | `0.14` | Flatten namespaces and clean up modules |
48
+ | `0.15` | Web template mapping and markup generators |
49
+ | `0.16` | Document custom Markdown components |
50
+ | `0.17` | Publishing support for S3 and GitHub/Netlify |
51
+ | `0.18` | Clean up local web server and watcher |
49
52
 
50
53
  License
51
54
  -------
data/lib/yarrow/config.rb CHANGED
@@ -10,11 +10,11 @@ module Yarrow
10
10
  # For larger web publishing projects, this should be moved out into a
11
11
  # template context or language/translation files to make it editable
12
12
  # for a larger group of people.
13
- Meta = Yarrow::Schema::Value.new(
14
- :title,
15
- :author
13
+ class Meta < Yarrow::Schema::Entity
14
+ attribute :title, :string
15
+ attribute :author, :string
16
16
  # :copyright TODO: what other basic details to include?
17
- )
17
+ end
18
18
 
19
19
  # Dev server config. This is mainly useful if you want to set up a specific
20
20
  # chain of Rack middleware and handlers. If you don’t care about default
@@ -52,27 +52,90 @@ module Yarrow
52
52
  # :__config_source_map,
53
53
  # Yarrow::Schema::Types::Map.of(Symbol)
54
54
  # )
55
-
56
55
  class Content < Yarrow::Schema::Entity
57
56
  attribute :module, :string
58
57
  #attribute :source_map, :__config_source_map
59
58
  attribute :source_map, :hash
60
59
  end
60
+
61
+ # Template engine and site generator configuration block
62
+ class OutputGenerator < Yarrow::Schema::Entity
63
+ attribute :engine, :string
64
+ attribute :template_dir, :string
65
+ attribute :options, :hash
66
+ end
67
+
68
+ # Document mapping configuration block
69
+ class OutputManifest < Yarrow::Schema::Entity
70
+ attribute :layout, :string
71
+ attribute :scheme, :string
72
+
73
+ DEFAULT_URL_SCHEME = "/{ancestors*}"
74
+
75
+ def self.from_spec(input, context)
76
+ attrs = {}
77
+
78
+ if input.is_a?(TrueClass)
79
+ attrs[:layout] = context[:key].to_s
80
+ elsif input.is_a?(FalseClass)
81
+ raise "Reconciliation for content type #{context[:key]} cannot be skipped (yet)"
82
+ elsif input.is_a?(Symbol)
83
+ attrs[:layout] = input.to_s
84
+ elsif input.is_a?(String)
85
+ attrs[:layout] = context[:key].to_s
86
+ attrs[:scheme] = input
87
+ elsif input.is_a?(Hash)
88
+ attrs = input
89
+ else
90
+ raise "Invalid data for output manifest: #{input.class}"
91
+ end
92
+
93
+ unless attrs.key?(:scheme)
94
+ attrs[:scheme] = DEFAULT_URL_SCHEME
95
+ end
96
+
97
+ new(attrs)
98
+ end
99
+ end
100
+
101
+ # Define output document map type
102
+ Yarrow::Schema::Definitions.register(
103
+ :yarrow_config_output_manifest_spec,
104
+ Yarrow::Schema::Types::Map.of(Symbol => OutputManifest).accept_elements(Hash)
105
+ .accept_elements(TrueClass, :from_spec)
106
+ .accept_elements(FalseClass, :from_spec)
107
+ .accept_elements(Symbol, :from_spec)
108
+ .accept_elements(String, :from_spec)
109
+ )
110
+
111
+ # Manifest reconciliation configuration block
112
+ class OutputReconcile < Yarrow::Schema::Entity
113
+ attribute :match, :string
114
+ attribute :manifest, :yarrow_config_output_manifest_spec
115
+ end
61
116
 
117
+ # Output configuration block
62
118
  class Output < Yarrow::Schema::Entity
63
- attribute :generator, :string
64
- attribute :template_dir, :path
65
- #attribute :scripts, :array
119
+ attribute :target, :string
120
+ attribute :generator, :yarrow_config_output_generator
121
+ attribute :reconcile, :yarrow_config_output_reconcile
66
122
  end
67
123
 
124
+ # Define output schema list type
125
+ Yarrow::Schema::Definitions.register(
126
+ :yarrow_config_output_list,
127
+ Yarrow::Schema::Types::List.of(Output).accept_elements(Hash)
128
+ )
129
+
68
130
  # Top level root config namespace.
69
131
  class Instance < Yarrow::Schema::Entity
70
132
  attribute :source_dir, :path
71
133
  attribute :output_dir, :path
72
- attribute :meta, :any
134
+ attribute :meta, :yarrow_config_meta
73
135
  attribute :server, :any
74
136
  attribute :content, :yarrow_config_content
75
- attribute :output, :yarrow_config_output
137
+ attribute :output, :yarrow_config_output_list
138
+ #attribute :output, :any
76
139
  end
77
140
  #
78
141
  # `content_dir` and `output_dir` are placeholders and should be overriden
@@ -1,21 +1,11 @@
1
1
  module Yarrow
2
2
  class Configuration
3
3
  class << self
4
- # Merges the given configuration or hash-like object with the
5
- # registered global configuration.
6
- #
7
- # @param [Hash, Hashie::Mash, Yarrow::Configuration]
8
- #
9
- def merge(config)
10
- instance.deep_merge!(config)
11
- end
12
-
13
4
  # Loads a configuration object from the given YAML file.
14
5
  #
15
6
  # @param [String] path to YAML file
16
7
  #
17
8
  # @return [Yarrow::Config]
18
- #
19
9
  def load(file)
20
10
  coerce_config_struct(YAML.load(File.read(file), symbolize_names: true))
21
11
  end
@@ -34,12 +24,9 @@ module Yarrow
34
24
  # TODO: this should be folded into the schema machinery with type coercions
35
25
  def coerce_config_struct(config)
36
26
  meta_obj = if config.key?(:meta)
37
- Yarrow::Config::Meta.new(
38
- title: config[:meta][:title],
39
- author: config[:meta][:author]
40
- )
27
+ config[:meta]
41
28
  else
42
- nil
29
+ raise "missing :meta key in config"
43
30
  end
44
31
 
45
32
  server_obj = if config.key?(:server)
@@ -49,7 +36,7 @@ module Yarrow
49
36
  end
50
37
 
51
38
  content_obj = if config.key?(:content)
52
- Yarrow::Config::Content.new(config[:content])
39
+ config[:content]
53
40
  else
54
41
  Yarrow::Config::Content.new({
55
42
  module: "",
@@ -60,9 +47,9 @@ module Yarrow
60
47
  end
61
48
 
62
49
  output_obj = if config.key?(:output)
63
- Yarrow::Config::Output.new(config[:output])
50
+ config[:output]
64
51
  else
65
- Yarrow::Config::Output.new({ generator: "web", template_dir: "templates" })
52
+ raise "missing :output key in config"
66
53
  end
67
54
 
68
55
  # TODO: messy hack to get rid of Hashie::Mash, this should either be
@@ -0,0 +1,7 @@
1
+ module Yarrow
2
+ module Content
3
+ class Collection < Yarrow::Schema::Entity
4
+
5
+ end
6
+ end
7
+ end
@@ -36,7 +36,7 @@ module Yarrow
36
36
  }
37
37
  collection_node.label = :collection
38
38
  collection_node.props[:type] = type
39
- collection_node.props[:resource] = collection_const.new(attributes)
39
+ collection_node.props[:collection] = collection_const.new(attributes)
40
40
  end
41
41
 
42
42
  # Add this collection id to the lookup table for edge construction
@@ -52,7 +52,7 @@ module Yarrow
52
52
  end
53
53
  end
54
54
 
55
- def create_entity(source_node, parent_path, type, entity_const)
55
+ def create_resource(source_node, parent_path, type, entity_const)
56
56
  contents = Yarrow::Format.read(source_node.props[:path])
57
57
 
58
58
  # Create an entity node with attached resource model
@@ -63,7 +63,7 @@ module Yarrow
63
63
  body: contents.document.to_s
64
64
  }.merge(contents.metadata)
65
65
 
66
- entity_node.label = :entity
66
+ entity_node.label = :resource
67
67
  entity_node.props[:type] = type
68
68
  entity_node.props[:resource] = entity_const.new(attributes)
69
69
  end
@@ -15,7 +15,7 @@ module Yarrow
15
15
  def expand_file(entity, policy)
16
16
  if policy.match_by_extension(entity.props[:ext])
17
17
  parent_path = entity.incoming(:directory).first.props[:path]
18
- create_entity(entity, parent_path, policy.entity, policy.entity_const)
18
+ create_resource(entity, parent_path, policy.entity, policy.entity_const)
19
19
  end
20
20
  end
21
21
  end
@@ -1,40 +1,53 @@
1
+ Yarrow::Schema::Definitions.register(:extension_list, Yarrow::Schema::Types::List.of(String))
2
+
1
3
  module Yarrow
2
4
  module Content
3
- class Policy
5
+ class Policy < Yarrow::Schema::Entity
6
+ # TODO: document meaning and purpose of each attribute
7
+ attribute :container, :symbol
8
+ attribute :collection, :symbol
9
+ attribute :entity, :symbol
10
+ attribute :expansion, :symbol
11
+ attribute :extensions, :extension_list
12
+ attribute :source_path, :string
13
+ attribute :module_prefix, :string
14
+
4
15
  DEFAULT_EXPANSION = :filename_map
5
16
 
6
17
  DEFAULT_EXTENSIONS = [".md", ".yml", ".htm"]
7
18
 
8
19
  DEFAULT_SOURCE_PATH = "."
9
20
 
21
+ DEFAULT_MODULE_PREFIX = ""
22
+
10
23
  MODULE_SEPARATOR = "::"
11
24
 
12
25
  # Construct a content policy from the given source specification.
13
- def self.from_spec(policy_label, policy_props, module_prefix="")
26
+ def self.from_spec(policy_label, policy_props=DEFAULT_SOURCE_PATH, module_prefix=DEFAULT_MODULE_PREFIX)
14
27
  # TODO: validate length, structure etc
15
28
 
16
29
  # If the spec holds a symbol value then treat it as an entity mapping
17
30
  if policy_props.is_a?(Symbol)
18
- new(
19
- policy_label,
20
- policy_label,
21
- policy_props,
22
- DEFAULT_EXPANSION,
23
- DEFAULT_EXTENSIONS,
24
- policy_label.to_s,
25
- module_prefix
26
- )
31
+ new({
32
+ container: policy_label,
33
+ collection: policy_label,
34
+ entity: policy_props,
35
+ expansion: DEFAULT_EXPANSION,
36
+ extensions: DEFAULT_EXTENSIONS,
37
+ source_path: policy_label.to_s,
38
+ module_prefix: module_prefix
39
+ })
27
40
 
28
41
  # If the spec holds a string value then treat it as a source path mapping
29
42
  elsif policy_props.is_a?(String)
30
43
  new(
31
- policy_label,
32
- policy_label,
33
- Yarrow::Symbols.to_singular(policy_label),
34
- DEFAULT_EXPANSION,
35
- DEFAULT_EXTENSIONS,
36
- policy_props,
37
- module_prefix
44
+ container: policy_label,
45
+ collection: policy_label,
46
+ entity: Yarrow::Symbols.to_singular(policy_label),
47
+ expansion: DEFAULT_EXPANSION,
48
+ extensions: DEFAULT_EXTENSIONS,
49
+ source_path: policy_props,
50
+ module_prefix: module_prefix
38
51
  )
39
52
 
40
53
  # Otherwise scan through the spec and fill in any gaps
@@ -43,7 +56,8 @@ module Yarrow
43
56
  collection = if policy_props.key?(:collection)
44
57
  policy_props[:collection]
45
58
  else
46
- # If an entity name is provided use its plural for the container name
59
+ # If an entity name is provided use its plural for the container name.
60
+ # Otherwise fall back to a container name or policy label.
47
61
  if policy_props.key?(:entity)
48
62
  Yarrow::Symbols.to_plural(policy_props[:entity])
49
63
  else
@@ -51,7 +65,7 @@ module Yarrow
51
65
  end
52
66
  end
53
67
 
54
- # Use explicit container name if provided
68
+ # Use explicit container name if provided. Otherwise fall back to the collection name.
55
69
  container = if policy_props.key?(:container)
56
70
  policy_props[:container]
57
71
  else
@@ -91,44 +105,36 @@ module Yarrow
91
105
  end
92
106
 
93
107
  # Construct the new policy
94
- new(
95
- container,
96
- collection,
97
- entity,
98
- expansion,
99
- extensions,
100
- source_path,
101
- module_prefix
102
- )
108
+ new({
109
+ container: container,
110
+ collection: collection,
111
+ entity: entity,
112
+ expansion: expansion,
113
+ extensions: extensions,
114
+ source_path: source_path,
115
+ module_prefix: module_prefix
116
+ })
103
117
  end
104
118
  end
105
119
 
106
- attr_reader :container, :collection, :entity, :expansion, :extensions, :source_path, :module_prefix
107
-
108
- def initialize(container, collection, entity, expansion, extensions, source_path, module_prefix)
109
- @container = container
110
- @collection = collection
111
- @entity = entity
112
- @expansion = expansion.to_sym
113
- @extensions = extensions
114
- @source_path = source_path
115
- @module_prefix = module_prefix.split(MODULE_SEPARATOR)
120
+ def module_prefix_parts
121
+ module_prefix.split(MODULE_SEPARATOR)
116
122
  end
117
123
 
118
124
  def container_const
119
- @container_const ||= Yarrow::Symbols.to_module_const([*module_prefix, container])
125
+ @container_const ||= Yarrow::Symbols.to_module_const([*module_prefix_parts, container])
120
126
  end
121
127
 
122
128
  def collection_const
123
129
  begin
124
- @collection_const ||= Yarrow::Symbols.to_module_const([*module_prefix, collection])
130
+ @collection_const ||= Yarrow::Symbols.to_module_const([*module_prefix_parts, collection])
125
131
  rescue NameError
126
132
  raise NameError, "cannot map undefined entity `#{collection}`"
127
133
  end
128
134
  end
129
135
 
130
136
  def entity_const
131
- @entity_const ||= Yarrow::Symbols.to_module_const([*module_prefix, entity])
137
+ @entity_const ||= Yarrow::Symbols.to_module_const([*module_prefix_parts, entity])
132
138
  end
133
139
 
134
140
  def aggregator_const
@@ -11,8 +11,21 @@ content:
11
11
  pages:
12
12
  expansion: tree
13
13
  output:
14
- generator: web
15
- template_dir: templates
14
+ - target: web
15
+ generator:
16
+ engine: __PENDING_LIBRARY_DECISION__
17
+ template_dir: templates
18
+ options:
19
+ error_mode: strict
20
+ reconcile:
21
+ match: collection
22
+ manifest:
23
+ collection:
24
+ layout: collection
25
+ scheme: /{ancestor_path}
26
+ resource:
27
+ layout: resource
28
+ scheme: /{ancestor_path}/{name}
16
29
  assets:
17
30
  input_dir: assets
18
31
  output_dir: public/assets
@@ -0,0 +1,37 @@
1
+ module Yarrow
2
+ module Output
3
+ class Build
4
+ attr_reader :graph, :output_config
5
+
6
+ def initialize(graph, output_config)
7
+ @graph = graph
8
+ @output_config = output_config
9
+ end
10
+
11
+ def reconcile
12
+ case output_config.reconcile.match
13
+ when "collection/resource" then traverse_by_path(:collection, :resource)
14
+ when "collection" then traverse_by_label(:collection)
15
+ when "resource" then traverse_by_label(:resource)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def traverse_by_label(label)
22
+ graph.n(label).each do |node|
23
+ puts "§ #{label} node #{node.label} #{node.props}"
24
+ end
25
+ end
26
+
27
+ def traverse_by_path(parent_label, child_label)
28
+ graph.n(parent_label).each do |collection_node|
29
+ puts "§ #{parent_label} node #{collection_node.label} #{collection_node.props}"
30
+ collection_node.out(child_label).each do |resource_node|
31
+ puts "§ #{child_label} node #{resource_node.label} #{resource_node.props}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -7,7 +7,7 @@ module Yarrow
7
7
  symbol: Types::Instance.of(Symbol).accept(String, :to_sym),
8
8
  path: Types::Instance.of(Pathname).accept(String),
9
9
  any: Types::Any.new,
10
- array: Types::List.of(Types::Any),
10
+ array: Types::List.any,
11
11
  hash: Types::Instance.of(Hash),
12
12
  markdown: Types::Instance.of(Kramdown::Document).accept(String)
13
13
  }
@@ -23,11 +23,12 @@ module Yarrow
23
23
  @attrs_spec.keys
24
24
  end
25
25
 
26
- def cast(input)
26
+ def cast(input, context=nil)
27
27
  missing_attrs = @attrs_spec.keys.difference(input.keys)
28
28
 
29
29
  if missing_attrs.any?
30
30
  missing_attrs.each do |name|
31
+ # TODO: add optional check
31
32
  raise "#{missing_attrs} wrong number of attributes" unless @attrs_spec[name].is_a?(Types::Any)
32
33
  end
33
34
  end
@@ -37,7 +38,7 @@ module Yarrow
37
38
  raise "attribute #{mismatching_attrs} does not exist" if mismatching_attrs.any?
38
39
 
39
40
  input.reduce({}) do |converted, (name, value)|
40
- converted[name] = @attrs_spec[name].cast(value)
41
+ converted[name] = @attrs_spec[name].cast(value, context)
41
42
  converted
42
43
  end
43
44
  end
@@ -33,7 +33,7 @@ module Yarrow
33
33
  end
34
34
  end
35
35
 
36
- def initialize(config)
36
+ def initialize(config, context=nil)
37
37
  converted = dictionary.cast(config)
38
38
 
39
39
  converted.each_pair do |key, value|
@@ -46,7 +46,15 @@ module Yarrow
46
46
  dictionary.attr_names.reduce({}) do |attr_dict, name|
47
47
  value = instance_variable_get("@#{name}")
48
48
 
49
- attr_dict[name] = if value.respond_to?(:to_h)
49
+ attr_dict[name] =if value.is_a?(Array)
50
+ value.map do |entry|
51
+ if entry.respond_to?(:to_h)
52
+ entry.to_h
53
+ else
54
+ entry
55
+ end
56
+ end
57
+ elsif value.respond_to?(:to_h)
50
58
  value.to_h
51
59
  else
52
60
  value
@@ -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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module Yarrow
3
3
  APP_NAME = "Yarrow"
4
- VERSION = "0.9.3"
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.3
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-14 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
@@ -1,45 +0,0 @@
1
- module Yarrow
2
- module Output
3
- module Web
4
- class IndexedFile
5
- WRITE_MODE = 'w+:UTF-8'.freeze
6
-
7
- # @return [String] Basename reflecting the server convention (usually: index.html)
8
- def index_name
9
- @index_name ||= config.index_name || 'index.html'
10
- end
11
-
12
- # @return [String] Docroot of the output target
13
- def docroot
14
- @docroot ||= config.output_dir || 'public'
15
- end
16
-
17
- # Write an output file to the specified path under the docroot.
18
- #
19
- # @param path [String]
20
- # @param content [String]
21
- def write(path, content)
22
- # If the target path is a directory,
23
- # generate a default index filename.
24
- if path[path.length-1] == '/'
25
- path = "#{path}#{index_name}"
26
- end
27
-
28
- target_path = Pathname.new("#{docroot}#{path}")
29
-
30
- ensure_dir_exists!(target_path.dirname)
31
-
32
- File.open(target_path.to_s, WRITE_MODE) do |file|
33
- file.puts(content)
34
- end
35
- end
36
-
37
- def ensure_dir_exists!(target)
38
- unless File.directory?(target)
39
- FileUtils.mkdir_p(target)
40
- end
41
- end
42
- end
43
- end
44
- end
45
- end
@@ -1,6 +0,0 @@
1
- module Yarrow
2
- module Source
3
- class Graph
4
- end
5
- end
6
- end
@@ -1,37 +0,0 @@
1
- module Yarrow
2
- module Tools
3
- # @deprecated
4
- # Maintained here as it is still used in a number of places but needs to be removed soon
5
- module FrontMatter
6
-
7
- def read_split_content(path, options={})
8
- extract_split_content(File.read(path, :encoding => 'utf-8'), options)
9
- end
10
-
11
- def extract_split_content(text, options={})
12
- pattern = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
13
- if text =~ pattern
14
- content = text.sub(pattern, "")
15
-
16
- begin
17
- if options.key?(:symbolize_keys)
18
- meta = YAML.load($1, symbolize_names: true)
19
- else
20
- meta = YAML.load($1)
21
- end
22
- return [content, meta]
23
- rescue Psych::SyntaxError => error
24
- if defined? ::Logger
25
- # todo: application wide logger
26
- #logger = ::Logger.new(STDOUT)
27
- #logger.error "#{error.message}"
28
- end
29
- return [content, nil]
30
- end
31
- end
32
-
33
- [text, nil]
34
- end
35
- end
36
- end
37
- end