henshin 0.4.2 → 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/LICENCE +18 -0
  2. data/README.md +133 -0
  3. data/Rakefile +6 -51
  4. data/bin/henshin +127 -134
  5. data/lib/henshin.rb +163 -38
  6. data/lib/henshin/compressor.rb +31 -0
  7. data/lib/henshin/compressors/css.rb +20 -0
  8. data/lib/henshin/compressors/js.rb +20 -0
  9. data/lib/henshin/core_ext.rb +69 -0
  10. data/lib/henshin/error.rb +28 -0
  11. data/lib/henshin/file.rb +67 -0
  12. data/lib/henshin/files/abstract.rb +102 -0
  13. data/lib/henshin/files/attributes.rb +31 -0
  14. data/lib/henshin/files/empty_template.rb +24 -0
  15. data/lib/henshin/files/physical.rb +117 -0
  16. data/lib/henshin/files/post.rb +64 -0
  17. data/lib/henshin/files/templatable.rb +29 -0
  18. data/lib/henshin/files/template.rb +45 -0
  19. data/lib/henshin/files/tilt.rb +50 -0
  20. data/lib/henshin/files/tilt_template.rb +45 -0
  21. data/lib/henshin/package.rb +27 -0
  22. data/lib/henshin/packages/script.rb +23 -0
  23. data/lib/henshin/packages/style.rb +20 -0
  24. data/lib/henshin/path.rb +143 -0
  25. data/lib/henshin/publisher.rb +42 -0
  26. data/lib/henshin/publishers/sftp.rb +79 -0
  27. data/lib/henshin/reader.rb +68 -0
  28. data/lib/henshin/safety.rb +74 -0
  29. data/lib/henshin/scope.rb +55 -0
  30. data/lib/henshin/site.rb +291 -228
  31. data/lib/henshin/ui.rb +35 -0
  32. data/lib/henshin/version.rb +3 -0
  33. data/lib/henshin/writer.rb +43 -0
  34. data/lib/rack/henshin.rb +113 -0
  35. data/site/assets/scripts/config.js +11 -0
  36. data/site/assets/styles/_mixins.sass +16 -0
  37. data/site/assets/styles/screen.sass +198 -0
  38. data/site/config.yml +13 -0
  39. data/site/drafts/a-work-in-progress.md +5 -0
  40. data/site/feed.xml.slim +19 -0
  41. data/site/index.html.slim +28 -0
  42. data/site/init.rb +33 -0
  43. data/site/posts/1-hello-world.md +46 -0
  44. data/site/posts/2-code-testing.md +36 -0
  45. data/site/posts/3-style-test.md +80 -0
  46. data/site/templates/default.slim +11 -0
  47. data/site/templates/post.slim +30 -0
  48. data/site/test.html.md +7 -0
  49. data/spec/helper.rb +70 -0
  50. data/spec/henshin/compressor_spec.rb +25 -0
  51. data/spec/henshin/compressors/css_spec.rb +22 -0
  52. data/spec/henshin/compressors/js_spec.rb +22 -0
  53. data/spec/henshin/core_ext_spec.rb +59 -0
  54. data/spec/henshin/error_spec.rb +22 -0
  55. data/spec/henshin/file_spec.rb +28 -0
  56. data/spec/henshin/files/abstract_spec.rb +98 -0
  57. data/spec/henshin/files/attributes_spec.rb +25 -0
  58. data/spec/henshin/files/empty_template_spec.rb +11 -0
  59. data/spec/henshin/files/physical_spec.rb +55 -0
  60. data/spec/henshin/files/post_spec.rb +66 -0
  61. data/spec/henshin/files/template_spec.rb +53 -0
  62. data/spec/henshin/files/tilt_spec.rb +59 -0
  63. data/spec/henshin/package_spec.rb +24 -0
  64. data/spec/henshin/packages/script_spec.rb +17 -0
  65. data/spec/henshin/packages/style_spec.rb +17 -0
  66. data/spec/henshin/path_spec.rb +56 -0
  67. data/spec/henshin/publisher_spec.rb +44 -0
  68. data/spec/henshin/publishers/sftp_spec.rb +21 -0
  69. data/spec/henshin/reader_spec.rb +55 -0
  70. data/spec/henshin/safety_spec.rb +66 -0
  71. data/spec/henshin/scope_spec.rb +33 -0
  72. data/spec/henshin/site_spec.rb +142 -0
  73. data/spec/henshin/ui_spec.rb +26 -0
  74. data/spec/henshin/writer_spec.rb +26 -0
  75. metadata +352 -197
  76. data/.gitignore +0 -27
  77. data/LICENSE +0 -20
  78. data/README.markdown +0 -35
  79. data/VERSION +0 -1
  80. data/henshin.gemspec +0 -121
  81. data/lib/henshin/archive.rb +0 -133
  82. data/lib/henshin/exec/files.rb +0 -46
  83. data/lib/henshin/ext.rb +0 -55
  84. data/lib/henshin/gen.rb +0 -154
  85. data/lib/henshin/labels.rb +0 -144
  86. data/lib/henshin/plugin.rb +0 -100
  87. data/lib/henshin/plugins/highlight.rb +0 -24
  88. data/lib/henshin/plugins/liquid.rb +0 -61
  89. data/lib/henshin/plugins/maruku.rb +0 -18
  90. data/lib/henshin/plugins/sass.rb +0 -24
  91. data/lib/henshin/plugins/textile.rb +0 -18
  92. data/lib/henshin/post.rb +0 -156
  93. data/lib/henshin/static.rb +0 -33
  94. data/test/helper.rb +0 -44
  95. data/test/site/css/_reset.sass +0 -34
  96. data/test/site/css/print.css +0 -12
  97. data/test/site/css/screen.sass +0 -70
  98. data/test/site/includes/head.html +0 -1
  99. data/test/site/index.html +0 -23
  100. data/test/site/layouts/archive_date.html +0 -20
  101. data/test/site/layouts/archive_month.html +0 -24
  102. data/test/site/layouts/archive_year.html +0 -26
  103. data/test/site/layouts/category_index.html +0 -27
  104. data/test/site/layouts/category_page.html +0 -20
  105. data/test/site/layouts/main.html +0 -13
  106. data/test/site/layouts/post.html +0 -36
  107. data/test/site/layouts/tag_index.html +0 -27
  108. data/test/site/layouts/tag_page.html +0 -20
  109. data/test/site/options.yaml +0 -17
  110. data/test/site/plugins/test.rb +0 -3
  111. data/test/site/posts/Testing-Stuff.markdown +0 -14
  112. data/test/site/posts/Textile-Test.textile +0 -7
  113. data/test/site/posts/cat/test.markdown +0 -6
  114. data/test/site/posts/lorem-ipsum.markdown +0 -7
  115. data/test/site/posts/same-date.markdown +0 -7
  116. data/test/site/static.html +0 -19
  117. data/test/suite.rb +0 -4
  118. data/test/test_gen.rb +0 -98
  119. data/test/test_options.rb +0 -73
  120. data/test/test_post.rb +0 -67
  121. data/test/test_site.rb +0 -197
  122. data/test/test_static.rb +0 -13
@@ -1,50 +1,175 @@
1
- $:.unshift File.dirname(__FILE__)
1
+ $: << File.dirname(__FILE__)
2
2
 
3
- require 'time'
3
+ require 'attr_plus/class'
4
+ require 'fileutils'
5
+ require 'hashie/mash'
6
+ require 'highline'
7
+ require 'tilt'
4
8
  require 'yaml'
5
- require 'pp'
6
- require 'pathname'
7
9
 
8
- require 'titlecase'
9
- require 'parsey'
10
+ require 'slim'
11
+ require 'redcarpet'
12
+ Object.send(:remove_const, :RedcarpetCompat) if defined?(::RedcarpetCompat)
10
13
 
11
- require 'henshin/site'
12
- require 'henshin/plugin'
14
+ require 'henshin/error'
15
+ require 'henshin/safety'
16
+
17
+ require 'henshin/compressor'
18
+ require 'henshin/compressors/css'
19
+ require 'henshin/compressors/js'
20
+
21
+ require 'henshin/core_ext'
22
+ require 'henshin/path'
23
+ require 'henshin/scope'
24
+
25
+ require 'henshin/writer'
26
+ require 'henshin/publisher'
27
+ require 'henshin/publishers/sftp'
13
28
 
14
- require 'henshin/gen'
15
- require 'henshin/post'
16
- require 'henshin/static'
29
+ require 'henshin/file'
30
+ require 'henshin/files/attributes'
31
+ require 'henshin/files/abstract'
32
+ require 'henshin/files/physical'
33
+ require 'henshin/files/templatable'
17
34
 
18
- require 'henshin/labels'
19
- require 'henshin/archive'
20
- require 'henshin/ext'
35
+ require 'henshin/files/empty_template'
36
+ require 'henshin/files/post'
37
+ require 'henshin/files/template'
38
+ require 'henshin/files/tilt'
39
+ require 'henshin/files/tilt_template'
21
40
 
41
+ require 'henshin/package'
42
+ require 'henshin/packages/script'
43
+ require 'henshin/packages/style'
44
+
45
+ require 'henshin/reader'
46
+ require 'henshin/site'
47
+ require 'henshin/ui'
48
+ require 'henshin/version'
22
49
 
23
50
  module Henshin
24
-
25
- # Default options for configuration
26
- Defaults = {'title' => 'A site',
27
- 'file_name' => '<{category}/>{title-with-dashes}.{extension}',
28
- 'permalink' => '/{year}/{month}/{date}/{title}.html',
29
- 'plugins' => ['maruku', 'liquid'],
30
- 'root' => './',
31
- 'target' => '_site',
32
- 'exclude' => []}.freeze
33
-
34
- # Partial regexs for use in parsing file names
35
- Partials = {'title' => '([a-zA-Z0-9_ -]+)',
36
- 'title-with-dashes' => '([a-zA-Z0-9-]+)',
37
- 'date' => '(\d{4}-\d{2}-\d{2})',
38
- 'date-time' => '(\d{4}-\d{2}-\d{2} at \d{2}:\d{2})',
39
- 'xml-date-time' => '(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?((\+|-)\d{2}:\d{2})?)',
40
- 'category' => '([a-zA-Z0-9_ -]+)',
41
- 'extension' => "([a-zA-Z0-9_-]+)"}.freeze
42
-
43
- # Reads the current version from VERSION
44
- #
45
- # @return [String] current version
46
- def self.version
47
- File.read( File.join(File.dirname(__FILE__), '..', 'VERSION') )
51
+ extend self
52
+
53
+ # Loads the yaml text given returning a Hash with symbol keys.
54
+ #
55
+ # @param text [String]
56
+ # @return [Hash{Symbol=>Object}]
57
+ def load_yaml(text)
58
+ (YAML.load(text) || {}).symbolise
59
+ end
60
+
61
+ SETTINGS = {
62
+ colour: true,
63
+ dry_run: false,
64
+ klass: Site,
65
+ local: false,
66
+ profile: false,
67
+ quiet: false
68
+ }
69
+
70
+ # Sets a global Henshin setting.
71
+ #
72
+ # @param key [Symbol]
73
+ # @example
74
+ #
75
+ # Henshin.set :colour
76
+ # Henshin.colour? #=> true
77
+ #
78
+ def set(key)
79
+ SETTINGS[key] = true
80
+ end
81
+
82
+ # Unsets a global Henshin setting.
83
+ #
84
+ # @param key [Symbol]
85
+ # @example
86
+ #
87
+ # Henshin.unset :colour
88
+ # Henshin.colour? #=> false
89
+ #
90
+ def unset(key)
91
+ SETTINGS[key] = false
92
+ end
93
+
94
+ # @return Whether to display colourful output.
95
+ def colour?
96
+ SETTINGS[:colour]
97
+ end
98
+
99
+ # @return Whether to write files to disk.
100
+ def dry_run?
101
+ SETTINGS[:dry_run]
102
+ end
103
+
104
+ # @return Whether to use local referencing urls.
105
+ # @note This is very much work in progress and does break!
106
+ def local?
107
+ SETTINGS[:local]
108
+ end
109
+
110
+ # @return Whether to calculate profiling data.
111
+ def profile?
112
+ SETTINGS[:profile]
113
+ end
114
+
115
+ # @return Whether to only show vital output.
116
+ def quiet?
117
+ SETTINGS[:quiet]
118
+ end
119
+
120
+ # Set the Site class that is used to build the site.
121
+ #
122
+ # @param klass [Class]
123
+ # @example
124
+ #
125
+ # class MyCoolSite < Henshin::Site
126
+ # # ...
127
+ # end
128
+ #
129
+ # Henshin.use MyCoolSite
130
+ #
131
+ def use(klass)
132
+ SETTINGS[:klass] = klass
133
+ end
134
+
135
+ # Evaluates the +init.rb+ file if it exists.
136
+ #
137
+ # @param root [Pathname] Root of the site.
138
+ def eval_init(root)
139
+ if (root + 'init.rb').exist?
140
+ eval (root + 'init.rb').read
141
+ end
142
+ end
143
+
144
+ # Builds the site in +root+.
145
+ #
146
+ # @param root [Pathname]
147
+ def build(root, opts={})
148
+ time = Time.now if profile?
149
+
150
+ site = SETTINGS[:klass].new(root)
151
+ writer = Writer.new(site.dest)
152
+ site.write(writer)
153
+
154
+ puts "#{Time.now - time}s to build site." if profile?
155
+ end
156
+
157
+ # Publishes the site in +root+.
158
+ #
159
+ # @param root [Pathname]
160
+ def publish(root, opts={})
161
+ time = Time.now if profile?
162
+
163
+ site = SETTINGS[:klass].new(root)
164
+
165
+ unless site.config.has_key?(:publish)
166
+ UI.fail "No publish configuration in config.yml."
167
+ end
168
+
169
+ writer = Publisher::Sftp.create(site.config[:publish])
170
+ site.write(writer)
171
+
172
+ puts "#{Time.now - time}s to publish site." if profile?
48
173
  end
49
174
 
50
175
  end
@@ -0,0 +1,31 @@
1
+ module Henshin
2
+
3
+ # @abstract You need to implement {#compress}.
4
+ #
5
+ # Compresses a set of files into one big ol' ugly file.
6
+ #
7
+ # @example
8
+ #
9
+ # compressed = Compressor.new(files)
10
+ # compressed.compress #=> "..."
11
+ #
12
+ class Compressor
13
+
14
+ # @param files [Array<File>]
15
+ def initialize(files=[])
16
+ @files = files
17
+ end
18
+
19
+ # @return [String] Simply joins the contents of all +files+ into a single
20
+ # string separated by newlines.
21
+ def join
22
+ @files.map {|file| file.text }.join("\n")
23
+ end
24
+
25
+ # @return [String] The compressed text for the +files+.
26
+ def compress
27
+ join
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,20 @@
1
+ require 'yui/compressor'
2
+
3
+ module Henshin
4
+
5
+ class Compressor
6
+ # Compresses css files using the yui-compressor.
7
+ class Css < Compressor
8
+
9
+ def initialize(*args)
10
+ super
11
+ @compressor = YUI::CssCompressor.new
12
+ end
13
+
14
+ # @return [String] The compressed, joined text from the given css files.
15
+ def compress
16
+ @compressor.compress(join)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'yui/compressor'
2
+
3
+ module Henshin
4
+
5
+ class Compressor
6
+ # Compresses js files using the yui-compressor.
7
+ class Js < Compressor
8
+
9
+ def initialize(*args)
10
+ super
11
+ @compressor = YUI::JavaScriptCompressor.new
12
+ end
13
+
14
+ # @return [String] The compressed, joined text from the given js files.
15
+ def compress
16
+ @compressor.compress(join)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,69 @@
1
+ class Hash
2
+
3
+ # Changes all keys to symbols, works recursively.
4
+ #
5
+ # @example
6
+ #
7
+ # {"a" => 1, "b" => 2}.symbolise
8
+ # #=> {:a => 1, :b => 2}
9
+ #
10
+ # @return [Hash]
11
+ def symbolise
12
+ each_with_object({}) do |(k, v), h|
13
+ h[k.to_sym] = (v.respond_to?(:symbolise) ? v.symbolise : v)
14
+ end
15
+ end
16
+
17
+ # Merges the hash given with this hash but deeply.
18
+ #
19
+ # @example
20
+ #
21
+ # a = {:person => {:name => "John", :age => 30}}
22
+ # b = {:person => {:age => 31, :job => "Dummy"}}
23
+ #
24
+ # a.merge(b)
25
+ # #=> {:person => {:age => 31, :job => "Dummy"}}
26
+ #
27
+ # a.deep_merge(b)
28
+ # #=> {:person => {:name => "John", :age => 31, :job => "Dummy"}}
29
+ #
30
+ # @param other [Hash]
31
+ # @see http://timelessrepo.com/when-in-doubt
32
+ def deep_merge(other)
33
+ m = proc {|_,o,n| o.respond_to?(:merge) ? o.merge(n, &m) : n }
34
+ merge(other, &m)
35
+ end
36
+
37
+ def to_hash
38
+ self
39
+ end
40
+ end
41
+
42
+ class Pathname
43
+ def same_type?(other)
44
+ if relative?
45
+ other.relative?
46
+ else
47
+ other.absolute?
48
+ end
49
+ end
50
+ end
51
+
52
+ class String
53
+
54
+ # Converts the string to a format suitable for use as a url.
55
+ #
56
+ # @example
57
+ #
58
+ # "Hey, Wait! I've got this string.".slugify
59
+ # #=> "hey-wait-ive-got-this-string"
60
+ #
61
+ # @return [String]
62
+ def slugify
63
+ gsub(/[']+/, '').
64
+ gsub(/\W+/, ' ').
65
+ strip.
66
+ downcase.
67
+ gsub(' ', '-')
68
+ end
69
+ end
@@ -0,0 +1,28 @@
1
+ module Henshin
2
+
3
+ # As most errors will be shown to a user of Henshin, they don't care about
4
+ # large backtraces. Using {.prettify} limits the backtrace and displays the
5
+ # message in an easier to read way.
6
+ class Error
7
+
8
+ LIMIT = 3
9
+
10
+ # @param msg [String] Message describing the error
11
+ # @param err [Exception] The exception raised
12
+ # @example
13
+ #
14
+ # begin
15
+ # # an exception is raised
16
+ # rescue => err
17
+ # Error.prettify 'Something went wrong', err
18
+ # end
19
+ #
20
+ def self.prettify(msg, err)
21
+ puts "\n#{msg}".red.bold
22
+ puts " #{err.message}"
23
+ puts err.backtrace.take(LIMIT).map {|l| " #{l}" }.join("\n")
24
+ exit 1
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,67 @@
1
+ module Henshin
2
+
3
+ # A Factory class which abstracts away the process of creating File (subclass)
4
+ # objects.
5
+ #
6
+ # File types can be registered using {.register} and then the
7
+ # appropriate class will be used when {.create} is used. You can also add
8
+ # functionality that is meant for broader use than a specific file type using
9
+ # modules, these can be registered using the {.apply} method and when
10
+ # {.create} is called the File will be extended with the correct modules.
11
+ #
12
+ # @example
13
+ #
14
+ # class MyFileType < Henshin::File::Physical
15
+ # # override some methods...
16
+ # end
17
+ #
18
+ # Henshin::File.register /\.abc\Z/, MyFileType
19
+ #
20
+ # module SomeFileRole
21
+ # # define some methods...
22
+ # end
23
+ #
24
+ # Henshin::File.apply %r{(^|/)special_files/}, SomeFileRole
25
+ #
26
+ # file = Henshin::File.create(site, Pathname.new('special_files/test.abc')
27
+ #
28
+ # file.class #=> MyFileType
29
+ # file.singleton_class.include?(SomeFileRole) #=> true
30
+ #
31
+ class File
32
+
33
+ @types = []
34
+ @applies = []
35
+
36
+ # Registers a new file type which can then be used by {.create}.
37
+ #
38
+ # @param match [#match] Regexp path must match to be +klass+ type
39
+ # @param klass [Class] Subclass of File
40
+ def self.register(match, klass)
41
+ @types.unshift [match, klass]
42
+ end
43
+
44
+ # Registers a module to be applied to files which have a path matching
45
+ # +match+.
46
+ #
47
+ # @param match [#match] Regexp path must match to extend +mod+
48
+ # @param mod [Module]
49
+ def self.apply(match, mod)
50
+ @applies.unshift [match, mod]
51
+ end
52
+
53
+ # Creates a new File, or if possible a subclass of File, depending on the
54
+ # extension of the path given.
55
+ #
56
+ # @param site [Site]
57
+ # @param path [Pathname]
58
+ def self.create(site, path)
59
+ klass = (@types.find {|k,v| k =~ path.to_s } || [nil, File::Physical]).last
60
+ obj = klass.new(site, path)
61
+
62
+ @applies.find_all {|k,v| k =~ path.to_s }.each {|_,v| obj.extend(v) }
63
+ obj
64
+ end
65
+ end
66
+
67
+ end