gryphon_nest 4.0.1 → 4.2.0
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.
- checksums.yaml +4 -4
- data/bin/nest +18 -5
- data/lib/gryphon_nest/gzip_compressor.rb +24 -0
- data/lib/gryphon_nest/layout_file.rb +35 -0
- data/lib/gryphon_nest/processors/mustache_processor.rb +28 -20
- data/lib/gryphon_nest/processors.rb +2 -1
- data/lib/gryphon_nest/version.rb +1 -1
- data/lib/gryphon_nest.rb +79 -27
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 455e829fa2827b15a7e0641049172d907615a4aa7bf5d29adefdb3440c71743e
|
4
|
+
data.tar.gz: 72a8c33e211645a385fb51c0581085159361e4c7d0dd62b1c284a7eb3e37cbc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9c81a97e0bccc92c99cd1e7fd512b2e63899ab73b2de58f31a0161ad67246d27c1dc7f5fa2bc253fb4961ffa8047d79b56f26a45ca3f29b98bc5f33bdcb2c2e
|
7
|
+
data.tar.gz: 8db1246fd5023222581c26b408c98996ea935b159b1673d4b98f9cd07802c3d53109352f0f6230711260e5a4ba1f1d6b343e144e1d2e5a9e8ddddaa422fb36c1
|
data/bin/nest
CHANGED
@@ -8,6 +8,8 @@ DEFAULT_PORT = 8000
|
|
8
8
|
EXIT_FAILURE = 1
|
9
9
|
|
10
10
|
options = {
|
11
|
+
compress: false,
|
12
|
+
force: false,
|
11
13
|
port: DEFAULT_PORT,
|
12
14
|
watch: false
|
13
15
|
}
|
@@ -21,7 +23,11 @@ end
|
|
21
23
|
|
22
24
|
begin
|
23
25
|
parser = OptionParser.new do |opts|
|
24
|
-
opts.banner = 'Usage: nest [build|serve] [options]'
|
26
|
+
opts.banner = 'Usage: nest [build|serve|clean] [options]'
|
27
|
+
|
28
|
+
opts.on('-c', '--compress', 'Create gzipped compressed versions of each file')
|
29
|
+
|
30
|
+
opts.on('-f', '--force', 'Force (re)build all files')
|
25
31
|
|
26
32
|
opts.on('-p', '--port [PORT]', Integer, 'Port to run dev server on')
|
27
33
|
|
@@ -42,15 +48,22 @@ begin
|
|
42
48
|
|
43
49
|
command = ARGV.fetch(0, 'build')
|
44
50
|
|
45
|
-
|
51
|
+
nest = GryphonNest::Nest.new(options[:force])
|
52
|
+
nest.compressor = GryphonNest::GzipCompressor.new if options[:compress]
|
46
53
|
|
47
|
-
|
48
|
-
|
54
|
+
case command
|
55
|
+
when 'clean'
|
56
|
+
nest.clean
|
57
|
+
when 'build'
|
58
|
+
nest.build
|
59
|
+
when 'serve'
|
60
|
+
nest.build
|
49
61
|
|
50
|
-
if command == 'serve'
|
51
62
|
nest.watch if options[:watch]
|
52
63
|
|
53
64
|
nest.serve(options[:port])
|
65
|
+
else
|
66
|
+
usage_error("Unknown command #{command}", parser)
|
54
67
|
end
|
55
68
|
rescue OptionParser::ParseError => e
|
56
69
|
usage_error(e.message, parser)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
module GryphonNest
|
6
|
+
class GzipCompressor
|
7
|
+
# @param file [Pathname]
|
8
|
+
# @return [Boolean]
|
9
|
+
def can_compress?(file)
|
10
|
+
file.size >= 20
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param file [Pathname]
|
14
|
+
def compress(file)
|
15
|
+
compressed = "#{file}.gz"
|
16
|
+
|
17
|
+
Zlib::GzipWriter.open(compressed, Zlib::BEST_COMPRESSION) do |gz|
|
18
|
+
gz.mtime = file.mtime
|
19
|
+
gz.orig_name = file.to_s
|
20
|
+
gz.write IO.binread(file)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GryphonNest
|
4
|
+
# Wrapper class for operations performed on the layout.yaml file
|
5
|
+
class LayoutFile
|
6
|
+
# @param path [Pathname]
|
7
|
+
def initialize(path)
|
8
|
+
@path = path
|
9
|
+
@content = nil
|
10
|
+
@last_mtime = Time.now
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Boolean]
|
14
|
+
def exist?
|
15
|
+
@path.exist?
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Time]
|
19
|
+
def mtime
|
20
|
+
@path.mtime
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
def content
|
25
|
+
mod_time = mtime
|
26
|
+
|
27
|
+
if @content.nil? || mod_time > @last_mtime
|
28
|
+
@content = @path.read
|
29
|
+
@last_mtime = mod_time
|
30
|
+
end
|
31
|
+
|
32
|
+
@content
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -8,8 +8,10 @@ module GryphonNest
|
|
8
8
|
# Renders a Mustache template into a html file
|
9
9
|
class MustacheProcessor
|
10
10
|
# @param renderer [Renderers::MustacheRenderer]
|
11
|
-
|
11
|
+
# @param layout_file [LayoutFile]
|
12
|
+
def initialize(renderer, layout_file)
|
12
13
|
@renderer = renderer
|
14
|
+
@layout_file = layout_file
|
13
15
|
end
|
14
16
|
|
15
17
|
# @param src [Pathname]
|
@@ -17,8 +19,6 @@ module GryphonNest
|
|
17
19
|
# @raise [Errors::YamlError]
|
18
20
|
# @raise [Errors::ParseError]
|
19
21
|
def process(src, dest)
|
20
|
-
@layout ||= read_layout_file
|
21
|
-
|
22
22
|
context = read_context(src)
|
23
23
|
content = build_output(src, context)
|
24
24
|
write_file(dest, content)
|
@@ -36,11 +36,21 @@ module GryphonNest
|
|
36
36
|
path.join('index.html')
|
37
37
|
end
|
38
38
|
|
39
|
-
# @param
|
40
|
-
# @param
|
39
|
+
# @param src [Pathname]
|
40
|
+
# @param dest [Pathname]
|
41
41
|
# @return [Boolean]
|
42
|
-
def file_modified?(
|
43
|
-
true
|
42
|
+
def file_modified?(src, dest)
|
43
|
+
return true unless dest.exist?
|
44
|
+
|
45
|
+
mod_time = dest.mtime
|
46
|
+
return true if src.mtime > mod_time
|
47
|
+
|
48
|
+
path = context_file_name(src)
|
49
|
+
return true if path.exist? && path.mtime > mod_time
|
50
|
+
|
51
|
+
return false unless @layout_file.exist?
|
52
|
+
|
53
|
+
@layout_file.mtime > mod_time
|
44
54
|
end
|
45
55
|
|
46
56
|
private
|
@@ -49,25 +59,30 @@ module GryphonNest
|
|
49
59
|
# @return [Hash]
|
50
60
|
# @raise [Errors::YamlError]
|
51
61
|
def read_context(src)
|
52
|
-
|
53
|
-
YAML.safe_load_file(path, symbolize_names: true)
|
62
|
+
YAML.safe_load_file(context_file_name(src), symbolize_names: true)
|
54
63
|
rescue IOError, Errno::ENOENT
|
55
64
|
{}
|
56
65
|
rescue Psych::SyntaxError => e
|
57
66
|
raise Errors::YamlError, "Encountered error while reading context file. Reason: #{e.message}"
|
58
67
|
end
|
59
68
|
|
69
|
+
# @param src [Pathname]
|
70
|
+
# @return [Pathname]
|
71
|
+
def context_file_name(src)
|
72
|
+
src.sub(CONTENT_DIR, DATA_DIR).sub_ext('.yaml')
|
73
|
+
end
|
74
|
+
|
60
75
|
# @param file [Pathname]
|
61
76
|
# @param context [Hash]
|
62
77
|
# @return [String]
|
63
78
|
# @raise [Errors::ParseError]
|
64
79
|
def build_output(file, context)
|
65
80
|
content =
|
66
|
-
if @
|
67
|
-
@renderer.render_file(file, context)
|
68
|
-
else
|
81
|
+
if @layout_file.exist?
|
69
82
|
context[:yield] = file.basename(TEMPLATE_EXT)
|
70
|
-
@renderer.render(@
|
83
|
+
@renderer.render(@layout_file.content, context)
|
84
|
+
else
|
85
|
+
@renderer.render_file(file, context)
|
71
86
|
end
|
72
87
|
|
73
88
|
HtmlBeautifier.beautify(content, stop_on_errors: true)
|
@@ -83,13 +98,6 @@ module GryphonNest
|
|
83
98
|
path.dirname.mkpath
|
84
99
|
path.write(content)
|
85
100
|
end
|
86
|
-
|
87
|
-
# @return [String]
|
88
|
-
def read_layout_file
|
89
|
-
File.read(LAYOUT_FILE)
|
90
|
-
rescue IOError, Errno::ENOENT
|
91
|
-
''
|
92
|
-
end
|
93
101
|
end
|
94
102
|
end
|
95
103
|
end
|
@@ -12,9 +12,10 @@ module GryphonNest
|
|
12
12
|
def create
|
13
13
|
ProcessorRegistry.new do |reg|
|
14
14
|
reg[TEMPLATE_EXT] = proc {
|
15
|
+
layout_file = LayoutFile.new(Pathname(LAYOUT_FILE))
|
15
16
|
renderer = Renderers::MustacheRenderer.new
|
16
17
|
renderer.template_path = CONTENT_DIR
|
17
|
-
Processors::MustacheProcessor.new(renderer)
|
18
|
+
Processors::MustacheProcessor.new(renderer, layout_file)
|
18
19
|
}
|
19
20
|
|
20
21
|
sass_proc = proc { Processors::SassProcessor.new }
|
data/lib/gryphon_nest/version.rb
CHANGED
data/lib/gryphon_nest.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'fileutils'
|
3
4
|
require 'listen'
|
4
5
|
require 'pathname'
|
5
6
|
require 'webrick'
|
6
7
|
|
7
8
|
module GryphonNest
|
8
9
|
autoload :Errors, 'gryphon_nest/errors'
|
10
|
+
autoload :GzipCompressor, 'gryphon_nest/gzip_compressor'
|
11
|
+
autoload :LayoutFile, 'gryphon_nest/layout_file'
|
9
12
|
autoload :Logging, 'gryphon_nest/logging'
|
10
13
|
autoload :Processors, 'gryphon_nest/processors'
|
11
14
|
autoload :Renderers, 'gryphon_nest/renderers'
|
@@ -18,43 +21,51 @@ module GryphonNest
|
|
18
21
|
LAYOUT_FILE = 'layout.mustache'
|
19
22
|
|
20
23
|
class Nest
|
21
|
-
|
24
|
+
# @return [GzipCompressor, nil]
|
25
|
+
attr_writer :compressor
|
26
|
+
|
27
|
+
# @param force [Boolean]
|
28
|
+
def initialize(force)
|
22
29
|
@processors = Processors.create
|
23
30
|
@logger = Logging.create
|
31
|
+
@force = force
|
32
|
+
@compressor = nil
|
33
|
+
@modifications = 0
|
24
34
|
end
|
25
35
|
|
26
36
|
# @raise [Errors::NotFoundError]
|
27
37
|
def build
|
28
|
-
|
38
|
+
unless Dir.exist?(CONTENT_DIR)
|
39
|
+
raise Errors::NotFoundError, "Content directory doesn't exist in the current directory"
|
40
|
+
end
|
29
41
|
|
30
42
|
Dir.mkdir(BUILD_DIR) unless Dir.exist?(BUILD_DIR)
|
31
43
|
|
32
|
-
existing_files = glob(
|
33
|
-
content_files = glob(
|
44
|
+
existing_files = glob(BUILD_DIR, '[!.gz]')
|
45
|
+
content_files = glob(CONTENT_DIR)
|
34
46
|
processed_files = content_files.collect { |src| process_file(src) }
|
35
|
-
existing_files.difference(processed_files)
|
47
|
+
files_to_delete = existing_files.difference(processed_files)
|
48
|
+
files_to_delete.each { |file| delete_file(file) }
|
49
|
+
|
50
|
+
@logger.info('No changes detected') if @modifications.zero? && files_to_delete.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def clean
|
54
|
+
FileUtils.remove_dir(BUILD_DIR, true)
|
55
|
+
@logger.info('Removed build dir')
|
36
56
|
end
|
37
57
|
|
38
58
|
def watch
|
39
59
|
@logger.info('Watching for content changes')
|
40
|
-
Listen.to(CONTENT_DIR, relative: true) do |modified, added, removed|
|
41
|
-
modified.union(added).each do |file|
|
42
|
-
path = Pathname(file)
|
43
|
-
process_file(path)
|
44
|
-
end
|
45
60
|
|
46
|
-
|
47
|
-
|
48
|
-
path = @processors[path.extname].dest_name(path)
|
49
|
-
delete_file(path)
|
50
|
-
end
|
51
|
-
end.start
|
61
|
+
# Bypass modification checks, we already know the files been changed
|
62
|
+
@force = true
|
52
63
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
64
|
+
only = [/^#{CONTENT_DIR}/, /^#{DATA_DIR}/, /^#{LAYOUT_FILE}$/]
|
65
|
+
Listen.to('.', relative: true, only: only) do |modified, added, removed|
|
66
|
+
modified.union(added).each { |file| process_changes(file) }
|
67
|
+
|
68
|
+
removed.each { |file| process_changes(file, removal: true) }
|
58
69
|
end.start
|
59
70
|
end
|
60
71
|
|
@@ -75,15 +86,51 @@ module GryphonNest
|
|
75
86
|
processor = @processors[src.extname]
|
76
87
|
dest = processor.dest_name(src)
|
77
88
|
|
78
|
-
if processor.file_modified?(src, dest)
|
89
|
+
if @force || processor.file_modified?(src, dest)
|
90
|
+
@modifications += 1
|
79
91
|
msg = File.exist?(dest) ? 'Recreating' : 'Creating'
|
80
92
|
@logger.info("#{msg} #{dest}")
|
81
93
|
processor.process(src, dest)
|
94
|
+
compress_file(dest)
|
82
95
|
end
|
83
96
|
|
84
97
|
dest
|
85
98
|
end
|
86
99
|
|
100
|
+
# @param file [Pathname]
|
101
|
+
def compress_file(file)
|
102
|
+
return unless @compressor.is_a?(GzipCompressor)
|
103
|
+
|
104
|
+
@logger.info("Compressing #{file}")
|
105
|
+
unless @compressor.can_compress?(file)
|
106
|
+
@logger.info("Skipping #{file}")
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
@compressor.compress(file)
|
111
|
+
end
|
112
|
+
|
113
|
+
# @param src [String]
|
114
|
+
# @param removal [Boolean]
|
115
|
+
def process_changes(src, removal: false)
|
116
|
+
if src == LAYOUT_FILE
|
117
|
+
glob(CONTENT_DIR, TEMPLATE_EXT).each { |file| process_file(file) }
|
118
|
+
else
|
119
|
+
path = Pathname(src)
|
120
|
+
|
121
|
+
if src.start_with?(DATA_DIR)
|
122
|
+
process_data_file(path)
|
123
|
+
elsif removal
|
124
|
+
path = @processors[path.extname].dest_name(path)
|
125
|
+
delete_file(path)
|
126
|
+
else
|
127
|
+
process_file(path)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
rescue StandardError => e
|
131
|
+
@logger.error(e.message)
|
132
|
+
end
|
133
|
+
|
87
134
|
# @param src [Pathname]
|
88
135
|
# @return [Pathname]
|
89
136
|
def process_data_file(src)
|
@@ -94,16 +141,21 @@ module GryphonNest
|
|
94
141
|
process_file(src)
|
95
142
|
end
|
96
143
|
|
97
|
-
# @params
|
144
|
+
# @params base [String]
|
145
|
+
# @params match [String]
|
98
146
|
# @return [Array<Pathname>]
|
99
|
-
def glob(
|
100
|
-
Pathname.glob(
|
147
|
+
def glob(base, match = '')
|
148
|
+
Pathname.glob("#{base}/**/*#{match}").reject(&:directory?)
|
101
149
|
end
|
102
150
|
|
103
151
|
# @param file [Pathname]
|
104
152
|
def delete_file(file)
|
105
|
-
|
106
|
-
|
153
|
+
[file, Pathname("#{file}.gz")].each do |f|
|
154
|
+
next unless f.exist?
|
155
|
+
|
156
|
+
@logger.info("Deleting #{f}")
|
157
|
+
f.delete
|
158
|
+
end
|
107
159
|
end
|
108
160
|
end
|
109
161
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gryphon_nest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0
|
4
|
+
version: 4.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christopher Birmingham
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-07-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: htmlbeautifier
|
@@ -121,6 +121,8 @@ files:
|
|
121
121
|
- bin/nest
|
122
122
|
- lib/gryphon_nest.rb
|
123
123
|
- lib/gryphon_nest/errors.rb
|
124
|
+
- lib/gryphon_nest/gzip_compressor.rb
|
125
|
+
- lib/gryphon_nest/layout_file.rb
|
124
126
|
- lib/gryphon_nest/logging.rb
|
125
127
|
- lib/gryphon_nest/processors.rb
|
126
128
|
- lib/gryphon_nest/processors/asset_processor.rb
|