yarrow 0.6.0 → 0.7.0

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: 7b1a14b73412ec27fad2ff75bd7944f1ef25f241779c532d3449339a436e4c32
4
- data.tar.gz: 2cf2db75cc8c107b1d1bcbb6a79ad7fd92a6ba32e27413f2057158f7b8bb3c8a
3
+ metadata.gz: ff50d9afd65a1ab1f8fd668c7db4acc0ceacabc095bccd65b384c2b47f8e7eb0
4
+ data.tar.gz: cb0f022b2163753231e701b98e1da2da2ee963b61cd635031f4128b7ce6034be
5
5
  SHA512:
6
- metadata.gz: 436cec2447208c2641d4ac332cbb1910b7b016983a0630782282a28fe34e7a929b4661f424f0002d937854316d00f6d822ec05407f40185a7d5bbd642a2b88d2
7
- data.tar.gz: bd798c5d999b7d2665209778d507bfe9b54da98473850dd66b62c60391a270f93aa00d913315a7b29e6c92e7cb436f64fb89b692ecb4ea2da8bad5387d43b0b6
6
+ metadata.gz: 93f4705d0556fee8b22ea19ca0a42a272a85ec52b7ced0ab408072147b27789173465c5ea0bcb4f95787da01499499f16d57971b325de5af3bb2d0a1b1f8ccf4
7
+ data.tar.gz: 59df4131a12acd9bd7ea051c1e308d17b99cd391e63e07b5582340f8bcd7e2be1216b98a32e2b4a86d8b88278dd11505d7d0b689997d249ca30057222ca4cc4e
@@ -1,16 +1,16 @@
1
- name: Ruby
1
+ name: ruby
2
2
  on:
3
3
  push:
4
- branches: [ master ]
4
+ branches: [ main ]
5
5
  pull_request:
6
- branches: [ master ]
6
+ branches: [ main ]
7
7
  jobs:
8
8
  test:
9
9
  strategy:
10
10
  fail-fast: false
11
11
  matrix:
12
12
  os: [ubuntu-latest, macos-latest]
13
- ruby: [2.7, '3.0', jruby]
13
+ ruby: [2.7, '3.0', jruby-head]
14
14
  runs-on: ${{ matrix.os }}
15
15
  steps:
16
16
  - uses: actions/checkout@v2
data/README.md CHANGED
@@ -1,17 +1,15 @@
1
1
  Yarrow
2
2
  ======
3
3
 
4
- [![Gem Version](http://img.shields.io/gem/v/yarrow.svg)][gem]
5
- [![Build Status](http://img.shields.io/github/workflow/status/:user/maetl/yarrow/workflow)][github]
6
- [![Coverage Status](http://img.shields.io/coveralls/maetl/yarrow.svg)][coveralls]
4
+ [![Gem Version](https://img.shields.io/gem/v/yarrow.svg)][gem]
5
+ [![Build Status](https://img.shields.io/github/workflow/status/maetl/yarrow/ruby/main)][github]
7
6
 
8
7
  [gem]: https://rubygems.org/gems/yarrow
9
8
  [github]: https://github.com/maetl/yarrow
10
- [coveralls]: https://coveralls.io/r/maetl/yarrow
11
9
 
12
- Yarrow is a tool for generating well structured documentation from a variety of input sources.
10
+ Yarrow is a framework for generating well structured publishing outputs from a variety of input sources.
13
11
 
14
- Unlike most static site generators and code documentation tools, Yarrow is written with design and content-strategy in mind. It does not impose its own structure on your content. This makes it appropriate for building static sites and blogs as well as style guides and API docs.
12
+ Unlike most static site generators and code documentation tools, Yarrow is written with design and content-strategy in mind. It does not impose its own structure on your content. This makes it useful for building style guides, technical docs and complex ebooks as well as static sites and blogs.
15
13
 
16
14
  Installation
17
15
  ------------
@@ -33,7 +31,7 @@ Status
33
31
 
34
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.
35
33
 
36
- Yarrow is being slowly developed as a part-time project to scratch a few itches. New features and bugfixes are pushed straight to master, and releases of the Gem are kept more or less in sync with the planned roadmap.
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.
37
35
 
38
36
  Roadmap
39
37
  -------
@@ -42,13 +40,13 @@ A rough sketch of the project direction.
42
40
 
43
41
  | Version | Features |
44
42
  |---------|----------|
45
- | `0.3` | **[Local dev server and asset pipeline](https://github.com/maetl/yarrow/issues/48)** |
46
- | `0.4` | Default media type mapping, collector, markup converters |
47
- | `0.5` | Content model/object mapping, template/site context |
48
- | `0.6` | HTML tag helpers, default layout templates |
49
- | `0.7` | Rake integration, task library |
50
- | `0.8` | Generic command line runner |
51
- | `0.9` | Refactoring, performance fixes, lock down API |
43
+ | `0.6` | Default media type mapping, graph collectors, markup converters |
44
+ | `0.7` | Content model/object mapping, template/site context |
45
+ | `0.8` | HTML publishing workflow |
46
+ | `0.9` | PDF publishing workflow |
47
+ | `0.10` | Media and video publishing workflow |
48
+ | `0.11` | Generic command line runner |
49
+ | `1.0` | Refactoring, performance fixes, lock down API |
52
50
 
53
51
  License
54
52
  -------
data/lib/yarrow.rb CHANGED
@@ -1,28 +1,29 @@
1
- require 'hashie'
1
+ require 'pathname'
2
2
  require 'yaml'
3
- require 'active_support/inflector'
4
3
 
5
4
  require 'yarrow/version'
5
+ require 'yarrow/extensions'
6
+ require 'yarrow/symbols'
6
7
  require 'yarrow/logging'
8
+ require 'yarrow/schema'
9
+ require 'yarrow/config'
7
10
  require 'yarrow/configuration'
8
11
  require 'yarrow/console_runner'
9
12
  require 'yarrow/generator'
10
- require 'yarrow/assets'
11
13
  require 'yarrow/tools/front_matter'
14
+ require 'yarrow/tools/content_utils'
12
15
  require 'yarrow/content/graph'
13
16
  require 'yarrow/content/content_type'
14
17
  require 'yarrow/content/source'
15
18
  require 'yarrow/content/source_collector'
16
19
  require 'yarrow/content/collection_expander'
17
- require 'yarrow/html/asset_tags'
18
20
  require 'yarrow/output/mapper'
19
21
  require 'yarrow/output/generator'
20
22
  require 'yarrow/output/context'
23
+ require 'yarrow/output/web/indexed_file'
21
24
  require 'yarrow/content_map'
22
- require 'yarrow/html'
23
25
  require 'yarrow/server'
24
26
  require 'yarrow/server/livereload'
25
- require 'yarrow/schema'
26
27
 
27
28
  require 'yarrow/process/workflow'
28
29
  require 'yarrow/process/step_processor'
@@ -0,0 +1,60 @@
1
+ # Replacement for the legacy Hashie::Mash/Module mixin configuration
2
+ # pattern. This provides the same API (chaining nested attribute calls) but
3
+ # handles schema validation and doesn’t pollute other namespaces.
4
+ module Yarrow
5
+ module Config
6
+ # Basic defaults which can be used to populate HTML metadata as well as
7
+ # used in CLI listings and other generated docs. The default H1 if no
8
+ # specific output config or template themes are provided.
9
+ #
10
+ # For larger web publishing projects, this should be moved out into a
11
+ # template context or language/translation files to make it editable
12
+ # for a larger group of people.
13
+ Meta = Yarrow::Schema::Value.new(
14
+ :title,
15
+ :author
16
+ # :copyright TODO: what other basic details to include?
17
+ )
18
+
19
+ # Dev server config. This is mainly useful if you want to set up a specific
20
+ # chain of Rack middleware and handlers. If you don’t care about default
21
+ # directory indexes or port handling, you can completely ignore this.
22
+ #
23
+ # There are many better live reloading options available in JS, so the Rack
24
+ # infrastructure here should be ignored for UI-heavy jobs. It’s otherwise
25
+ # fine for slower-paced general purpose web publishing.
26
+ #
27
+ # The default index config could possibly move into a dedicated namespace
28
+ # in future if it makes sense to use the underlying graph infrastructure
29
+ # as a live lookup rather than a compiler-generated artifact. This would
30
+ # mean rather than doing a ls on the directory, the index pages would
31
+ # grab a list of entries out of a graph projection for the directory.
32
+ Server = Yarrow::Schema::Value.new(
33
+ :live_reload,
34
+ :auto_index,
35
+ :default_index,
36
+ :default_type,
37
+ :port,
38
+ :host,
39
+ :handler,
40
+ :docroot,
41
+ :middleware,
42
+ :root_dir
43
+ )
44
+
45
+ # Top level root config namespace. Source, content and output are directory
46
+ # paths and should be the only required defaults for a complete batch run.
47
+ #
48
+ # Additional server config is optional and only needed if running the dev
49
+ # server locally.
50
+ #
51
+ # TODO: meta should be union of Type::Optional and Config::Meta
52
+ Instance = Yarrow::Schema::Value.new(
53
+ source: Pathname,
54
+ content: Pathname,
55
+ output_dir: Pathname,
56
+ meta: Yarrow::Schema::Type::Any,
57
+ server: Yarrow::Schema::Type::Any
58
+ )
59
+ end
60
+ end
@@ -1,50 +1,6 @@
1
1
  module Yarrow
2
- ##
3
- # Hash-like object containing runtime configuration values. Can be accessed indifferently
4
- # via object style lookups or symbol keys.
5
- #
6
- class Configuration < Hashie::Mash
2
+ class Configuration
7
3
  class << self
8
- ##
9
- # Provides access to the registered global configuration.
10
- #
11
- # If no configuration is registered, returns a blank object.
12
- #
13
- # @return [Yarrow::Configuration]
14
- #
15
- def instance
16
- @@configuration ||= self.new
17
- end
18
-
19
- ##
20
- # Loads and registers a global configuration instance.
21
- #
22
- # This will reset any previously initialized configuration.
23
- #
24
- # @param [String] path to YAML file
25
- #
26
- def register(file)
27
- @@configuration = load(file)
28
- end
29
-
30
- ##
31
- # Loads and registers a global configuration instance with
32
- # library-provided defaults.
33
- #
34
- # This will reset any previously initialized configuration.
35
- #
36
- def register_defaults
37
- @@configuration = load_defaults
38
- end
39
-
40
- ##
41
- # Clears the global configuration to the empty default.
42
- #
43
- def clear
44
- @@configuration = self.new
45
- end
46
-
47
- ##
48
4
  # Merges the given configuration or hash-like object with the
49
5
  # registered global configuration.
50
6
  #
@@ -54,18 +10,16 @@ module Yarrow
54
10
  instance.deep_merge!(config)
55
11
  end
56
12
 
57
- ##
58
13
  # Loads a configuration object from the given YAML file.
59
14
  #
60
15
  # @param [String] path to YAML file
61
16
  #
62
- # @return [Yarrow::Configuration]
17
+ # @return [Yarrow::Config]
63
18
  #
64
19
  def load(file)
65
- self.new(YAML.load_file(file))
20
+ coerce_config_struct(YAML.load(File.read(file), symbolize_names: true))
66
21
  end
67
22
 
68
- ##
69
23
  # Yarrow is distributed with a `defaults.yml` which provides minimum
70
24
  # boostrap configuration and default settings for various internal
71
25
  # classes. Use this method to automatically load these defaults.
@@ -74,26 +28,49 @@ module Yarrow
74
28
  def load_defaults
75
29
  load(File.join(File.dirname(__FILE__), 'defaults.yml'))
76
30
  end
77
- end
78
- end
79
31
 
80
- ##
81
- # Embeds global configuration access in a client object.
82
- #
83
- module Configurable
84
- ##
85
- # Provides access to the registered global configuration. This can
86
- # be overriden by subclasses to customize behaviour (eg: in test cases).
87
- #
88
- # @return [Yarrow::Configuration]
89
- #
90
- def config
91
- Configuration.instance
32
+ private
33
+
34
+ # TODO: this should be folded into the schema machinery with type coercions
35
+ def coerce_config_struct(config)
36
+ meta_obj = if config.key?(:meta)
37
+ Yarrow::Config::Meta.new(
38
+ title: config[:meta][:title],
39
+ author: config[:meta][:author]
40
+ )
41
+ else
42
+ nil
43
+ end
44
+
45
+ server_obj = if config.key?(:server)
46
+ Yarrow::Config::Server.new(**config[:server])
47
+ else
48
+ nil
49
+ end
50
+
51
+ # TODO: messy hack to get rid of Hashie::Mash, this should either be
52
+ # automated as part of the schema types or a default value should be
53
+ # generated here (eg: `"#{Dir.pwd}/docs"`)
54
+ out_dir_or_string = config[:output_dir] || ""
55
+ source_dir_or_string = config[:source] || ""
56
+ content_dir_or_string = config[:content] || ""
57
+
58
+ Yarrow::Config::Instance.new(
59
+ output_dir: Pathname.new(File.expand_path(out_dir_or_string)),
60
+ source: Pathname.new(File.expand_path(source_dir_or_string)),
61
+ content: Pathname.new(File.expand_path(content_dir_or_string)),
62
+ meta: meta_obj,
63
+ server: server_obj
64
+ )
65
+ end
92
66
  end
93
67
  end
94
68
 
95
69
  ##
96
70
  # Raised when a required config section or property is missing.
97
71
  class ConfigurationError < StandardError
72
+ def self.missing_section(name)
73
+ new("Missing config section #{name.to_sym}")
74
+ end
98
75
  end
99
76
  end
@@ -31,27 +31,27 @@ module Yarrow
31
31
 
32
32
  def run_application
33
33
  print_header
34
-
34
+
35
35
  begin
36
36
  process_arguments
37
-
37
+
38
38
  if has_option?(:version)
39
39
  return SUCCESS
40
40
  end
41
-
41
+
42
42
  if has_option?(:help)
43
43
  print_help
44
44
  return SUCCESS
45
45
  end
46
-
46
+
47
47
  process_configuration
48
-
48
+
49
49
  run_input_process
50
-
50
+
51
51
  run_output_process
52
-
52
+
53
53
  print_footer
54
-
54
+
55
55
  SUCCESS
56
56
  rescue Exception => e
57
57
  print_error e
@@ -81,20 +81,20 @@ module Yarrow
81
81
 
82
82
  def process_configuration
83
83
  # load_configuration(Dir.pwd)
84
-
84
+
85
85
  # @targets.each do |input_path|
86
86
  # @config.deep_merge! load_configuration(input_path)
87
87
  # end
88
-
88
+
89
89
  if has_option?(:config)
90
90
  path = @options[:config]
91
91
  @config.deep_merge! Configuration.load(path)
92
92
  end
93
-
93
+
94
94
  @config.options = @options.to_hash
95
95
 
96
96
  # normalize_theme_path
97
-
97
+
98
98
  # theme = @config.options.theme
99
99
  # @config.append load_configuration(theme)
100
100
  end
@@ -113,9 +113,9 @@ module Yarrow
113
113
  else
114
114
  value = true
115
115
  end
116
-
116
+
117
117
  name = option.gsub("-", "")
118
-
118
+
119
119
  if option[0..1] == "--"
120
120
  if ENABLED_OPTIONS.has_value?(name.to_sym)
121
121
  @options[name.to_sym] = value
@@ -127,7 +127,7 @@ module Yarrow
127
127
  return
128
128
  end
129
129
  end
130
-
130
+
131
131
  raise "Unrecognized option: #{raw_option}"
132
132
  end
133
133
 
@@ -142,7 +142,7 @@ module Yarrow
142
142
  def run_input_process
143
143
  # noop
144
144
  end
145
-
145
+
146
146
  def run_output_process
147
147
  # noop
148
148
  end
@@ -161,7 +161,7 @@ module Yarrow
161
161
 
162
162
  def print_help
163
163
  help = <<HELP
164
- See http://yarrowdoc.org for more information.
164
+ See http://yarrow.maetl.net for more information.
165
165
 
166
166
  Usage:
167
167
 
@@ -125,7 +125,7 @@ module Yarrow
125
125
 
126
126
  def build_content_nodes(graph, objects, type, parent_index)
127
127
  # TODO: this may need to use a strategy that can be overriden
128
- content_type = ActiveSupport::Inflector.singularize(type).to_sym
128
+ content_type = Yarrow::Symbols.to_singular(type)
129
129
 
130
130
  # Process collected content objects and generate entity nodes
131
131
  objects.each do |name, sources|
@@ -172,8 +172,9 @@ module Yarrow
172
172
  # TODO: consider whether to provide `body` on the item/document or at
173
173
  # the custom content type level.
174
174
  begin
175
- content_struct = Object.const_get(ActiveSupport::Inflector.classify(content_type))
175
+ content_struct = Yarrow::Symbols.to_const(content_type)
176
176
  rescue
177
+ # No immutable struct found: fall back to slower dynamically typed open struct
177
178
  require "ostruct"
178
179
  content_struct = OpenStruct
179
180
  end
@@ -1,10 +1,14 @@
1
+ gem "strings-inflection"
2
+
1
3
  module Yarrow
2
4
  module Content
3
5
  class ContentType
6
+ Value = Yarrow::Schema::Value.new(:collection, :entity, :extensions)
7
+
4
8
  DEFAULT_EXTENSIONS = [".md", ".yml", ".htm"]
5
9
 
6
10
  def self.from_name(name)
7
- new(Yarrow::Configuration.new(collection: name.to_sym))
11
+ new(Value.new(collection: name.to_sym))
8
12
  end
9
13
 
10
14
  def initialize(properties)
@@ -16,17 +20,17 @@ module Yarrow
16
20
  end
17
21
 
18
22
  def collection
19
- return @properties.collection if @properties.respond_to?(:collection)
20
- ActiveSupport::Inflector.pluralize(@properties.entity).to_sym
23
+ return @properties.collection if @properties.collection
24
+ Yarrow::Symbols.to_plural(@properties.entity)
21
25
  end
22
26
 
23
27
  def entity
24
- return @properties.entity if @properties.respond_to?(:entity)
25
- ActiveSupport::Inflector.singularize(@properties.collection).to_sym
28
+ return @properties.entity if @properties.entity
29
+ Yarrow::Symbols.to_singular(@properties.collection)
26
30
  end
27
31
 
28
32
  def extensions
29
- return @properties.extensions if @properties.respond_to?(:extensions)
33
+ return @properties.extensions if @properties.extensions
30
34
  DEFAULT_EXTENSIONS
31
35
  end
32
36
 
@@ -1,35 +1,10 @@
1
- require "mementus"
2
-
3
- module Mementus
4
- module Pipeline
5
- class Step
6
- # Monkeypatch extension to ensure each pipeline step supports enumerable
7
- # methods. Mostly used for #map. API needs to be fixed in the gem itself.
8
- include Enumerable
9
- end
10
- end
11
- module Structure
12
- class IncidenceList
13
- def inspect
14
- "<Mementus::Structure::IncidenceList>"
15
- end
16
- end
17
- end
18
- class Graph
19
- def inspect
20
- "<Mementus::Graph @structure=#{@structure.inspect} " +
21
- "nodes_count=#{nodes_count} edges_count=#{edges_count}>"
22
- end
23
- end
24
- end
25
-
26
1
  module Yarrow
27
2
  module Content
28
3
  # A directed graph of every element of content in the project.
29
4
  class Graph
30
5
  # Construct a graph collected from source content files.
31
6
  def self.from_source(config)
32
- new(SourceCollector.collect(config.input_dir), config)
7
+ new(SourceCollector.collect(config.source), config)
33
8
  end
34
9
 
35
10
  attr_reader :graph, :config
@@ -40,7 +15,7 @@ module Yarrow
40
15
  end
41
16
 
42
17
  def expand_pages
43
- expander = Yarrow::Content::CollectionExpander.new(config.content_types)
18
+ expander = Yarrow::Content::CollectionExpander.new
44
19
  expander.expand(graph)
45
20
  end
46
21
 
@@ -22,7 +22,7 @@ server:
22
22
  auto_index: true
23
23
  default_index: index.html
24
24
  default_type: text/plain
25
- port: 4000
25
+ port: 5005
26
26
  host: localhost
27
27
  handler: webrick
28
28
  middleware:
@@ -0,0 +1 @@
1
+ require "yarrow/extensions/mementus"
@@ -0,0 +1,24 @@
1
+ require "mementus"
2
+
3
+ module Mementus
4
+ module Pipeline
5
+ class Step
6
+ # Monkeypatch extension to ensure each pipeline step supports enumerable
7
+ # methods. Mostly used for #map. API needs to be fixed in the gem itself.
8
+ include Enumerable
9
+ end
10
+ end
11
+ module Structure
12
+ class IncidenceList
13
+ def inspect
14
+ "<Mementus::Structure::IncidenceList>"
15
+ end
16
+ end
17
+ end
18
+ class Graph
19
+ def inspect
20
+ "<Mementus::Graph @structure=#{@structure.inspect} " +
21
+ "nodes_count=#{nodes_count} edges_count=#{edges_count}>"
22
+ end
23
+ end
24
+ end
@@ -4,11 +4,7 @@ module Yarrow
4
4
  #
5
5
  # Methods provided by this class become available as named variables in
6
6
  # Mustache templates.
7
- #
8
- # Includes the library of helpers for dynamically generating HTML tags.
9
7
  class Context
10
- include Yarrow::HTML::AssetTags
11
-
12
8
  def initialize(attributes)
13
9
  metaclass = class << self; self; end
14
10
  attributes.each do |name, value|
@@ -8,12 +8,12 @@ module Yarrow
8
8
 
9
9
  # Mapping between template types and provided object model
10
10
  def object_map
11
- @config.output.object_map
11
+ @config[:output][:object_map]
12
12
  end
13
13
 
14
14
  # Mapping between template types and provided output templates.
15
15
  def template_map
16
-
16
+
17
17
  end
18
18
 
19
19
  # Template converter used by this generator instance.
@@ -0,0 +1,39 @@
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
+ FileUtils.mkdir_p(target_path.dirname)
31
+
32
+ File.open(target_path.to_s, WRITE_MODE) do |file|
33
+ file.puts(content)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
data/lib/yarrow/schema.rb CHANGED
@@ -11,25 +11,29 @@ module Yarrow
11
11
  #
12
12
  # Current design throws on error rather than returns a boolean result.
13
13
  class Validator
14
- # @param slots [Array<Symbol>, <Hash>] defines the slots in the schema to validate against
15
- def initialize(slots)
16
- @keys = slots.reduce({}) do |keys, slot|
17
- if slot.is_a?(Array)
18
- keys[slot[0]] = slot[1]
19
- else
20
- keys[slot.to_sym] = Type::Any
21
- end
22
-
23
- keys
24
- end
14
+ # @param fields_spec [Hash] defines the slots in the schema to validate against
15
+ def initialize(fields_spec)
16
+ @spec = fields_spec
25
17
  end
26
18
 
27
19
  def check(fields)
28
- raise "wrong number of args" unless fields.keys.length == @keys.keys.length
20
+ missing_fields = @spec.keys.difference(fields.keys)
21
+
22
+ if missing_fields.any?
23
+ missing_fields.each do |field|
24
+ raise "wrong number of args" unless @spec[field].eql?(Type::Any)
25
+ end
26
+ end
27
+
28
+ mismatching_fields = fields.keys.difference(@spec.keys)
29
+
30
+ raise "key does not exist" if mismatching_fields.any?
29
31
 
30
- fields.keys.each do |key|
31
- raise "key does not exist" unless @keys.key?(key)
32
+ fields.each do |(field, value)|
33
+ raise "wrong data type" unless value.is_a?(@spec[field]) || @spec[field].eql?(Type::Any)
32
34
  end
35
+
36
+ true
33
37
  end
34
38
  end
35
39
 
@@ -38,19 +42,38 @@ module Yarrow
38
42
  # Ruby struct but wraps the constructor with method advice that handles
39
43
  # validation (and eventually type coercion if !yagni).
40
44
  class Value
41
- def self.new(*slots, &block)
42
- factory(*slots, &block)
45
+ def self.new(*slots, **fields, &block)
46
+ factory(*slots, **fields, &block)
43
47
  end
44
48
 
45
- def self.factory(*slots, &block)
46
- validator = Validator.new(slots)
49
+ def self.factory(*slots, **fields, &block)
50
+ if slots.empty? && fields.empty?
51
+ raise ArgumentError.new("missing attribute definition")
52
+ end
53
+
54
+ slots_spec, fields_spec = if fields.any?
55
+ raise ArgumentError.new("cannot use slots when field map is supplied") if slots.any?
56
+ [fields.keys, fields]
57
+ else
58
+ [slots, Hash[slots.map { |s| [s, Type::Any]}]]
59
+ end
60
+
61
+ validator = Validator.new(fields_spec)
47
62
 
48
- struct = Struct.new(*slots, keyword_init: true, &block)
63
+ struct = Struct.new(*slots_spec, keyword_init: true, &block)
49
64
 
50
- struct.define_method :initialize do |kwargs|
51
- validator.check(kwargs)
65
+ struct.define_method :initialize do |*args, **kwargs|
66
+ attr_values = if args.any?
67
+ raise ArgumentError.new("cannot mix slots and kwargs") if kwargs.any?
68
+ Hash[slots.zip(args)]
69
+ else
70
+ kwargs
71
+ end
72
+
73
+ validator.check(attr_values)
52
74
  # TODO: type coercion or mapping decision goes here
53
- super(**kwargs)
75
+ super(**attr_values)
76
+
54
77
  freeze
55
78
  end
56
79
 
data/lib/yarrow/server.rb CHANGED
@@ -4,12 +4,18 @@ module Yarrow
4
4
  ##
5
5
  # Little web server for browsing local files.
6
6
  class Server
7
- include Configurable
7
+ attr_reader :config
8
8
 
9
- def initialize
10
- if config.server.nil?
11
- raise ConfigurationError.new('Missing server entry')
9
+ def self.default
10
+ new(Yarrow::Configuration.load_defaults)
11
+ end
12
+
13
+ def initialize(instance_config)
14
+ if instance_config.server.nil?
15
+ raise ConfigurationError.missing_section(:server)
12
16
  end
17
+
18
+ @config = instance_config
13
19
  end
14
20
 
15
21
  ##
@@ -55,6 +61,7 @@ module Yarrow
55
61
  app.use(DirectoryIndex, root: docroot, index: default_index)
56
62
 
57
63
  app_args = [docroot, {}].tap { |args| args.push(default_type) if default_type }
64
+
58
65
  static_app = Rack::File.new(*app_args)
59
66
 
60
67
  if live_reload?
@@ -87,7 +94,7 @@ module Yarrow
87
94
 
88
95
  trap(:INT) do
89
96
  handler.shutdown if handler.respond_to?(:shutdown)
90
- reactor.stop
97
+ reactor.stop if live_reload?
91
98
  end
92
99
 
93
100
  handler.run(app, run_options)
@@ -95,10 +102,13 @@ module Yarrow
95
102
 
96
103
  private
97
104
 
98
- ##
105
+ # Host directory of the mounted web server.
106
+ #
107
+ # Fallback to `config.output_dir`.
108
+ #
99
109
  # @return [String]
100
110
  def docroot
101
- config.output_dir || Dir.pwd
111
+ config.output_dir
102
112
  end
103
113
 
104
114
  ##
@@ -0,0 +1,19 @@
1
+ require "strings-inflection"
2
+ require "strings-case"
3
+
4
+ module Yarrow
5
+ module Symbols
6
+ # Converts an atomic content identifier to a live class constant.
7
+ def self.to_const(atom)
8
+ Object.const_get(Strings::Case.pascalcase(atom.to_s).to_sym)
9
+ end
10
+
11
+ def self.to_singular(atom)
12
+ Strings::Inflection.singularize(atom.to_s).to_sym
13
+ end
14
+
15
+ def self.to_plural(atom)
16
+ Strings::Inflection.pluralize(atom.to_s).to_sym
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,74 @@
1
+ module Yarrow
2
+ module Tools
3
+ # Synchronous utility functions for working with filesystem content tasks.
4
+ module ContentUtils
5
+ # Pass in a source path and get back a parsed representation of the
6
+ # content if it is in a known text format. Mostly used as a fallback if
7
+ # a custom parser or processing chain is not configured for a content
8
+ # type.
9
+ #
10
+ # Supported formats:
11
+ # - HTML template and document partials
12
+ # - Markdown documents
13
+ # - YAML documents
14
+ # - JSON (untested)
15
+ #
16
+ # Works around meta and content source in multiple files or a single
17
+ # file with front matter.
18
+ def read_yfm(name)
19
+ path = if name.is_a?(Pathname)
20
+ name
21
+ else
22
+ Pathname.new(name)
23
+ end
24
+
25
+ text = File.read(path, :encoding => 'utf-8')
26
+
27
+ case path.extname
28
+ when '.htm', '.md', '.txt', '.yfm'
29
+ extract_yfm(text, symbolize_keys: true)
30
+ # when '.md'
31
+ # body, data = read_split_content(path.to_s, symbolize_keys: true)
32
+ # [Kramdown::Document.new(body).to_html, data]
33
+ when '.yml'
34
+ [nil, YAML.load(File.read(path.to_s), symbolize_names: true)]
35
+ when '.json'
36
+ [nil, JSON.parse(File.read(path.to_s))]
37
+ end
38
+ end
39
+
40
+ def extract_yfm(text, options={})
41
+ pattern = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
42
+ if text =~ pattern
43
+ content = text.sub(pattern, "")
44
+
45
+ begin
46
+ if options.key?(:symbolize_keys)
47
+ meta = YAML.load($1, symbolize_names: true)
48
+ else
49
+ meta = YAML.load($1)
50
+ end
51
+ return [content, meta]
52
+ rescue Psych::SyntaxError => error
53
+ if defined? ::Logger
54
+ # todo: application wide logger
55
+ #logger = ::Logger.new(STDOUT)
56
+ #logger.error "#{error.message}"
57
+ end
58
+ return [content, nil]
59
+ end
60
+ end
61
+
62
+ [text, nil]
63
+ end
64
+
65
+ def write_yfm(name, text, meta)
66
+ # Symbolized keys are better to deal with when manipulating data in
67
+ # Ruby but are an interop nightmare when serialized so here we do a
68
+ # round-trip through JSON encoding to ensure all keys are string
69
+ # encoded before dumping them to the front matter format.
70
+ File.write(name, [YAML.dump(meta.to_json), "---", text].join("\n"))
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,7 @@
1
1
  module Yarrow
2
2
  module Tools
3
+ # @deprecated
4
+ # Maintained here as it is still used in a number of places but needs to be removed soon
3
5
  module FrontMatter
4
6
 
5
7
  def read_split_content(path, options={})
@@ -21,8 +23,8 @@ module Yarrow
21
23
  rescue Psych::SyntaxError => error
22
24
  if defined? ::Logger
23
25
  # todo: application wide logger
24
- logger = ::Logger.new(STDOUT)
25
- logger.error "#{error.message}"
26
+ #logger = ::Logger.new(STDOUT)
27
+ #logger.error "#{error.message}"
26
28
  end
27
29
  return [content, nil]
28
30
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module Yarrow
3
3
  APP_NAME = 'Yarrow'
4
- VERSION = '0.6.0'
4
+ VERSION = '0.7.0'
5
5
  end
data/yarrow.gemspec CHANGED
@@ -13,14 +13,13 @@ Gem::Specification.new do |spec|
13
13
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
14
14
  spec.executables << 'yarrow'
15
15
  spec.executables << 'yarrow-server'
16
- spec.add_runtime_dependency 'hashie', '~> 3.5'
17
16
  spec.add_runtime_dependency 'mementus', '~> 0.8'
18
- spec.add_runtime_dependency 'activesupport', '~> 5.1'
19
17
  spec.add_runtime_dependency 'rack', '~> 2.0'
20
18
  spec.add_runtime_dependency 'rack-livereload', '~> 0.3'
21
19
  spec.add_runtime_dependency 'eventmachine', '~> 1.2'
22
20
  spec.add_runtime_dependency 'em-websocket', '~> 0.5.1'
23
- spec.add_runtime_dependency 'sprockets', '~> 3.7'
21
+ spec.add_runtime_dependency 'strings-inflection', '~> 0.1'
22
+ spec.add_runtime_dependency 'strings-case', '~> 0.3'
24
23
  spec.add_development_dependency 'bundler', '~> 2.2.9'
25
24
  spec.add_development_dependency 'rake', '~> 13.0'
26
25
  spec.add_development_dependency 'rspec', '~> 3.10'
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yarrow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Rickerby
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-02 00:00:00.000000000 Z
11
+ date: 2021-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: hashie
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '3.5'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '3.5'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: mementus
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -38,20 +24,6 @@ dependencies:
38
24
  - - "~>"
39
25
  - !ruby/object:Gem::Version
40
26
  version: '0.8'
41
- - !ruby/object:Gem::Dependency
42
- name: activesupport
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '5.1'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '5.1'
55
27
  - !ruby/object:Gem::Dependency
56
28
  name: rack
57
29
  requirement: !ruby/object:Gem::Requirement
@@ -109,19 +81,33 @@ dependencies:
109
81
  - !ruby/object:Gem::Version
110
82
  version: 0.5.1
111
83
  - !ruby/object:Gem::Dependency
112
- name: sprockets
84
+ name: strings-inflection
113
85
  requirement: !ruby/object:Gem::Requirement
114
86
  requirements:
115
87
  - - "~>"
116
88
  - !ruby/object:Gem::Version
117
- version: '3.7'
89
+ version: '0.1'
118
90
  type: :runtime
119
91
  prerelease: false
120
92
  version_requirements: !ruby/object:Gem::Requirement
121
93
  requirements:
122
94
  - - "~>"
123
95
  - !ruby/object:Gem::Version
124
- version: '3.7'
96
+ version: '0.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: strings-case
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.3'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.3'
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: bundler
127
113
  requirement: !ruby/object:Gem::Requirement
@@ -214,6 +200,7 @@ files:
214
200
  - lib/yarrow/assets.rb
215
201
  - lib/yarrow/assets/manifest.rb
216
202
  - lib/yarrow/assets/pipeline.rb
203
+ - lib/yarrow/config.rb
217
204
  - lib/yarrow/configuration.rb
218
205
  - lib/yarrow/console_runner.rb
219
206
  - lib/yarrow/content/collection_expander.rb
@@ -223,14 +210,14 @@ files:
223
210
  - lib/yarrow/content/source_collector.rb
224
211
  - lib/yarrow/content_map.rb
225
212
  - lib/yarrow/defaults.yml
213
+ - lib/yarrow/extensions.rb
214
+ - lib/yarrow/extensions/mementus.rb
226
215
  - lib/yarrow/generator.rb
227
- - lib/yarrow/html.rb
228
- - lib/yarrow/html/asset_tags.rb
229
- - lib/yarrow/html/content_tags.rb
230
216
  - lib/yarrow/logging.rb
231
217
  - lib/yarrow/output/context.rb
232
218
  - lib/yarrow/output/generator.rb
233
219
  - lib/yarrow/output/mapper.rb
220
+ - lib/yarrow/output/web/indexed_file.rb
234
221
  - lib/yarrow/process/expand_content.rb
235
222
  - lib/yarrow/process/extract_source.rb
236
223
  - lib/yarrow/process/project_manifest.rb
@@ -243,8 +230,9 @@ files:
243
230
  - lib/yarrow/server.rb
244
231
  - lib/yarrow/server/livereload.rb
245
232
  - lib/yarrow/source/graph.rb
233
+ - lib/yarrow/symbols.rb
234
+ - lib/yarrow/tools/content_utils.rb
246
235
  - lib/yarrow/tools/front_matter.rb
247
- - lib/yarrow/tools/output_file.rb
248
236
  - lib/yarrow/version.rb
249
237
  - lib/yarrow/web/html_document.rb
250
238
  - lib/yarrow/web/manifest.rb
@@ -255,7 +243,7 @@ homepage: http://rubygemspec.org/gems/yarrow
255
243
  licenses:
256
244
  - MIT
257
245
  metadata: {}
258
- post_install_message:
246
+ post_install_message:
259
247
  rdoc_options: []
260
248
  require_paths:
261
249
  - lib
@@ -271,7 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
271
259
  version: '0'
272
260
  requirements: []
273
261
  rubygems_version: 3.1.2
274
- signing_key:
262
+ signing_key:
275
263
  specification_version: 4
276
264
  summary: Documentation generator based on a fluent data model.
277
265
  test_files: []
data/lib/yarrow/html.rb DELETED
@@ -1 +0,0 @@
1
- require_relative 'html/asset_tags'
@@ -1,59 +0,0 @@
1
- module Yarrow
2
- module HTML
3
- module AssetTags
4
- include Yarrow::Configurable
5
-
6
- # TODO: make sprockets manifest optional/pluggable
7
- def manifest
8
- Yarrow::Assets::Manifest.new(config)
9
- end
10
-
11
- ##
12
- # Computes the base URL path to assets in the public web directory.
13
- def base_url_path
14
- if config.assets.nil? || config.output_dir.nil?
15
- raise Yarrow::ConfigurationError
16
- end
17
-
18
- # TODO: prepend configurable CDN URL for host path
19
- # TODO: dev/production mode switch
20
-
21
- config.assets.output_dir.gsub(config.output_dir, '')
22
- end
23
-
24
- def script_tags
25
- manifest.js_logical_paths.map { |path| script_tag(asset: path) }.join("\n")
26
- end
27
-
28
- def script_tag(options)
29
- src_path = if asset_in_manifest?(options)
30
- digest_path(options[:asset])
31
- else
32
- options[:src]
33
- end
34
-
35
- "<script src=\"#{src_path}\"></script>"
36
- end
37
-
38
- def link_tag(options)
39
- href_path = if asset_in_manifest?(options)
40
- digest_path(options[:asset])
41
- else
42
- options[:href]
43
- end
44
-
45
- "<link href=\"#{href_path}\" rel=\"stylesheet\" type=\"text/css\">"
46
- end
47
-
48
- private
49
-
50
- def asset_in_manifest?(options)
51
- options.has_key?(:asset) and manifest.exists?(options[:asset])
52
- end
53
-
54
- def digest_path(path)
55
- "#{base_url_path}/#{manifest.digest_path(path)}"
56
- end
57
- end
58
- end
59
- end
@@ -1,7 +0,0 @@
1
- module Yarrow
2
- module HTML
3
- module ContentTags
4
-
5
- end
6
- end
7
- end
@@ -1,40 +0,0 @@
1
- module Yarrow
2
- module Tools
3
- # TODO: consider renaming this to OutputDocument.
4
- class OutputFile
5
- include Yarrow::Configurable
6
-
7
- WRITE_MODE = 'w+:UTF-8'.freeze
8
-
9
- # @return [String] Basename reflecting the server convention (usually: index.html)
10
- def index_name
11
- @index_name ||= config.index_name || 'index.html'
12
- end
13
-
14
- # @return [String] Docroot of the output target
15
- def docroot
16
- @docroot ||= config.output_dir || 'public'
17
- end
18
-
19
- # Write an output file to the specified path under the docroot.
20
- #
21
- # @param path [String]
22
- # @param content [String]
23
- def write(path, content)
24
- # If the target path is a directory,
25
- # generate a default index filename.
26
- if path[path.length-1] == '/'
27
- path = "#{path}#{index_name}"
28
- end
29
-
30
- target_path = Pathname.new("#{docroot}#{path}")
31
-
32
- FileUtils.mkdir_p(target_path.dirname)
33
-
34
- File.open(target_path.to_s, WRITE_MODE) do |file|
35
- file.puts(content)
36
- end
37
- end
38
- end
39
- end
40
- end