bunto 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.markdown +59 -0
- data/bin/bunto +51 -0
- data/lib/bunto.rb +179 -0
- data/lib/bunto/cleaner.rb +105 -0
- data/lib/bunto/collection.rb +205 -0
- data/lib/bunto/command.rb +65 -0
- data/lib/bunto/commands/build.rb +77 -0
- data/lib/bunto/commands/clean.rb +42 -0
- data/lib/bunto/commands/doctor.rb +114 -0
- data/lib/bunto/commands/help.rb +31 -0
- data/lib/bunto/commands/new.rb +82 -0
- data/lib/bunto/commands/serve.rb +204 -0
- data/lib/bunto/commands/serve/servlet.rb +61 -0
- data/lib/bunto/configuration.rb +323 -0
- data/lib/bunto/converter.rb +48 -0
- data/lib/bunto/converters/identity.rb +21 -0
- data/lib/bunto/converters/markdown.rb +92 -0
- data/lib/bunto/converters/markdown/kramdown_parser.rb +117 -0
- data/lib/bunto/converters/markdown/rdiscount_parser.rb +33 -0
- data/lib/bunto/converters/markdown/redcarpet_parser.rb +102 -0
- data/lib/bunto/converters/smartypants.rb +34 -0
- data/lib/bunto/convertible.rb +297 -0
- data/lib/bunto/deprecator.rb +46 -0
- data/lib/bunto/document.rb +444 -0
- data/lib/bunto/drops/bunto_drop.rb +21 -0
- data/lib/bunto/drops/collection_drop.rb +22 -0
- data/lib/bunto/drops/document_drop.rb +27 -0
- data/lib/bunto/drops/drop.rb +176 -0
- data/lib/bunto/drops/site_drop.rb +38 -0
- data/lib/bunto/drops/unified_payload_drop.rb +25 -0
- data/lib/bunto/drops/url_drop.rb +83 -0
- data/lib/bunto/entry_filter.rb +72 -0
- data/lib/bunto/errors.rb +10 -0
- data/lib/bunto/excerpt.rb +127 -0
- data/lib/bunto/external.rb +59 -0
- data/lib/bunto/filters.rb +367 -0
- data/lib/bunto/frontmatter_defaults.rb +188 -0
- data/lib/bunto/generator.rb +3 -0
- data/lib/bunto/hooks.rb +101 -0
- data/lib/bunto/layout.rb +49 -0
- data/lib/bunto/liquid_extensions.rb +22 -0
- data/lib/bunto/liquid_renderer.rb +39 -0
- data/lib/bunto/liquid_renderer/file.rb +50 -0
- data/lib/bunto/liquid_renderer/table.rb +94 -0
- data/lib/bunto/log_adapter.rb +115 -0
- data/lib/bunto/mime.types +800 -0
- data/lib/bunto/page.rb +180 -0
- data/lib/bunto/plugin.rb +96 -0
- data/lib/bunto/plugin_manager.rb +95 -0
- data/lib/bunto/post.rb +329 -0
- data/lib/bunto/publisher.rb +21 -0
- data/lib/bunto/reader.rb +126 -0
- data/lib/bunto/readers/collection_reader.rb +20 -0
- data/lib/bunto/readers/data_reader.rb +69 -0
- data/lib/bunto/readers/layout_reader.rb +53 -0
- data/lib/bunto/readers/page_reader.rb +21 -0
- data/lib/bunto/readers/post_reader.rb +62 -0
- data/lib/bunto/readers/static_file_reader.rb +21 -0
- data/lib/bunto/regenerator.rb +175 -0
- data/lib/bunto/related_posts.rb +56 -0
- data/lib/bunto/renderer.rb +191 -0
- data/lib/bunto/site.rb +391 -0
- data/lib/bunto/static_file.rb +141 -0
- data/lib/bunto/stevenson.rb +58 -0
- data/lib/bunto/tags/highlight.rb +122 -0
- data/lib/bunto/tags/include.rb +190 -0
- data/lib/bunto/tags/post_url.rb +88 -0
- data/lib/bunto/url.rb +136 -0
- data/lib/bunto/utils.rb +287 -0
- data/lib/bunto/utils/ansi.rb +59 -0
- data/lib/bunto/utils/platforms.rb +30 -0
- data/lib/bunto/version.rb +3 -0
- data/lib/site_template/.gitignore +3 -0
- data/lib/site_template/_config.yml +21 -0
- data/lib/site_template/_includes/footer.html +38 -0
- data/lib/site_template/_includes/head.html +12 -0
- data/lib/site_template/_includes/header.html +27 -0
- data/lib/site_template/_includes/icon-github.html +1 -0
- data/lib/site_template/_includes/icon-github.svg +1 -0
- data/lib/site_template/_includes/icon-twitter.html +1 -0
- data/lib/site_template/_includes/icon-twitter.svg +1 -0
- data/lib/site_template/_layouts/default.html +20 -0
- data/lib/site_template/_layouts/page.html +14 -0
- data/lib/site_template/_layouts/post.html +15 -0
- data/lib/site_template/_posts/0000-00-00-welcome-to-bunto.markdown.erb +25 -0
- data/lib/site_template/_sass/_base.scss +206 -0
- data/lib/site_template/_sass/_layout.scss +242 -0
- data/lib/site_template/_sass/_syntax-highlighting.scss +71 -0
- data/lib/site_template/about.md +15 -0
- data/lib/site_template/css/main.scss +53 -0
- data/lib/site_template/feed.xml +30 -0
- data/lib/site_template/index.html +23 -0
- metadata +252 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
module Bunto
|
2
|
+
class Command
|
3
|
+
class << self
|
4
|
+
# A list of subclasses of Bunto::Command
|
5
|
+
def subclasses
|
6
|
+
@subclasses ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
# Keep a list of subclasses of Bunto::Command every time it's inherited
|
10
|
+
# Called automatically.
|
11
|
+
#
|
12
|
+
# base - the subclass
|
13
|
+
#
|
14
|
+
# Returns nothing
|
15
|
+
def inherited(base)
|
16
|
+
subclasses << base
|
17
|
+
super(base)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Run Site#process and catch errors
|
21
|
+
#
|
22
|
+
# site - the Bunto::Site object
|
23
|
+
#
|
24
|
+
# Returns nothing
|
25
|
+
def process_site(site)
|
26
|
+
site.process
|
27
|
+
rescue Bunto::Errors::FatalException => e
|
28
|
+
Bunto.logger.error "ERROR:", "YOUR SITE COULD NOT BE BUILT:"
|
29
|
+
Bunto.logger.error "", "------------------------------------"
|
30
|
+
Bunto.logger.error "", e.message
|
31
|
+
exit(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create a full Bunto configuration with the options passed in as overrides
|
35
|
+
#
|
36
|
+
# options - the configuration overrides
|
37
|
+
#
|
38
|
+
# Returns a full Bunto configuration
|
39
|
+
def configuration_from_options(options)
|
40
|
+
Bunto.configuration(options)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Add common options to a command for building configuration
|
44
|
+
#
|
45
|
+
# c - the Bunto::Command to add these options to
|
46
|
+
#
|
47
|
+
# Returns nothing
|
48
|
+
def add_build_options(c)
|
49
|
+
c.option 'config', '--config CONFIG_FILE[,CONFIG_FILE2,...]', Array, 'Custom configuration file'
|
50
|
+
c.option 'destination', '-d', '--destination DESTINATION', 'The current folder will be generated into DESTINATION'
|
51
|
+
c.option 'source', '-s', '--source SOURCE', 'Custom source directory'
|
52
|
+
c.option 'future', '--future', 'Publishes posts with a future date'
|
53
|
+
c.option 'limit_posts', '--limit_posts MAX_POSTS', Integer, 'Limits the number of posts to parse and publish'
|
54
|
+
c.option 'watch', '-w', '--[no-]watch', 'Watch for changes and rebuild'
|
55
|
+
c.option 'force_polling', '--force_polling', 'Force watch to use polling'
|
56
|
+
c.option 'lsi', '--lsi', 'Use LSI for improved related posts'
|
57
|
+
c.option 'show_drafts', '-D', '--drafts', 'Render posts in the _drafts folder'
|
58
|
+
c.option 'unpublished', '--unpublished', 'Render posts that were marked as unpublished'
|
59
|
+
c.option 'quiet', '-q', '--quiet', 'Silence output.'
|
60
|
+
c.option 'verbose', '-V', '--verbose', 'Print verbose output.'
|
61
|
+
c.option 'incremental', '-I', '--incremental', 'Enable incremental rebuild.'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Bunto
|
2
|
+
module Commands
|
3
|
+
class Build < Command
|
4
|
+
class << self
|
5
|
+
# Create the Mercenary command for the Bunto CLI for this Command
|
6
|
+
def init_with_program(prog)
|
7
|
+
prog.command(:build) do |c|
|
8
|
+
c.syntax 'build [options]'
|
9
|
+
c.description 'Build your site'
|
10
|
+
c.alias :b
|
11
|
+
|
12
|
+
add_build_options(c)
|
13
|
+
|
14
|
+
c.action do |_, options|
|
15
|
+
options["serving"] = false
|
16
|
+
Bunto::Commands::Build.process(options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Build your bunto site
|
22
|
+
# Continuously watch if `watch` is set to true in the config.
|
23
|
+
def process(options)
|
24
|
+
# Adjust verbosity quickly
|
25
|
+
Bunto.logger.adjust_verbosity(options)
|
26
|
+
|
27
|
+
options = configuration_from_options(options)
|
28
|
+
site = Bunto::Site.new(options)
|
29
|
+
|
30
|
+
if options.fetch('skip_initial_build', false)
|
31
|
+
Bunto.logger.warn "Build Warning:", "Skipping the initial build. This may result in an out-of-date site."
|
32
|
+
else
|
33
|
+
build(site, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
if options.fetch('detach', false)
|
37
|
+
Bunto.logger.info "Auto-regeneration:", "disabled when running server detached."
|
38
|
+
elsif options.fetch('watch', false)
|
39
|
+
watch(site, options)
|
40
|
+
else
|
41
|
+
Bunto.logger.info "Auto-regeneration:", "disabled. Use --watch to enable."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Build your Bunto site.
|
46
|
+
#
|
47
|
+
# site - the Bunto::Site instance to build
|
48
|
+
# options - A Hash of options passed to the command
|
49
|
+
#
|
50
|
+
# Returns nothing.
|
51
|
+
def build(site, options)
|
52
|
+
t = Time.now
|
53
|
+
source = options['source']
|
54
|
+
destination = options['destination']
|
55
|
+
incremental = options['incremental']
|
56
|
+
Bunto.logger.info "Source:", source
|
57
|
+
Bunto.logger.info "Destination:", destination
|
58
|
+
Bunto.logger.info "Incremental build:", (incremental ? "enabled" : "disabled. Enable with --incremental")
|
59
|
+
Bunto.logger.info "Generating..."
|
60
|
+
process_site(site)
|
61
|
+
Bunto.logger.info "", "done in #{(Time.now - t).round(3)} seconds."
|
62
|
+
end
|
63
|
+
|
64
|
+
# Private: Watch for file changes and rebuild the site.
|
65
|
+
#
|
66
|
+
# site - A Bunto::Site instance
|
67
|
+
# options - A Hash of options passed to the command
|
68
|
+
#
|
69
|
+
# Returns nothing.
|
70
|
+
def watch(_site, options)
|
71
|
+
External.require_with_graceful_fail 'bunto-watch'
|
72
|
+
Bunto::Watcher.watch(options)
|
73
|
+
end
|
74
|
+
end # end of class << self
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Bunto
|
2
|
+
module Commands
|
3
|
+
class Clean < Command
|
4
|
+
class << self
|
5
|
+
def init_with_program(prog)
|
6
|
+
prog.command(:clean) do |c|
|
7
|
+
c.syntax 'clean [subcommand]'
|
8
|
+
c.description 'Clean the site (removes site output and metadata file) without building.'
|
9
|
+
|
10
|
+
add_build_options(c)
|
11
|
+
|
12
|
+
c.action do |_, options|
|
13
|
+
Bunto::Commands::Clean.process(options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def process(options)
|
19
|
+
options = configuration_from_options(options)
|
20
|
+
destination = options['destination']
|
21
|
+
metadata_file = File.join(options['source'], '.bunto-metadata')
|
22
|
+
|
23
|
+
if File.directory? destination
|
24
|
+
Bunto.logger.info "Cleaning #{destination}..."
|
25
|
+
FileUtils.rm_rf(destination)
|
26
|
+
Bunto.logger.info "", "done."
|
27
|
+
else
|
28
|
+
Bunto.logger.info "Nothing to do for #{destination}."
|
29
|
+
end
|
30
|
+
|
31
|
+
if File.file? metadata_file
|
32
|
+
Bunto.logger.info "Removing #{metadata_file}..."
|
33
|
+
FileUtils.rm_rf(metadata_file)
|
34
|
+
Bunto.logger.info "", "done."
|
35
|
+
else
|
36
|
+
Bunto.logger.info "Nothing to do for #{metadata_file}."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Bunto
|
2
|
+
module Commands
|
3
|
+
class Doctor < Command
|
4
|
+
class << self
|
5
|
+
def init_with_program(prog)
|
6
|
+
prog.command(:doctor) do |c|
|
7
|
+
c.syntax 'doctor'
|
8
|
+
c.description 'Search site and print specific deprecation warnings'
|
9
|
+
c.alias(:hyde)
|
10
|
+
|
11
|
+
c.option '--config CONFIG_FILE[,CONFIG_FILE2,...]', Array, 'Custom configuration file'
|
12
|
+
|
13
|
+
c.action do |_, options|
|
14
|
+
Bunto::Commands::Doctor.process(options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def process(options)
|
20
|
+
site = Bunto::Site.new(configuration_from_options(options))
|
21
|
+
site.read
|
22
|
+
|
23
|
+
if healthy?(site)
|
24
|
+
Bunto.logger.info "Your test results", "are in. Everything looks fine."
|
25
|
+
else
|
26
|
+
abort
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def healthy?(site)
|
31
|
+
[
|
32
|
+
fsnotify_buggy?(site),
|
33
|
+
!deprecated_relative_permalinks(site),
|
34
|
+
!conflicting_urls(site),
|
35
|
+
!urls_only_differ_by_case(site)
|
36
|
+
].all?
|
37
|
+
end
|
38
|
+
|
39
|
+
def deprecated_relative_permalinks(site)
|
40
|
+
if site.config['relative_permalinks']
|
41
|
+
Bunto::Deprecator.deprecation_message "Your site still uses relative" \
|
42
|
+
" permalinks, which was removed in" \
|
43
|
+
" Bunto v3.0.0."
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def conflicting_urls(site)
|
49
|
+
conflicting_urls = false
|
50
|
+
urls = {}
|
51
|
+
urls = collect_urls(urls, site.pages, site.dest)
|
52
|
+
urls = collect_urls(urls, site.posts.docs, site.dest)
|
53
|
+
urls.each do |url, paths|
|
54
|
+
next unless paths.size > 1
|
55
|
+
conflicting_urls = true
|
56
|
+
Bunto.logger.warn "Conflict:", "The URL '#{url}' is the destination" \
|
57
|
+
" for the following pages: #{paths.join(", ")}"
|
58
|
+
end
|
59
|
+
conflicting_urls
|
60
|
+
end
|
61
|
+
|
62
|
+
def fsnotify_buggy?(_site)
|
63
|
+
return true unless Utils::Platforms.osx?
|
64
|
+
if Dir.pwd != `pwd`.strip
|
65
|
+
Bunto.logger.error " " + <<-STR.strip.gsub(/\n\s+/, "\n ")
|
66
|
+
We have detected that there might be trouble using fsevent on your
|
67
|
+
operating system, you can read https://github.com/thibaudgg/rb-fsevent/wiki/no-fsevents-fired-(OSX-bug)
|
68
|
+
for possible work arounds or you can work around it immediately
|
69
|
+
with `--force-polling`.
|
70
|
+
STR
|
71
|
+
|
72
|
+
false
|
73
|
+
end
|
74
|
+
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def urls_only_differ_by_case(site)
|
79
|
+
urls_only_differ_by_case = false
|
80
|
+
urls = case_insensitive_urls(site.pages + site.docs_to_write, site.dest)
|
81
|
+
urls.each do |case_insensitive_url, real_urls|
|
82
|
+
next unless real_urls.uniq.size > 1
|
83
|
+
urls_only_differ_by_case = true
|
84
|
+
Bunto.logger.warn "Warning:", "The following URLs only differ" \
|
85
|
+
" by case. On a case-insensitive file system one of the URLs" \
|
86
|
+
" will be overwritten by the other: #{real_urls.join(", ")}"
|
87
|
+
end
|
88
|
+
urls_only_differ_by_case
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
def collect_urls(urls, things, destination)
|
93
|
+
things.each do |thing|
|
94
|
+
dest = thing.destination(destination)
|
95
|
+
if urls[dest]
|
96
|
+
urls[dest] << thing.path
|
97
|
+
else
|
98
|
+
urls[dest] = [thing.path]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
urls
|
102
|
+
end
|
103
|
+
|
104
|
+
def case_insensitive_urls(things, destination)
|
105
|
+
things.inject({}) do |memo, thing|
|
106
|
+
dest = thing.destination(destination)
|
107
|
+
(memo[dest.downcase] ||= []) << dest
|
108
|
+
memo
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Bunto
|
2
|
+
module Commands
|
3
|
+
class Help < Command
|
4
|
+
class << self
|
5
|
+
def init_with_program(prog)
|
6
|
+
prog.command(:help) do |c|
|
7
|
+
c.syntax 'help [subcommand]'
|
8
|
+
c.description 'Show the help message, optionally for a given subcommand.'
|
9
|
+
|
10
|
+
c.action do |args, _|
|
11
|
+
cmd = (args.first || "").to_sym
|
12
|
+
if args.empty?
|
13
|
+
puts prog
|
14
|
+
elsif prog.has_command? cmd
|
15
|
+
puts prog.commands[cmd]
|
16
|
+
else
|
17
|
+
invalid_command(prog, cmd)
|
18
|
+
abort
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def invalid_command(prog, cmd)
|
25
|
+
Bunto.logger.error "Error:", "Hmm... we don't know what the '#{cmd}' command is."
|
26
|
+
Bunto.logger.info "Valid commands:", prog.commands.keys.join(", ")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Bunto
|
4
|
+
module Commands
|
5
|
+
class New < Command
|
6
|
+
class << self
|
7
|
+
def init_with_program(prog)
|
8
|
+
prog.command(:new) do |c|
|
9
|
+
c.syntax 'new PATH'
|
10
|
+
c.description 'Creates a new Bunto site scaffold in PATH'
|
11
|
+
|
12
|
+
c.option 'force', '--force', 'Force creation even if PATH already exists'
|
13
|
+
c.option 'blank', '--blank', 'Creates scaffolding but with empty files'
|
14
|
+
|
15
|
+
c.action do |args, options|
|
16
|
+
Bunto::Commands::New.process(args, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def process(args, options = {})
|
22
|
+
raise ArgumentError.new('You must specify a path.') if args.empty?
|
23
|
+
|
24
|
+
new_blog_path = File.expand_path(args.join(" "), Dir.pwd)
|
25
|
+
FileUtils.mkdir_p new_blog_path
|
26
|
+
if preserve_source_location?(new_blog_path, options)
|
27
|
+
Bunto.logger.abort_with "Conflict:", "#{new_blog_path} exists and is not empty."
|
28
|
+
end
|
29
|
+
|
30
|
+
if options["blank"]
|
31
|
+
create_blank_site new_blog_path
|
32
|
+
else
|
33
|
+
create_sample_files new_blog_path
|
34
|
+
|
35
|
+
File.open(File.expand_path(initialized_post_name, new_blog_path), "w") do |f|
|
36
|
+
f.write(scaffold_post_content)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Bunto.logger.info "New bunto site installed in #{new_blog_path}."
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_blank_site(path)
|
44
|
+
Dir.chdir(path) do
|
45
|
+
FileUtils.mkdir(%w(_layouts _posts _drafts))
|
46
|
+
FileUtils.touch("index.html")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def scaffold_post_content
|
51
|
+
ERB.new(File.read(File.expand_path(scaffold_path, site_template))).result
|
52
|
+
end
|
53
|
+
|
54
|
+
# Internal: Gets the filename of the sample post to be created
|
55
|
+
#
|
56
|
+
# Returns the filename of the sample post, as a String
|
57
|
+
def initialized_post_name
|
58
|
+
"_posts/#{Time.now.strftime('%Y-%m-%d')}-welcome-to-bunto.markdown"
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def preserve_source_location?(path, options)
|
64
|
+
!options["force"] && !Dir["#{path}/**/*"].empty?
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_sample_files(path)
|
68
|
+
FileUtils.cp_r site_template + '/.', path
|
69
|
+
FileUtils.rm File.expand_path(scaffold_path, path)
|
70
|
+
end
|
71
|
+
|
72
|
+
def site_template
|
73
|
+
File.expand_path("../../site_template", File.dirname(__FILE__))
|
74
|
+
end
|
75
|
+
|
76
|
+
def scaffold_path
|
77
|
+
"_posts/0000-00-00-welcome-to-bunto.markdown.erb"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Bunto
|
2
|
+
module Commands
|
3
|
+
class Serve < Command
|
4
|
+
class << self
|
5
|
+
COMMAND_OPTIONS = {
|
6
|
+
"ssl_cert" => ["--ssl-cert [CERT]", "X.509 (SSL) certificate."],
|
7
|
+
"host" => ["host", "-H", "--host [HOST]", "Host to bind to"],
|
8
|
+
"open_url" => ["-o", "--open-url", "Launch your browser with your site."],
|
9
|
+
"detach" => ["-B", "--detach", "Run the server in the background (detach)"],
|
10
|
+
"ssl_key" => ["--ssl-key [KEY]", "X.509 (SSL) Private Key."],
|
11
|
+
"port" => ["-P", "--port [PORT]", "Port to listen on"],
|
12
|
+
"baseurl" => ["-b", "--baseurl [URL]", "Base URL"],
|
13
|
+
"skip_initial_build" => ["skip_initial_build", "--skip-initial-build",
|
14
|
+
"Skips the initial site build which occurs before the server is started."]
|
15
|
+
}
|
16
|
+
|
17
|
+
#
|
18
|
+
|
19
|
+
def init_with_program(prog)
|
20
|
+
prog.command(:serve) do |cmd|
|
21
|
+
cmd.description "Serve your site locally"
|
22
|
+
cmd.syntax "serve [options]"
|
23
|
+
cmd.alias :server
|
24
|
+
cmd.alias :s
|
25
|
+
|
26
|
+
add_build_options(cmd)
|
27
|
+
COMMAND_OPTIONS.each do |key, val|
|
28
|
+
cmd.option key, *val
|
29
|
+
end
|
30
|
+
|
31
|
+
cmd.action do |_, opts|
|
32
|
+
opts["serving"] = true
|
33
|
+
opts["watch" ] = true unless opts.key?("watch")
|
34
|
+
Build.process(opts)
|
35
|
+
Serve.process(opts)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
|
42
|
+
def process(opts)
|
43
|
+
opts = configuration_from_options(opts)
|
44
|
+
destination = opts["destination"]
|
45
|
+
setup(destination)
|
46
|
+
|
47
|
+
server = WEBrick::HTTPServer.new(webrick_opts(opts)).tap { |o| o.unmount("") }
|
48
|
+
server.mount(opts["baseurl"], Servlet, destination, file_handler_opts)
|
49
|
+
Bunto.logger.info "Server address:", server_address(server, opts)
|
50
|
+
launch_browser server, opts if opts["open_url"]
|
51
|
+
boot_or_detach server, opts
|
52
|
+
end
|
53
|
+
|
54
|
+
# Do a base pre-setup of WEBRick so that everything is in place
|
55
|
+
# when we get ready to party, checking for an setting up an error page
|
56
|
+
# and making sure our destination exists.
|
57
|
+
|
58
|
+
private
|
59
|
+
def setup(destination)
|
60
|
+
require_relative "serve/servlet"
|
61
|
+
|
62
|
+
FileUtils.mkdir_p(destination)
|
63
|
+
if File.exist?(File.join(destination, "404.html"))
|
64
|
+
WEBrick::HTTPResponse.class_eval do
|
65
|
+
def create_error_page
|
66
|
+
@header["Content-Type"] = "text/html; charset=UTF-8"
|
67
|
+
@body = IO.read(File.join(@config[:DocumentRoot], "404.html"))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
|
75
|
+
private
|
76
|
+
def webrick_opts(opts)
|
77
|
+
opts = {
|
78
|
+
:BuntoOptions => opts,
|
79
|
+
:DoNotReverseLookup => true,
|
80
|
+
:MimeTypes => mime_types,
|
81
|
+
:DocumentRoot => opts["destination"],
|
82
|
+
:StartCallback => start_callback(opts["detach"]),
|
83
|
+
:BindAddress => opts["host"],
|
84
|
+
:Port => opts["port"],
|
85
|
+
:DirectoryIndex => %W(
|
86
|
+
index.htm
|
87
|
+
index.html
|
88
|
+
index.rhtml
|
89
|
+
index.cgi
|
90
|
+
index.xml
|
91
|
+
)
|
92
|
+
}
|
93
|
+
|
94
|
+
enable_ssl(opts)
|
95
|
+
enable_logging(opts)
|
96
|
+
opts
|
97
|
+
end
|
98
|
+
|
99
|
+
# Recreate NondisclosureName under utf-8 circumstance
|
100
|
+
|
101
|
+
private
|
102
|
+
def file_handler_opts
|
103
|
+
WEBrick::Config::FileHandler.merge({
|
104
|
+
:FancyIndexing => true,
|
105
|
+
:NondisclosureName => [
|
106
|
+
'.ht*', '~*'
|
107
|
+
]
|
108
|
+
})
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
|
113
|
+
private
|
114
|
+
def server_address(server, opts)
|
115
|
+
address = server.config[:BindAddress]
|
116
|
+
baseurl = "#{opts["baseurl"]}/" if opts["baseurl"]
|
117
|
+
port = server.config[:Port]
|
118
|
+
|
119
|
+
"http://#{address}:#{port}#{baseurl}"
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
|
124
|
+
private
|
125
|
+
def launch_browser(server, opts)
|
126
|
+
command =
|
127
|
+
if Utils::Platforms.windows?
|
128
|
+
"start"
|
129
|
+
elsif Utils::Platforms.osx?
|
130
|
+
"open"
|
131
|
+
else
|
132
|
+
"xdg-open"
|
133
|
+
end
|
134
|
+
system command, server_address(server, opts)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Keep in our area with a thread or detach the server as requested
|
138
|
+
# by the user. This method determines what we do based on what you
|
139
|
+
# ask us to do.
|
140
|
+
|
141
|
+
private
|
142
|
+
def boot_or_detach(server, opts)
|
143
|
+
if opts["detach"]
|
144
|
+
pid = Process.fork do
|
145
|
+
server.start
|
146
|
+
end
|
147
|
+
|
148
|
+
Process.detach(pid)
|
149
|
+
Bunto.logger.info "Server detached with pid '#{pid}'.", \
|
150
|
+
"Run `pkill -f bunto' or `kill -9 #{pid}' to stop the server."
|
151
|
+
else
|
152
|
+
t = Thread.new { server.start }
|
153
|
+
trap("INT") { server.shutdown }
|
154
|
+
t.join
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Make the stack verbose if the user requests it.
|
159
|
+
|
160
|
+
private
|
161
|
+
def enable_logging(opts)
|
162
|
+
opts[:AccessLog] = []
|
163
|
+
level = WEBrick::Log.const_get(opts[:BuntoOptions]["verbose"] ? :DEBUG : :WARN)
|
164
|
+
opts[:Logger] = WEBrick::Log.new($stdout, level)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Add SSL to the stack if the user triggers --enable-ssl and they
|
168
|
+
# provide both types of certificates commonly needed. Raise if they
|
169
|
+
# forget to add one of the certificates.
|
170
|
+
|
171
|
+
private
|
172
|
+
def enable_ssl(opts)
|
173
|
+
return if !opts[:BuntoOptions]["ssl_cert"] && !opts[:BuntoOptions]["ssl_key"]
|
174
|
+
if !opts[:BuntoOptions]["ssl_cert"] || !opts[:BuntoOptions]["ssl_key"]
|
175
|
+
raise RuntimeError, "--ssl-cert or --ssl-key missing."
|
176
|
+
end
|
177
|
+
|
178
|
+
require "openssl"
|
179
|
+
require "webrick/https"
|
180
|
+
source_key = Bunto.sanitized_path(opts[:BuntoOptions]["source"], opts[:BuntoOptions]["ssl_key" ])
|
181
|
+
source_certificate = Bunto.sanitized_path(opts[:BuntoOptions]["source"], opts[:BuntoOptions]["ssl_cert"])
|
182
|
+
opts[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read(source_certificate))
|
183
|
+
opts[:SSLPrivateKey ] = OpenSSL::PKey::RSA.new(File.read(source_key))
|
184
|
+
opts[:EnableSSL] = true
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
def start_callback(detached)
|
189
|
+
unless detached
|
190
|
+
proc do
|
191
|
+
Bunto.logger.info("Server running...", "press ctrl-c to stop.")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
def mime_types
|
198
|
+
file = File.expand_path('../mime.types', File.dirname(__FILE__))
|
199
|
+
WEBrick::HTTPUtils.load_mime_types(file)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|