brandish 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +41 -0
  5. data/.travis.yml +5 -0
  6. data/.yardopts +1 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +10 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +41 -0
  11. data/Rakefile +9 -0
  12. data/bin/brandish +16 -0
  13. data/brandish.gemspec +39 -0
  14. data/defaults/templates/html.liquid +39 -0
  15. data/lib/brandish.rb +51 -0
  16. data/lib/brandish/application.rb +163 -0
  17. data/lib/brandish/application/bench_command.rb +96 -0
  18. data/lib/brandish/application/build_command.rb +73 -0
  19. data/lib/brandish/application/initialize_command.rb +83 -0
  20. data/lib/brandish/application/serve_command.rb +150 -0
  21. data/lib/brandish/configure.rb +196 -0
  22. data/lib/brandish/configure/dsl.rb +135 -0
  23. data/lib/brandish/configure/dsl/form.rb +136 -0
  24. data/lib/brandish/configure/form.rb +32 -0
  25. data/lib/brandish/errors.rb +65 -0
  26. data/lib/brandish/execute.rb +26 -0
  27. data/lib/brandish/markup.rb +10 -0
  28. data/lib/brandish/markup/redcarpet.rb +14 -0
  29. data/lib/brandish/markup/redcarpet/format.rb +127 -0
  30. data/lib/brandish/markup/redcarpet/html.rb +95 -0
  31. data/lib/brandish/parser.rb +26 -0
  32. data/lib/brandish/parser/main.rb +237 -0
  33. data/lib/brandish/parser/node.rb +89 -0
  34. data/lib/brandish/parser/node/block.rb +98 -0
  35. data/lib/brandish/parser/node/command.rb +102 -0
  36. data/lib/brandish/parser/node/pair.rb +42 -0
  37. data/lib/brandish/parser/node/root.rb +83 -0
  38. data/lib/brandish/parser/node/string.rb +18 -0
  39. data/lib/brandish/parser/node/text.rb +114 -0
  40. data/lib/brandish/path_set.rb +163 -0
  41. data/lib/brandish/processor.rb +47 -0
  42. data/lib/brandish/processor/base.rb +144 -0
  43. data/lib/brandish/processor/block.rb +47 -0
  44. data/lib/brandish/processor/command.rb +47 -0
  45. data/lib/brandish/processor/context.rb +169 -0
  46. data/lib/brandish/processor/descend.rb +32 -0
  47. data/lib/brandish/processor/inline.rb +49 -0
  48. data/lib/brandish/processor/name_filter.rb +67 -0
  49. data/lib/brandish/processor/pair_filter.rb +96 -0
  50. data/lib/brandish/processors.rb +26 -0
  51. data/lib/brandish/processors/all.rb +19 -0
  52. data/lib/brandish/processors/all/comment.rb +29 -0
  53. data/lib/brandish/processors/all/embed.rb +56 -0
  54. data/lib/brandish/processors/all/if.rb +109 -0
  55. data/lib/brandish/processors/all/import.rb +95 -0
  56. data/lib/brandish/processors/all/literal.rb +42 -0
  57. data/lib/brandish/processors/all/verify.rb +47 -0
  58. data/lib/brandish/processors/common.rb +20 -0
  59. data/lib/brandish/processors/common/asset.rb +118 -0
  60. data/lib/brandish/processors/common/asset/paths.rb +93 -0
  61. data/lib/brandish/processors/common/group.rb +67 -0
  62. data/lib/brandish/processors/common/header.rb +86 -0
  63. data/lib/brandish/processors/common/markup.rb +127 -0
  64. data/lib/brandish/processors/common/output.rb +73 -0
  65. data/lib/brandish/processors/html.rb +18 -0
  66. data/lib/brandish/processors/html/group.rb +33 -0
  67. data/lib/brandish/processors/html/header.rb +46 -0
  68. data/lib/brandish/processors/html/markup.rb +131 -0
  69. data/lib/brandish/processors/html/output.rb +62 -0
  70. data/lib/brandish/processors/html/output/document.rb +127 -0
  71. data/lib/brandish/processors/html/script.rb +64 -0
  72. data/lib/brandish/processors/html/script/babel.rb +48 -0
  73. data/lib/brandish/processors/html/script/coffee.rb +47 -0
  74. data/lib/brandish/processors/html/script/vanilla.rb +45 -0
  75. data/lib/brandish/processors/html/style.rb +82 -0
  76. data/lib/brandish/processors/html/style/highlight.rb +89 -0
  77. data/lib/brandish/processors/html/style/sass.rb +64 -0
  78. data/lib/brandish/processors/html/style/vanilla.rb +71 -0
  79. data/lib/brandish/processors/latex.rb +15 -0
  80. data/lib/brandish/processors/latex/markup.rb +47 -0
  81. data/lib/brandish/scanner.rb +64 -0
  82. data/lib/brandish/version.rb +9 -0
  83. data/templates/initialize/Gemfile.tt +14 -0
  84. data/templates/initialize/brandish.config.rb.tt +49 -0
  85. metadata +296 -0
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Brandish
5
+ class Application
6
+ # The bench command. This builds the project in the set directory,
7
+ # and benchmarks. This is for debugging.
8
+ class BenchCommand
9
+ include Commander::Methods
10
+
11
+ # Defines the command on the given application. This sets the important
12
+ # data information for the command, for use for the help output.
13
+ #
14
+ # @param application [Application]
15
+ # @param command [Commander::Command]
16
+ # @return [void]
17
+ def self.define(application, command)
18
+ command.syntax = "brandish build"
19
+ command.option "-o", "--only NAMES", [::String]
20
+ command.option "-p", "--path PATH", ::String
21
+ command.option "-n", "--name NAME", ::String
22
+
23
+ command.action { |_, o| call(application, o.__hash__) }
24
+ end
25
+
26
+ # The default options for the build command.
27
+ #
28
+ # @return [{::Symbol => ::Object}]
29
+ DEFAULTS = { only: :all, path: "profile", name: "default" }.freeze
30
+
31
+ # Performs the build command. This initializes the command, and
32
+ # calls {#call}.
33
+ #
34
+ # @param application [Application] The application.
35
+ # @param options [{::Symbol => ::Object}] The options for the command.
36
+ #
37
+ # @return [void]
38
+ def self.call(application, options)
39
+ new(application, options).call
40
+ end
41
+
42
+ # Initialize the build command.
43
+ #
44
+ # @params (see {.call})
45
+ def initialize(application, options)
46
+ @application = application
47
+ @options = DEFAULTS.merge(options)
48
+ end
49
+
50
+ # Performs the build. First, it loads the configuration file for the
51
+ # build. Then, it performs the build, calling {Configure#build} with
52
+ # the `:only` filter provided by the options. This uses a Commander
53
+ # native called `progress` to make it look nice on the output.
54
+ #
55
+ # @return [void]
56
+ def call
57
+ require "ruby-prof"
58
+ require "benchmark"
59
+
60
+ @configure = @application.load_configuration_file
61
+ @path = ::Pathname.new(@options[:path]).expand_path(Dir.pwd)
62
+ @path.mkpath
63
+ say "=> Beginning build..."
64
+
65
+ result = nil
66
+ time = Benchmark.measure { result = RubyProf.profile { perform_build } }
67
+ say "-> Build ended, time: #{time}"
68
+ say "=> Outputting profile..."
69
+
70
+ result.eliminate_methods!(method_eleminations)
71
+ output_profile(result)
72
+ end
73
+
74
+ private
75
+
76
+ def output_profile(result)
77
+ printer = RubyProf::MultiPrinter.new(result)
78
+ printer.print(path: @path, profile: @options[:name])
79
+ say "-> Profile output to `#{@options[:path]}'!"
80
+ end
81
+
82
+ def perform_build
83
+ @configure.build(@options[:only]).each(&:call)
84
+ rescue => e
85
+ say_error "!> Error while building!"
86
+ say_error "-> Received exception: #{e.class}: #{e.message}"
87
+ e.backtrace.each { |l| say_warning "\t-> in #{l}" } if @options[:trace]
88
+ exit!
89
+ end
90
+
91
+ def method_eleminations
92
+ [/\A(Set|Class|Array|Enumerable)#/]
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,73 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Brandish
5
+ class Application
6
+ # The build command. This just builds the project in the set directory.
7
+ class BuildCommand
8
+ include Commander::Methods
9
+
10
+ # The description for the build command.
11
+ #
12
+ # @return [::String]
13
+ COMMAND_DESCRIPTION =
14
+ "Builds an existing Brandish project. If no directory is specified " \
15
+ " using --directory or -d, it defaults to the current directory."
16
+
17
+ # Defines the command on the given application. This sets the important
18
+ # data information for the command, for use for the help output.
19
+ #
20
+ # @param application [Application]
21
+ # @param command [Commander::Command]
22
+ # @return [void]
23
+ def self.define(application, command)
24
+ command.syntax = "brandish build"
25
+ command.description = COMMAND_DESCRIPTION
26
+ command.option "-o", "--only NAMES", [::String],
27
+ "Which forms to build. If this is omitted, it defaults to all."
28
+
29
+ command.action { |_, o| call(application, o.__hash__) }
30
+ end
31
+
32
+ # The default options for the build command.
33
+ #
34
+ # @return [{::Symbol => ::Object}]
35
+ DEFAULTS = { only: :all }.freeze
36
+
37
+ # Performs the build command. This initializes the command, and
38
+ # calls {#call}.
39
+ #
40
+ # @param application [Application] The application.
41
+ # @param options [{::Symbol => ::Object}] The options for the command.
42
+ #
43
+ # @return [void]
44
+ def self.call(application, options)
45
+ new(application, options).call
46
+ end
47
+
48
+ # Initialize the build command.
49
+ #
50
+ # @params (see {.call})
51
+ def initialize(application, options)
52
+ @application = application
53
+ @options = DEFAULTS.merge(options)
54
+ end
55
+
56
+ # Performs the build. First, it loads the configuration file for the
57
+ # build. Then, it performs the build, calling {Configure#build} with
58
+ # the `:only` filter provided by the options. This uses a Commander
59
+ # native called `progress` to make it look nice on the output.
60
+ #
61
+ # @return [void]
62
+ def call
63
+ configure = @application.load_configuration_file
64
+ @application.progress(configure.build(@options[:only]).to_a, &:call)
65
+ rescue => e
66
+ say_error "\n!> Error while building!"
67
+ say_error "!> #{e.location}" if e.respond_to?(:location)
68
+ say_error "!> Received exception: #{e.class}: #{e.message}"
69
+ e.backtrace.each { |l| say_warning "\t~> in #{l}" } if @options[:trace]
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "thor"
5
+ require "rubygems"
6
+
7
+ module Brandish
8
+ class Application
9
+ # The initialize command for the application. This creates a new project
10
+ # at a given directory. The Brandish project is placed at
11
+ # `directory`/`name`.
12
+ class InitializeCommand
13
+ include Thor::Base
14
+ include Thor::Actions
15
+
16
+ # The description for the initialize command.
17
+ #
18
+ # @return [::String]
19
+ COMMAND_DESCRIPTION =
20
+ "Creates a new Brandish project. The name given should not contain " \
21
+ "any path seperators - if a brandish project needs to be placed in " \
22
+ "a seperate directory, use the --directory (or -d) option."
23
+
24
+ # The source root for the templates used by Thor for initialization.
25
+ #
26
+ # @return [::String]
27
+ def self.source_root
28
+ File.expand_path("../../../../templates/initialize", __FILE__)
29
+ end
30
+
31
+ # Defines the command on the given application. This sets the important
32
+ # data information for the command, for use for the help output.
33
+ #
34
+ # @param application [Application]
35
+ # @param command [Commander::Command]
36
+ # @return [void]
37
+ def self.define(application, command)
38
+ command.syntax = "brandish initialize NAME"
39
+ command.description = COMMAND_DESCRIPTION
40
+
41
+ command.action { |a, o| call(application, a, o.__hash__) }
42
+ end
43
+
44
+ # Performs the initialize command. Since this class uses Thor, this
45
+ # performs some setup to interface with the Thor class.
46
+ #
47
+ # @param application [Application]
48
+ # @param arguments [<::String>] The arguments passed to the initialize
49
+ # command. This should contain one value - the name.
50
+ # @param _options [Hash] The options for the command. Since this command
51
+ # takes no specific options, this is ignored.
52
+ # @return [void]
53
+ def self.call(application, arguments, _options)
54
+ name = arguments[0]
55
+ directory = application.directory / name
56
+ new([], { name: name }, destination_root: directory).call
57
+ end
58
+
59
+ # Performs the initialize command, setting up the project.
60
+ #
61
+ # @return [void]
62
+ def call
63
+ template "brandish.config.rb"
64
+ %w(source source/assets source/assets/styles source/assets/scripts
65
+ templates output).each { |d| empty_directory(d) }
66
+ template "Gemfile"
67
+ inside(".") { run "bundle install" }
68
+ end
69
+
70
+ protected
71
+
72
+ # The approximate recommendation for the current running version of
73
+ # Brandish. This is used to set up a requirement for Brandish in both
74
+ # the Gemfile and the `brandish.config.rb` file.
75
+ #
76
+ # @return [::String]
77
+ def approx
78
+ @approx ||=
79
+ Gem::Version.new(Brandish::VERSION).approximate_recommendation
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,150 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Brandish
5
+ class Application
6
+ # The serve command. This builds, and then serves, an existing Brandish
7
+ # project. This watches the source. If it detects a change, it rebuilds.
8
+ class ServeCommand
9
+ include Commander::Methods
10
+
11
+ # The description for the serve command.
12
+ #
13
+ # @return [::String]
14
+ COMMAND_DESCRIPTION = "Builds, and serves, an existing Brandish project."
15
+
16
+ # Defines the command on the given application. This sets the important
17
+ # data information for the command, for use for the help output.
18
+ #
19
+ # @param application [Application]
20
+ # @param command [Commander::Command]
21
+ # @return [void]
22
+ def self.define(application, command)
23
+ command.syntax = "branish serve"
24
+ command.description = COMMAND_DESCRIPTION
25
+ command.option "-p", "--port PORT", ::Integer, "The port to listen on"
26
+ command.option "-o", "--only NAMES", [::String], "The forms to build"
27
+ command.option "--verbose", "Whether or not to be verbose in the output"
28
+
29
+ command.action { |_, o| call(application, o.__hash__) }
30
+ end
31
+
32
+ # Performs the serve command. This initializes the command, and
33
+ # calls {#call}.
34
+ #
35
+ # @param application [Application] The application.
36
+ # @param options [{::Symbol => ::Object}] The options for the command.
37
+ #
38
+ # @return [void]
39
+ def self.call(application, options)
40
+ puts "Running with options #{options.inspect}..."
41
+ new(application, options).call
42
+ end
43
+
44
+ # The default options for the serve command.
45
+ #
46
+ # @return [{::Symbol => ::Object}]
47
+ DEFAULTS = { only: :all, show_build: false }.freeze
48
+
49
+ # Initialize the serve command.
50
+ #
51
+ # @params (see {.call})
52
+ def initialize(application, options)
53
+ @application = application
54
+ @options = DEFAULTS.merge(options)
55
+ @port = @options.fetch(:port, (ENV["PORT"] || "3000").to_i)
56
+ end
57
+
58
+ # Performs the serve command. It first loads the configuration file,
59
+ # then calls {#start_webserver}, followed by {#start_buildserver}. Once
60
+ # both servers are setup, it calls {#wait_on_servers}.
61
+ #
62
+ # @return [void]
63
+ def call
64
+ @configuration = @application.load_configuration_file
65
+ say "=> Beginning serve..."
66
+ start_webserver
67
+ start_buildserver
68
+ color "\r=> Ready and waiting! ", :erase_line, :green
69
+ wait_on_servers
70
+ rescue StandardError, ScriptError => e
71
+ # Whenever we receive a general error, which only occurs while setup,
72
+ # we complain, and pass up the exception.
73
+ say_error "\n!> Received exception: #{e.class}: #{e.message}"
74
+ e.backtrace.each { |l| say_warning "\t-> in #{l}" } if @options[:trace]
75
+ fail
76
+ rescue SignalException, NoMemoryError, SystemExit, SystemStackError => e
77
+ # Whenever we receive a signal, or an unrecoverable error, we kill
78
+ # the servers and complain. These exceptions occur on the main thread,
79
+ # and so we handle them here.
80
+ say_warning "\n!> Received exception: #{e.class}: #{e.message}"
81
+ say_ok "\n-> Received termination, shutting down..."
82
+ kill_webserver
83
+ kill_buildserver
84
+ end
85
+
86
+ private
87
+
88
+ def start_webserver
89
+ say "-> Setting up web server on port #{@port}..."
90
+ log_file = @options[:verbose] ? $stdout : StringIO.new
91
+ log = WEBrick::Log.new(log_file)
92
+ access_log = [[log_file, WEBrick::AccessLog::COMBINED_LOG_FORMAT]]
93
+ data = { Port: @port, DocumentRoot: @configuration.output.to_s,
94
+ Logger: log, AccessLog: access_log }
95
+
96
+ @webserver = Thread.start { WEBrick::HTTPServer.new(data).start }
97
+ end
98
+
99
+ def print(a, *)
100
+ fail if a.is_a?(::IO)
101
+ super
102
+ end
103
+
104
+ def start_buildserver
105
+ perform_build
106
+ say "\n"
107
+ say "-> Setting up listen server..."
108
+ source_build_server = Listen.to(*listen_paths) { perform_build }
109
+ config_build_server = Listen.to(@application.directory.to_s) do
110
+ say "-> Configuration file changed, updating..."
111
+ @configuration = @application.load_configuration_file!
112
+ perform_build
113
+ end
114
+ config_build_server.only(/#{Regexp.escape(@application.config_file.to_s)}\z/)
115
+
116
+ @buildservers = [source_build_server, config_build_server].each(&:start)
117
+ end
118
+
119
+ def perform_build
120
+ builds = @configuration.build!(@options[:only])
121
+ color "\r~> Building... ", :erase_line, :clear
122
+ builds.each(&:call)
123
+ color "\r=> Build completed at #{Time.now.strftime('%T.%L')}! ", :erase_line, :green
124
+
125
+ rescue => e
126
+ say_error "\n!> Error while building!"
127
+ say_error "!> #{e.location}" if e.respond_to?(:location)
128
+ say_error "-> Received exception: #{e.class}: #{e.message}"
129
+ e.backtrace.each { |l| say_warning "\t-> in #{l}" } if @options[:trace]
130
+ end
131
+
132
+ def kill_webserver
133
+ @webserver&.kill
134
+ end
135
+
136
+ def kill_buildserver
137
+ @buildservers&.each { |b| b&.stop }
138
+ end
139
+
140
+ def wait_on_servers
141
+ @webserver.join
142
+ end
143
+
144
+ def listen_paths
145
+ (@configuration.sources.to_a + @configuration.templates.to_a)
146
+ .select(&:directory?).map(&:to_s)
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,196 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems" # for Gem::Requirement
5
+ require "securerandom"
6
+ require "forwardable"
7
+
8
+ require "brandish/configure/dsl"
9
+ require "brandish/configure/form"
10
+
11
+ module Brandish
12
+ # This provides a central location for all configuration options. For the
13
+ # DSL, see {DSL}.
14
+ class Configure
15
+ extend Forwardable
16
+
17
+ # The options. These can be anything; predefined options are `:root`,
18
+ # `:source`, `:output`, and `:templates`; for more information on those,
19
+ # see {#root}, {#sources}, {#output}, and {#templates} for more information.
20
+ # These options should not be accessed directly, but rather through their
21
+ # accessors. Other options must be accessed through this.
22
+ #
23
+ # @return [{::Symbol => ::Object}]
24
+ attr_reader :options
25
+
26
+ # The forms that are defined on this configuration instance.
27
+ #
28
+ # @return [Set<Configure::Form>]
29
+ attr_reader :forms
30
+
31
+ # @!method [](key)
32
+ # Sets a key on the options. This gets an option that is used for all
33
+ # processors on the options.
34
+ #
35
+ # @param key [::Symbol, ::String] The key.
36
+ # @return [::Object]
37
+ # @!method []=(key, value)
38
+ # Sets a key on the options. This sets an option that is used for all
39
+ # processors on the options.
40
+ #
41
+ # @param key [::Symbol, ::String] The key.
42
+ # @param value [::Object] The value.
43
+ # @return [::Object]
44
+ # @!method fetch(key, default = CANARY, &block)
45
+ # Fetches a value at the given key, or provides a default if the key
46
+ # doesn't exist. If both a block and a default argument are given,
47
+ # the block form takes precedence.
48
+ #
49
+ # @overload fetch(key)
50
+ # Attempts to retrieve a value at the given key. If there is no
51
+ # key-value pair at the given key, it raises an error.
52
+ #
53
+ # @raise [KeyError] if the key isn't on the options.
54
+ # @param key [::Symbol, ::String] The key.
55
+ # @return [::Object] The value.
56
+ #
57
+ # @overload fetch(key, default)
58
+ # Attempts to retrieve a value at the given key. If there is no
59
+ # key-value pair at the given key, it returns the value given by
60
+ # `default`.
61
+ #
62
+ # @param key [::Symbol, ::String] The key.
63
+ # @param default [::Object] The default value.
64
+ # @return [::Object] The value, or the default value if there isn't
65
+ # one.
66
+ #
67
+ # @overload fetch(key, &block)
68
+ # attempts to retrieve a value at the given key. If there is no
69
+ # key-value pair at the given key, it yields.
70
+ #
71
+ # @yield if there is no corresponding key-value pair.
72
+ # @param key [::Symbol, ::String] The key.
73
+ # @return [::Object] The value, or the result of the block if there
74
+ # isn't one.
75
+ delegate [:[], :fetch] => :options
76
+
77
+ # Initializes the configure instance.
78
+ #
79
+ # @param root [::String, ::Pathname] The root for the project. This is
80
+ # used to determine the correct paths for the output directory, the
81
+ # sources directory, and the templates directory.
82
+ def initialize(root = Dir.pwd)
83
+ root = ::Pathname.new(root)
84
+ @options = { root: root, sources: PathSet.new, templates: PathSet.new }
85
+ @forms = ::Set.new
86
+ default_paths
87
+ end
88
+
89
+ # Retrieves the root path. This is where all of the other directories
90
+ # should be located, and where the configuration file should be
91
+ # located.
92
+ #
93
+ # @return [::Pathname]
94
+ def root
95
+ fetch(:root)
96
+ end
97
+
98
+ # Retrieves the output path. This is where the outputs for all of the forms
99
+ # should be located.
100
+ #
101
+ # @return [::Pathname]
102
+ def output
103
+ fetch(:output) { root / "output" }
104
+ end
105
+
106
+ # Retrieves the source path. This is where the sources for all of the
107
+ # documents in the Brandish project are located.
108
+ #
109
+ # @return [PathSet]
110
+ def sources
111
+ fetch(:sources)
112
+ end
113
+
114
+ # Retrieves the templates path. This is where all of the templates for
115
+ # all of the forms should be located.
116
+ #
117
+ # @return [PathSet]
118
+ def templates
119
+ fetch(:templates)
120
+ end
121
+
122
+ # Given a set of forms to build, it yields blocks that can be called to
123
+ # build a form.
124
+ #
125
+ # @param which [::Symbol, <::Symbol>] If this is `:all`, all of the forms
126
+ # available are built; otherwise, it only builds the forms whose names
127
+ # are listed.
128
+ # @yield [build] Yields for each form that can be built.
129
+ # @yieldparam build [::Proc<void>] A block that can be called to build
130
+ # a form.
131
+ # @return [void]
132
+ def build(which = :all)
133
+ return to_enum(:build, which) unless block_given?
134
+ select_forms(which).each { |f| yield proc { f.build(self) } }
135
+ end
136
+
137
+ # Given a set of forms to build, it yields blocks that can be called to
138
+ # build a form.
139
+ #
140
+ # This first clears the cache for file nodes.
141
+ #
142
+ # @param which [::Symbol, <::Symbol>] If this is `:all`, all of the forms
143
+ # available are built; otherwise, it only builds the forms whose names
144
+ # are listed.
145
+ # @yield [build] Yields for each form that can be built.
146
+ # @yieldparam build [::Proc<void>] A block that can be called to build
147
+ # a form.
148
+ # @return [void]
149
+ def build!(which = :all)
150
+ return to_enum(:build!, which) unless block_given?
151
+ @_roots = nil
152
+ select_forms(which).each { |f| yield proc { f.build(self) } }
153
+ end
154
+
155
+ # A cache for all of the root nodes. This is a regular hash; however, upon
156
+ # attempt to access an item that isn't already in the hash, it first
157
+ # parses the file at that item, and stores the result in the hash, returning
158
+ # the root node in the file. This is to cache files so that they do not
159
+ # get reparsed multiple times.
160
+ #
161
+ # @return [{::Pathname => Parser::Root}]
162
+ def roots
163
+ @_roots ||= ::Hash.new { |h, k| h[k] = parse_from(k) }
164
+ end
165
+
166
+ # Parses a file. This bypasses the cache.
167
+ #
168
+ # @param path [::Pathname] The path to the actual file. This should
169
+ # respond to `#read`. If this isn't a pathname, the short should be
170
+ # provided.
171
+ # @param short [::String] The short name of the file. This is used for
172
+ # location information.
173
+ # @return [Parser::Root]
174
+ def parse_from(path, short = path.relative_path_from(root))
175
+ Parser.new(Scanner.new(path.read, short, options).call).call
176
+ end
177
+
178
+ private
179
+
180
+ def default_paths
181
+ sources <<
182
+ File.expand_path("../../../defaults/source", __FILE__) <<
183
+ (root / "source")
184
+ templates <<
185
+ File.expand_path("../../../defaults/templates", __FILE__) <<
186
+ (root / "templates")
187
+ end
188
+
189
+ def select_forms(which)
190
+ which = @forms.map(&:name) if which == :all || !which.is_a?(::Array) ||
191
+ which.empty?
192
+ which = which.to_set
193
+ @forms.select { |f| which.include?(f.name) }
194
+ end
195
+ end
196
+ end