moft 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/Gemfile +2 -0
  2. data/LICENSE +21 -0
  3. data/bin/moft +83 -0
  4. data/lib/moft.rb +89 -0
  5. data/lib/moft/command.rb +27 -0
  6. data/lib/moft/commands/build.rb +64 -0
  7. data/lib/moft/commands/new.rb +50 -0
  8. data/lib/moft/commands/serve.rb +33 -0
  9. data/lib/moft/configuration.rb +171 -0
  10. data/lib/moft/converter.rb +48 -0
  11. data/lib/moft/converters/identity.rb +21 -0
  12. data/lib/moft/converters/markdown.rb +43 -0
  13. data/lib/moft/converters/markdown/kramdown_parser.rb +44 -0
  14. data/lib/moft/converters/markdown/maruku_parser.rb +47 -0
  15. data/lib/moft/converters/markdown/rdiscount_parser.rb +26 -0
  16. data/lib/moft/converters/markdown/redcarpet_parser.rb +40 -0
  17. data/lib/moft/converters/textile.rb +50 -0
  18. data/lib/moft/convertible.rb +152 -0
  19. data/lib/moft/core_ext.rb +68 -0
  20. data/lib/moft/deprecator.rb +34 -0
  21. data/lib/moft/draft.rb +35 -0
  22. data/lib/moft/errors.rb +4 -0
  23. data/lib/moft/filters.rb +141 -0
  24. data/lib/moft/generator.rb +4 -0
  25. data/lib/moft/generators/pagination.rb +131 -0
  26. data/lib/moft/layout.rb +42 -0
  27. data/lib/moft/logger.rb +52 -0
  28. data/lib/moft/mime.types +85 -0
  29. data/lib/moft/page.rb +147 -0
  30. data/lib/moft/plugin.rb +75 -0
  31. data/lib/moft/post.rb +377 -0
  32. data/lib/moft/site.rb +422 -0
  33. data/lib/moft/static_file.rb +70 -0
  34. data/lib/moft/tags/gist.rb +30 -0
  35. data/lib/moft/tags/highlight.rb +83 -0
  36. data/lib/moft/tags/include.rb +37 -0
  37. data/lib/moft/tags/post_url.rb +46 -0
  38. data/lib/site_template/_config.yml +1 -0
  39. data/lib/site_template/_layouts/default.html +38 -0
  40. data/lib/site_template/_layouts/post.html +6 -0
  41. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +24 -0
  42. data/lib/site_template/css/screen.css +189 -0
  43. data/lib/site_template/css/syntax.css +60 -0
  44. data/lib/site_template/images/.gitkeep +0 -0
  45. data/lib/site_template/images/rss.png +0 -0
  46. data/lib/site_template/index.html +13 -0
  47. data/moft.gemspec +100 -0
  48. metadata +412 -0
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2008 Tom Preston-Werner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the 'Software'), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ STDOUT.sync = true
3
+
4
+ $:.unshift File.join(File.dirname(__FILE__), *%w{ .. lib })
5
+
6
+ require 'commander/import'
7
+ require 'moft'
8
+
9
+ Moft::Deprecator.process(ARGV)
10
+
11
+ program :name, 'moft'
12
+ program :version, Moft::VERSION
13
+ program :description, 'Moft is a static site generator in Ruby, originally forked from Jekyll'
14
+
15
+ default_command :help
16
+
17
+ global_option '-s', '--source [DIR]', 'Source directory (defaults to ./)'
18
+ global_option '-d', '--destination [DIR]', 'Destination directory (defaults to ./_site)'
19
+ global_option '--safe', 'Safe mode (defaults to false)'
20
+ global_option '--plugins PLUGINS_DIR1[,PLUGINS_DIR2[,...]]', Array, 'Plugins directory (defaults to ./_plugins)'
21
+ global_option '--layouts', 'Layouts directory (defaults to ./_layouts)'
22
+
23
+ # Option names don't always directly match the configuration value we'd like.
24
+ # This method will rename options to match what Moft configuration expects.
25
+ #
26
+ # options - The Hash of options from Commander.
27
+ #
28
+ # Returns the normalized Hash.
29
+ def normalize_options(options)
30
+ if drafts_state = options.delete(:drafts)
31
+ options[:show_drafts] = drafts_state
32
+ end
33
+ options
34
+ end
35
+
36
+ command :new do |c|
37
+ c.syntax = 'moft new PATH'
38
+ c.description = 'Creates a new Moft site scaffold in PATH'
39
+
40
+ c.action do |args, options|
41
+ Moft::Commands::New.process(args)
42
+ end
43
+ end
44
+
45
+ command :build do |c|
46
+ c.syntax = 'moft build [options]'
47
+ c.description = 'Build your site'
48
+
49
+ c.option '--config CONFIG_FILE[,CONFIG_FILE2,...]', Array, 'Custom configuration file'
50
+ c.option '-w', '--watch', 'Watch for changes and rebuild'
51
+ c.option '--drafts', 'Render posts in the _drafts folder'
52
+
53
+ c.action do |args, options|
54
+ options = normalize_options(options.__hash__)
55
+ options = Moft.configuration(options)
56
+ Moft::Commands::Build.process(options)
57
+ end
58
+ end
59
+
60
+ command :serve do |c|
61
+ c.syntax = 'moft serve [options]'
62
+ c.description = 'Serve your site locally'
63
+
64
+ c.option '--config CONFIG_FILE[,CONFIG_FILE2,...]', Array, 'Custom configuration file'
65
+ c.option '-w', '--watch', 'Watch for changes and rebuild'
66
+ c.option '--drafts', 'Render posts in the _drafts folder'
67
+
68
+ c.option '-p', '--port [PORT]', 'Port to listen on'
69
+ c.option '-h', '--host [HOST]', 'Host to bind to'
70
+ c.option '-b', '--baseurl [URL]', 'Base URL'
71
+
72
+ c.action do |args, options|
73
+ options.default :serving => true
74
+
75
+ options = normalize_options(options.__hash__)
76
+ options = Moft.configuration(options)
77
+ Moft::Commands::Build.process(options)
78
+ Moft::Commands::Serve.process(options)
79
+ end
80
+ end
81
+ alias_command :server, :serve
82
+
83
+
@@ -0,0 +1,89 @@
1
+ $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
2
+
3
+ # Require all of the Ruby files in the given directory.
4
+ #
5
+ # path - The String relative path from here to the directory.
6
+ #
7
+ # Returns nothing.
8
+ def require_all(path)
9
+ glob = File.join(File.dirname(__FILE__), path, '*.rb')
10
+ Dir[glob].each do |f|
11
+ require f
12
+ end
13
+ end
14
+
15
+ # rubygems
16
+ require 'rubygems'
17
+
18
+ # stdlib
19
+ require 'fileutils'
20
+ require 'time'
21
+ require 'safe_yaml'
22
+ # require 'English'
23
+
24
+ # 3rd party
25
+ require 'liquid'
26
+ require 'maruku'
27
+ require 'colorator'
28
+
29
+ # internal requires
30
+ require 'moft/core_ext'
31
+ require 'moft/logger'
32
+ require 'moft/deprecator'
33
+ require 'moft/configuration'
34
+ require 'moft/site'
35
+ require 'moft/convertible'
36
+ require 'moft/layout'
37
+ require 'moft/page'
38
+ require 'moft/post'
39
+ require 'moft/draft'
40
+ require 'moft/filters'
41
+ require 'moft/static_file'
42
+ require 'moft/errors'
43
+
44
+ # extensions
45
+ require 'moft/plugin'
46
+ require 'moft/converter'
47
+ require 'moft/generator'
48
+ require 'moft/command'
49
+
50
+ require_all 'moft/commands'
51
+ require_all 'moft/converters'
52
+ require_all 'moft/converters/markdown'
53
+ require_all 'moft/generators'
54
+ require_all 'moft/tags'
55
+
56
+ SafeYAML::OPTIONS[:suppress_warnings] = true
57
+
58
+ module Moft
59
+ VERSION = '1.0.0'
60
+
61
+ # Public: Generate a Moft configuration Hash by merging the default
62
+ # options with anything in _config.yml, and adding the given options on top.
63
+ #
64
+ # override - A Hash of config directives that override any options in both
65
+ # the defaults and the config file. See Moft::Configuration::DEFAULTS for a
66
+ # list of option names and their defaults.
67
+ #
68
+ # Returns the final configuration Hash.
69
+ def self.configuration(override)
70
+ config = Configuration[Configuration::DEFAULTS]
71
+ override = Configuration[override].stringify_keys
72
+ config = config.read_config_files(config.config_files(override))
73
+
74
+ # Merge DEFAULTS < _config.yml < override
75
+ config = config.deep_merge(override).stringify_keys
76
+ set_timezone(config['timezone']) if config['timezone']
77
+
78
+ config
79
+ end
80
+
81
+ # Static: Set the TZ environment variable to use the timezone specified
82
+ #
83
+ # timezone - the IANA Time Zone
84
+ #
85
+ # Returns nothing
86
+ def self.set_timezone(timezone)
87
+ ENV['TZ'] = timezone
88
+ end
89
+ end
@@ -0,0 +1,27 @@
1
+ module Moft
2
+ class Command
3
+ def self.globs(source, destination)
4
+ Dir.chdir(source) do
5
+ dirs = Dir['*'].select { |x| File.directory?(x) }
6
+ dirs -= [destination, File.expand_path(destination), File.basename(destination)]
7
+ dirs = dirs.map { |x| "#{x}/**/*" }
8
+ dirs += ['*']
9
+ end
10
+ end
11
+
12
+ # Static: Run Site#process and catch errors
13
+ #
14
+ # site - the Moft::Site object
15
+ #
16
+ # Returns nothing
17
+ def self.process_site(site)
18
+ site.process
19
+ rescue Moft::FatalException => e
20
+ puts
21
+ Moft::Logger.error "ERROR:", "YOUR SITE COULD NOT BE BUILT:"
22
+ Moft::Logger.error "", "------------------------------------"
23
+ Moft::Logger.error "", e.message
24
+ exit(1)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ module Moft
2
+ module Commands
3
+ class Build < Command
4
+ def self.process(options)
5
+ site = Moft::Site.new(options)
6
+
7
+ self.build(site, options)
8
+ self.watch(site, options) if options['watch']
9
+ end
10
+
11
+ # Private: Build the site from source into destination.
12
+ #
13
+ # site - A Moft::Site instance
14
+ # options - A Hash of options passed to the command
15
+ #
16
+ # Returns nothing.
17
+ def self.build(site, options)
18
+ source = options['source']
19
+ destination = options['destination']
20
+ Moft::Logger.info "Source:", source
21
+ Moft::Logger.info "Destination:", destination
22
+ print Moft::Logger.formatted_topic "Generating..."
23
+ self.process_site(site)
24
+ puts "done."
25
+ end
26
+
27
+ # Private: Watch for file changes and rebuild the site.
28
+ #
29
+ # site - A Moft::Site instance
30
+ # options - A Hash of options passed to the command
31
+ #
32
+ # Returns nothing.
33
+ def self.watch(site, options)
34
+ require 'directory_watcher'
35
+
36
+ source = options['source']
37
+ destination = options['destination']
38
+
39
+ Moft::Logger.info "Auto-regeneration:", "enabled"
40
+
41
+ dw = DirectoryWatcher.new(source, :glob => self.globs(source, destination), :pre_load => true)
42
+ dw.interval = 1
43
+
44
+ dw.add_observer do |*args|
45
+ t = Time.now.strftime("%Y-%m-%d %H:%M:%S")
46
+ print Moft::Logger.formatted_topic("Regenerating:") + "#{args.size} files at #{t} "
47
+ self.process_site(site)
48
+ puts "...done."
49
+ end
50
+
51
+ dw.start
52
+
53
+ unless options['serving']
54
+ trap("INT") do
55
+ puts " Halting auto-regeneration."
56
+ exit 0
57
+ end
58
+
59
+ loop { sleep 1000 }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,50 @@
1
+ require 'erb'
2
+
3
+ module Moft
4
+ module Commands
5
+ class New < Command
6
+ def self.process(args)
7
+ raise ArgumentError.new('You must specify a path.') if args.empty?
8
+
9
+ new_blog_path = File.expand_path(args.join(" "), Dir.pwd)
10
+ FileUtils.mkdir_p new_blog_path
11
+ unless Dir["#{new_blog_path}/**/*"].empty?
12
+ Moft::Logger.error "Conflict:", "#{new_blog_path} exists and is not empty."
13
+ exit(1)
14
+ end
15
+
16
+ create_sample_files new_blog_path
17
+
18
+ File.open(File.expand_path(self.initialized_post_name, new_blog_path), "w") do |f|
19
+ f.write(self.scaffold_post_content(site_template))
20
+ end
21
+ puts "New moft site installed in #{new_blog_path}."
22
+ end
23
+
24
+ def self.scaffold_post_content(template_site)
25
+ ERB.new(File.read(File.expand_path(scaffold_path, site_template))).result
26
+ end
27
+
28
+ # Internal: Gets the filename of the sample post to be created
29
+ #
30
+ # Returns the filename of the sample post, as a String
31
+ def self.initialized_post_name
32
+ "_posts/#{Time.now.strftime('%Y-%m-%d')}-welcome-to-moft.markdown"
33
+ end
34
+
35
+ private
36
+ def self.create_sample_files(path)
37
+ FileUtils.cp_r site_template + '/.', path
38
+ FileUtils.rm File.expand_path(scaffold_path, path)
39
+ end
40
+
41
+ def self.site_template
42
+ File.expand_path("../../site_template", File.dirname(__FILE__))
43
+ end
44
+
45
+ def self.scaffold_path
46
+ "_posts/0000-00-00-welcome-to-moft.markdown.erb"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Moft
3
+ module Commands
4
+ class Serve < Command
5
+ def self.process(options)
6
+ require 'webrick'
7
+ include WEBrick
8
+
9
+ destination = options['destination']
10
+
11
+ FileUtils.mkdir_p(destination)
12
+
13
+ mime_types_file = File.expand_path('../mime.types', File.dirname(__FILE__))
14
+ mime_types = WEBrick::HTTPUtils::load_mime_types(mime_types_file)
15
+
16
+ # recreate NondisclosureName under utf-8 circumstance
17
+ fh_option = WEBrick::Config::FileHandler
18
+ fh_option[:NondisclosureName] = ['.ht*','~*']
19
+
20
+ s = HTTPServer.new(
21
+ :Port => options['port'],
22
+ :BindAddress => options['host'],
23
+ :MimeTypes => mime_types
24
+ )
25
+
26
+ s.mount(options['baseurl'], HTTPServlet::FileHandler, destination, fh_option)
27
+ t = Thread.new { s.start }
28
+ trap("INT") { s.shutdown }
29
+ t.join()
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,171 @@
1
+ # encoding: UTF-8
2
+
3
+ module Moft
4
+ class Configuration < Hash
5
+
6
+ # Default options. Overriden by values in _config.yml.
7
+ # Strings rather than symbols are used for compatability with YAML.
8
+ DEFAULTS = {
9
+ 'source' => Dir.pwd,
10
+ 'destination' => File.join(Dir.pwd, '_site'),
11
+ 'plugins' => '_plugins',
12
+ 'layouts' => '_layouts',
13
+ 'keep_files' => ['.git','.svn'],
14
+
15
+ 'timezone' => nil, # use the local timezone
16
+
17
+ 'safe' => false,
18
+ 'show_drafts' => nil,
19
+ 'limit_posts' => 0,
20
+ 'lsi' => false,
21
+ 'future' => true, # remove and make true just default
22
+ 'pygments' => true, # remove and make true just default
23
+
24
+ 'markdown' => 'maruku',
25
+ 'permalink' => 'date',
26
+ 'baseurl' => '/',
27
+ 'include' => ['.htaccess'],
28
+ 'exclude' => [],
29
+ 'paginate_path' => 'page:num',
30
+
31
+ 'markdown_ext' => 'markdown,mkd,mkdn,md',
32
+ 'textile_ext' => 'textile',
33
+
34
+ 'port' => '4000',
35
+ 'host' => '0.0.0.0',
36
+
37
+ 'excerpt_separator' => "\n\n",
38
+
39
+ 'maruku' => {
40
+ 'use_tex' => false,
41
+ 'use_divs' => false,
42
+ 'png_engine' => 'blahtex',
43
+ 'png_dir' => 'images/latex',
44
+ 'png_url' => '/images/latex'
45
+ },
46
+
47
+ 'rdiscount' => {
48
+ 'extensions' => []
49
+ },
50
+
51
+ 'redcarpet' => {
52
+ 'extensions' => []
53
+ },
54
+
55
+ 'kramdown' => {
56
+ 'auto_ids' => true,
57
+ 'footnote_nr' => 1,
58
+ 'entity_output' => 'as_char',
59
+ 'toc_levels' => '1..6',
60
+ 'smart_quotes' => 'lsquo,rsquo,ldquo,rdquo',
61
+ 'use_coderay' => false,
62
+
63
+ 'coderay' => {
64
+ 'coderay_wrap' => 'div',
65
+ 'coderay_line_numbers' => 'inline',
66
+ 'coderay_line_number_start' => 1,
67
+ 'coderay_tab_width' => 4,
68
+ 'coderay_bold_every' => 10,
69
+ 'coderay_css' => 'style'
70
+ }
71
+ },
72
+
73
+ 'redcloth' => {
74
+ 'hard_breaks' => true
75
+ }
76
+ }
77
+
78
+ # Public: Turn all keys into string
79
+ #
80
+ # Return a copy of the hash where all its keys are strings
81
+ def stringify_keys
82
+ reduce({}) { |hsh,(k,v)| hsh.merge(k.to_s => v) }
83
+ end
84
+
85
+ # Public: Directory of the Moft source folder
86
+ #
87
+ # override - the command-line options hash
88
+ #
89
+ # Returns the path to the Moft source directory
90
+ def source(override)
91
+ override['source'] || self['source'] || DEFAULTS['source']
92
+ end
93
+
94
+ # Public: Generate list of configuration files from the override
95
+ #
96
+ # override - the command-line options hash
97
+ #
98
+ # Returns an Array of config files
99
+ def config_files(override)
100
+ # Get configuration from <source>/_config.yml or <source>/<config_file>
101
+ config_files = override.delete('config')
102
+ config_files = File.join(source(override), "_config.yml") if config_files.to_s.empty?
103
+ config_files = [config_files] unless config_files.is_a? Array
104
+ config_files
105
+ end
106
+
107
+ # Public: Read configuration and return merged Hash
108
+ #
109
+ # file - the path to the YAML file to be read in
110
+ #
111
+ # Returns this configuration, overridden by the values in the file
112
+ def read_config_file(file)
113
+ next_config = YAML.safe_load_file(file)
114
+ raise "Configuration file: (INVALID) #{file}".yellow if !next_config.is_a?(Hash)
115
+ Moft::Logger.info "Configuration file:", file
116
+ next_config
117
+ end
118
+
119
+ # Public: Read in a list of configuration files and merge with this hash
120
+ #
121
+ # files - the list of configuration file paths
122
+ #
123
+ # Returns the full configuration, with the defaults overridden by the values in the
124
+ # configuration files
125
+ def read_config_files(files)
126
+ configuration = clone
127
+
128
+ begin
129
+ files.each do |config_file|
130
+ new_config = read_config_file(config_file)
131
+ configuration = configuration.deep_merge(new_config)
132
+ end
133
+ rescue SystemCallError
134
+ # Errno:ENOENT = file not found
135
+ Moft::Logger.warn "Configuration file:", "none"
136
+ rescue => err
137
+ Moft::Logger.warn "WARNING:", "Error reading configuration. " +
138
+ "Using defaults (and options)."
139
+ $stderr.puts "#{err}"
140
+ end
141
+
142
+ configuration.backwards_compatibilize
143
+ end
144
+
145
+ # Public: Ensure the proper options are set in the configuration to allow for
146
+ # backwards-compatibility with Moft pre-1.0
147
+ #
148
+ # Returns the backwards-compatible configuration
149
+ def backwards_compatibilize
150
+ config = clone
151
+ # Provide backwards-compatibility
152
+ if config.has_key?('auto') || config.has_key?('watch')
153
+ Moft::Logger.warn "Deprecation:", "Auto-regeneration can no longer" +
154
+ " be set from your configuration file(s). Use the"+
155
+ " --watch/-w command-line option instead."
156
+ config.delete('auto')
157
+ config.delete('watch')
158
+ end
159
+
160
+ if config.has_key? 'server'
161
+ Moft::Logger.warn "Deprecation:", "The 'server' configuration option" +
162
+ " is no longer accepted. Use the 'moft serve'" +
163
+ " subcommand to serve your site with WEBrick."
164
+ config.delete('server')
165
+ end
166
+
167
+ config
168
+ end
169
+
170
+ end
171
+ end