brandish 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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