moft 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.
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