yarrow 0.9.2 → 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: ee0d0048f5ad41b5bd0d8c0cb49059728e2920217876618294cc08997e450914
4
- data.tar.gz: e12fc19f4d9e973e522ae20e995bddf81c1e21861cfa26136cc3f03acf7f20b0
3
+ metadata.gz: 9312b361a125e332c4283433f1b26d29eda2a66c19b08023778eb77ea4b7e297
4
+ data.tar.gz: 04d6809bb61575cb508b0abb87de095e5bc8c861261177558eb602c95f6913a1
5
5
  SHA512:
6
- metadata.gz: 81d64f248a17176116104d16f3d1e1a718aa5aa68eced9cc5eefa5ae8fb2d050bf43532c378a131010eab80125cb63360704790d62687287669f1a8c96bf91e3
7
- data.tar.gz: 1f3e90865e4a947a1615eb0ea190a35f7cf4b8d3b51de6a1e1d9b78391f7e47d7a480153aacdef51028debca6d6eeb58222017c480cb1c66b143e361fa762bb6
6
+ metadata.gz: 2c9869456f8255277347e442693d68e894b4af657f82ecba0024a9b3d624cfd4393e83f30f4e98f8cb3897bbbc36c15fcd564060fc940a0de727c193f38ceb3b
7
+ data.tar.gz: 6ed205652f8a844e87d6c9c5c447d34a65e7e26bfcdc661b742ff26079796082b8b1fcf7fd7d521d37885f79ea83342dcf07e605e64ee5d84d1a8a447d556266
@@ -10,10 +10,10 @@ jobs:
10
10
  fail-fast: false
11
11
  matrix:
12
12
  os: [ubuntu-latest, macos-latest]
13
- ruby: [2.7, 3.1, 3.2]
13
+ ruby: [3.1, 3.2, "3.3.0-preview1"]
14
14
  runs-on: ${{ matrix.os }}
15
15
  steps:
16
- - uses: actions/checkout@v2
16
+ - uses: actions/checkout@v4
17
17
  - name: Set up Ruby
18
18
  uses: ruby/setup-ruby@v1
19
19
  with:
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
- class Content < Yarrow::Schema::Entity[:__config_content]
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
 
62
- class Output < Yarrow::Schema::Entity[:__config_output]
63
- attribute :generator, :string
64
- attribute :template_dir, :path
65
- #attribute :scripts, :array
117
+ # Output configuration block
118
+ class Output < Yarrow::Schema::Entity
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
- attribute :content, :__config_content
75
- attribute :output, :__config_output
136
+ attribute :content, :yarrow_config_content
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
@@ -20,26 +20,24 @@ module Yarrow
20
20
  end
21
21
 
22
22
  def inherited(class_name)
23
+ class_type = Yarrow::Schema::Types::Instance.of(class_name).accept(Hash)
24
+
23
25
  if @label
24
- class_type = Yarrow::Schema::Types::Instance.of(class_name)
25
- Yarrow::Schema::Definitions.register(@label, class_type)
26
+ label = @label
26
27
  @label = nil
28
+ else
29
+ label = Yarrow::Symbols.from_const(class_name)
27
30
  end
31
+
32
+ Yarrow::Schema::Definitions.register(label, class_type)
28
33
  end
29
34
  end
30
35
 
31
- def initialize(config)
36
+ def initialize(config, context=nil)
32
37
  converted = dictionary.cast(config)
33
38
 
34
39
  converted.each_pair do |key, value|
35
- # raise "#{key} not a declared attribute" unless dictionary.key?(key)
36
- #
37
- # defined_type = dictionary[key]
38
- #
39
- # unless value.is_a?(defined_type)
40
- # raise "#{key} accepts #{defined_type} but #{value.class} given"
41
- # end
42
-
40
+ # TODO: should we represent this as an attribute set rather than instance vars?
43
41
  instance_variable_set("@#{key}", value)
44
42
  end
45
43
  end
@@ -48,7 +46,15 @@ module Yarrow
48
46
  dictionary.attr_names.reduce({}) do |attr_dict, name|
49
47
  value = instance_variable_get("@#{name}")
50
48
 
51
- 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)
52
58
  value.to_h
53
59
  else
54
60
  value