gryphon_nest 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 007cb70e7643f8a15d8272811493c561373c316ce2d5f53d61f4e933e060a6b0
4
+ data.tar.gz: 61b6f2910f36870f10db5783d4bbb4b8e363f12f5a8ed8d7b0a213d91fbf8725
5
+ SHA512:
6
+ metadata.gz: ac43163a885e0ea4409f98707916d339753295ea1c59c64fa43aebb5b2e91077e597c46ef8db626cda1ac770c0c9f69992486db34cf76ecab2a7ff0b4751ab34
7
+ data.tar.gz: 5aa4d1916214adea0f0d2c857fb651e7f5b99b2368a2dad535097c8f2f6f8d5949357e0da0d6f5368af83373dd68bfffa0acd4b5ac3838982f23b61e10f26134
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
data/bin/nest ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'gryphon_nest'
5
+ require 'optparse'
6
+
7
+ DEFAULT_PORT = 8000
8
+
9
+ options = {
10
+ port: DEFAULT_PORT
11
+ }
12
+
13
+ # @param options [Hash]
14
+ # @return [Array]
15
+ begin
16
+ parser = OptionParser.new do |opts|
17
+ opts.banner = 'Usage: nest [build|serve] [options]'
18
+
19
+ opts.on('-p', '--port [PORT]', Integer, 'Port to run dev server on')
20
+
21
+ opts.on('-h', '--help', 'Show this message') do
22
+ puts opts
23
+ exit
24
+ end
25
+
26
+ opts.on('-v', '--version', 'Print version') do
27
+ puts GryphonNest::VERSION
28
+ exit
29
+ end
30
+ end
31
+
32
+ parser.parse!(into: options)
33
+
34
+ command = ARGV.fetch(0, 'build')
35
+
36
+ unless %w[build serve].include?(command)
37
+ warn "Unknown command #{command}"
38
+ warn parser
39
+ exit(1)
40
+ end
41
+
42
+ GryphonNest.build_website
43
+ GryphonNest.serve_website(options[:port]) if command == 'serve'
44
+ rescue OptionParser::ParseError => e
45
+ warn e.message
46
+ warn parser
47
+ exit(1)
48
+ rescue StandardError => e
49
+ warn e.message
50
+ exit(1)
51
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GryphonNest
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'htmlbeautifier'
5
+ require 'mustache'
6
+ require 'pathname'
7
+ require 'webrick'
8
+ require 'yaml'
9
+
10
+ module GryphonNest
11
+ autoload :VERSION, 'gryphon_nest/version'
12
+
13
+ class << self
14
+ BUILD_DIR = '_site'
15
+ CONTENT_DIR = 'content'
16
+ DATA_DIR = 'data'
17
+ ASSETS_DIR = 'assets'
18
+ LAYOUT_DIR = 'layouts'
19
+ TEMPLATE_EXT = '.mustache'
20
+
21
+ # @param template_name [String]
22
+ # @return [String]
23
+ def get_output_name(template_name)
24
+ dir = File.dirname(template_name)
25
+ basename = File.basename(template_name, TEMPLATE_EXT)
26
+
27
+ path = Pathname.new(dir)
28
+ path = path.sub(CONTENT_DIR, BUILD_DIR)
29
+
30
+ path = path.join(basename) if basename != 'index'
31
+
32
+ path = path.join('index.html')
33
+ path.to_s
34
+ end
35
+
36
+ # @param name [String]
37
+ # @param layouts [Hash]
38
+ # @param template_data [Hash]
39
+ # @return [String]
40
+ def get_template_layout(name, layouts, template_data)
41
+ if template_data.key?(:layout)
42
+ val = template_data[:layout]
43
+
44
+ raise "#{name} requires layout file #{val} but it isn't a known layout" unless layouts.key?(val)
45
+
46
+ layouts[val]
47
+ else
48
+ layouts.fetch('main', '{{{yield}}}')
49
+ end
50
+ end
51
+
52
+ # @param name [String]
53
+ # @param content [String]
54
+ # @param layouts [Hash]
55
+ # @param data [Hash]
56
+ # @return [String]
57
+ def render_template(name, content, layouts, data)
58
+ key = File.basename(name, TEMPLATE_EXT)
59
+ template_data = data.fetch(key, {})
60
+
61
+ content = Mustache.render(content, template_data)
62
+
63
+ layout = get_template_layout(name, layouts, template_data)
64
+ template_data[:yield] = content
65
+ Mustache.render(layout, template_data)
66
+ end
67
+
68
+ # @params path [String]
69
+ # @return [Array]
70
+ def filter_glob(path)
71
+ Dir.glob(path).reject do |p|
72
+ File.directory?(p)
73
+ end
74
+ end
75
+
76
+ # @param layouts [Hash]
77
+ # @param data [Hash]
78
+ # @return [Array]
79
+ def process_content(layouts, data)
80
+ created_files = []
81
+
82
+ filter_glob("#{CONTENT_DIR}/**/*").each do |template|
83
+ if File.extname(template) != TEMPLATE_EXT
84
+ puts "Skipping non template file #{template}"
85
+ next
86
+ end
87
+
88
+ File.open(template) do |f|
89
+ content = render_template(template, f.read, layouts, data)
90
+ content = HtmlBeautifier.beautify(content)
91
+ html_file = get_output_name(template)
92
+
93
+ dir = File.dirname(html_file)
94
+
95
+ unless Dir.exist?(dir)
96
+ puts "Creating #{dir}"
97
+ Dir.mkdir(dir)
98
+ end
99
+
100
+ File.write(html_file, content)
101
+ puts "Creating #{html_file}"
102
+
103
+ created_files << html_file
104
+ end
105
+ end
106
+
107
+ created_files
108
+ end
109
+
110
+ # @param src [String]
111
+ # @param dest [String]
112
+ # @return [Boolean]
113
+ def can_copy_asset?(src, dest)
114
+ return true unless File.exist?(dest)
115
+
116
+ File.mtime(src) > File.mtime(dest)
117
+ end
118
+
119
+ # @return [Array]
120
+ def copy_assets
121
+ return [] unless Dir.exist?(ASSETS_DIR)
122
+
123
+ copied_files = []
124
+ filter_glob("#{ASSETS_DIR}/**/*").each do |asset|
125
+ dest = Pathname.new(asset)
126
+ dest = dest.sub(ASSETS_DIR, BUILD_DIR)
127
+ copied_files << dest.to_s
128
+
129
+ next unless can_copy_asset?(asset, dest)
130
+
131
+ dest_dir = File.dirname(dest)
132
+ FileUtils.makedirs(dest_dir)
133
+ FileUtils.copy_file(asset, dest)
134
+ puts "Copying #{asset} to #{dest}"
135
+ end
136
+
137
+ copied_files
138
+ end
139
+
140
+ # @return [Hash]
141
+ def read_layout_files
142
+ return {} unless Dir.exist?(LAYOUT_DIR)
143
+
144
+ layouts = {}
145
+
146
+ Dir.glob("#{LAYOUT_DIR}/*#{TEMPLATE_EXT}") do |f|
147
+ key = File.basename(f, TEMPLATE_EXT)
148
+ layouts[key] = File.read(f)
149
+ end
150
+
151
+ layouts
152
+ end
153
+
154
+ # @param file [String]
155
+ # @return [Hash]
156
+ def read_yaml(file)
157
+ File.open(file) do |yaml|
158
+ YAML.safe_load(yaml)
159
+ end
160
+ end
161
+
162
+ # @return [Hash]
163
+ def read_data_files
164
+ return {} unless Dir.exist?(DATA_DIR)
165
+
166
+ data = {}
167
+
168
+ Dir.glob("#{DATA_DIR}/*.{yaml,yml}") do |f|
169
+ key = File.basename(f, '.*')
170
+ data[key] = read_yaml(f)
171
+ end
172
+
173
+ data
174
+ end
175
+
176
+ # @param junk_files [Array]
177
+ def cleanup(junk_files)
178
+ junk_files.each do |f|
179
+ puts "Deleting #{f}"
180
+ FileUtils.remove_file(f)
181
+ end
182
+ end
183
+
184
+ def build_website
185
+ raise "Content directory doesn't exist" unless Dir.exist?(CONTENT_DIR)
186
+
187
+ existing_files = []
188
+ if Dir.exist?(BUILD_DIR)
189
+ existing_files = filter_glob("#{BUILD_DIR}/**/*")
190
+ else
191
+ Dir.mkdir(BUILD_DIR)
192
+ end
193
+
194
+ data = read_data_files
195
+ layouts = read_layout_files
196
+ existing_files = existing_files.difference(process_content(layouts, data))
197
+ existing_files = existing_files.difference(copy_assets)
198
+ cleanup(existing_files)
199
+ end
200
+
201
+ # @param port [Integer]
202
+ def serve_website(port)
203
+ put "Running local server on #{port}"
204
+ server = WEBrick::HTTPServer.new(Port: port, DocumentRoot: BUILD_DIR)
205
+ # Trap ctrl c so we don't get the horrible stack trace
206
+ trap('INT') { server.shutdown }
207
+ server.start
208
+ end
209
+ end
210
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gryphon_nest
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Christopher Birmingham
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-04-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: htmlbeautifier
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mustache
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webrick
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ description: A slightly opinionated static website generator for those who like working
84
+ in html and mustache
85
+ email:
86
+ - chris.birmingham@hotmail.co.uk
87
+ executables:
88
+ - nest
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - LICENSE
93
+ - bin/nest
94
+ - lib/gryphon_nest.rb
95
+ - lib/gryphon_nest/version.rb
96
+ homepage: https://github.com/chrisBirmingham/gryphon_nest
97
+ licenses:
98
+ - Unlicense
99
+ metadata:
100
+ homepage_uri: https://github.com/chrisBirmingham/gryphon_nest
101
+ source_code_uri: https://github.com/chrisBirmingham/gryphon_nest
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 2.6.0
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubygems_version: 3.1.2
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Yet another static site generator
121
+ test_files: []