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
@@ -0,0 +1,102 @@
1
+ module Henshin
2
+
3
+ class File
4
+
5
+ # @abstract You will want to implement {#raw_text}, {#path} and maybe
6
+ # {#text}.
7
+ #
8
+ # This class implements all the functionality that is required to build or
9
+ # serve a file. {Abstract} instances do not relate to a file in the file
10
+ # system, use {Physical} in this case.
11
+ class Abstract
12
+
13
+ include Henshin::Helpers, Safety, Comparable
14
+ extend Attributes
15
+
16
+ attr_reader :site
17
+
18
+ def initialize(site)
19
+ @site = site.safe
20
+ end
21
+
22
+ # Simple version of text. This is raw in that it has not been run through
23
+ # a template. It may still have been passed through an ordinary rendering
24
+ # engine (for instance a Markdown engine).
25
+ #
26
+ # @return [String]
27
+ # @see #text
28
+ def raw_text
29
+ ""
30
+ end
31
+
32
+ # @return [String] Text to write to the file.
33
+ def text
34
+ raw_text
35
+ end
36
+
37
+ # @return [Path] Path to the file.
38
+ def path
39
+
40
+ end
41
+
42
+ # @return [String] The absolute url to the file.
43
+ def permalink
44
+ path.permalink
45
+ end
46
+
47
+ # @return [Pathname] A pretty url to the file, the permalink with
48
+ # 'index.html' stripped from the end generally.
49
+ def url
50
+ path.url
51
+ end
52
+
53
+ # @return [String] Extension for the file to be written.
54
+ def extension
55
+ path.extension
56
+ end
57
+
58
+ # Allow template to be set, needed for Template to work properly.
59
+ def template
60
+ @template || nil
61
+ end
62
+ attr_writer :template
63
+
64
+ # Writes the file.
65
+ #
66
+ # @param writer [#write] Object which is able to write text to a path.
67
+ def write(writer)
68
+ return unless writeable?
69
+ start = Time.now if Henshin.profile?
70
+ writer.write Pathname.new(permalink.sub(/^\//, '')), text
71
+ if Henshin.profile?
72
+ UI.wrote permalink, (Time.now - start)
73
+ else
74
+ UI.wrote permalink
75
+ end
76
+ rescue => e
77
+ Error.prettify("Error writing #{inspect}", e)
78
+ end
79
+
80
+ unsafe :write
81
+
82
+ # Compares the files based on their permalinks.
83
+ #
84
+ # @param other [File]
85
+ def <=>(other)
86
+ permalink <=> other.permalink
87
+ end
88
+
89
+ def inspect
90
+ "#<#{self.class} #{permalink}>"
91
+ end
92
+
93
+ private
94
+
95
+ # @return Whether this file should be written.
96
+ def writeable?
97
+ true
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,31 @@
1
+ require 'set'
2
+
3
+ module Henshin
4
+
5
+ class File
6
+
7
+ # Extend any module (classes will generally be inheriting from {Abstract} so
8
+ # will pick this up) with this module to gain the ability to set required
9
+ # yaml keys and default templates.
10
+ module Attributes
11
+
12
+ def requires(*keys)
13
+ @required ||= Set.new
14
+ @required += keys
15
+ end
16
+
17
+ def required
18
+ @required || Set.new
19
+ end
20
+
21
+ def template(name)
22
+ @template = name
23
+ end
24
+
25
+ def default_template
26
+ @template
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ module Henshin
2
+
3
+ class File
4
+ class EmptyTemplate < Abstract
5
+
6
+ def initialize
7
+ # ...
8
+ end
9
+
10
+ def raw_text
11
+ ""
12
+ end
13
+
14
+ def name
15
+ "none"
16
+ end
17
+
18
+ def render(*args)
19
+ ""
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,117 @@
1
+ module Henshin
2
+
3
+ class File
4
+
5
+ # A file physically located on the file system. A subclass of file will
6
+ # have a @path variable with it's location.
7
+ class Physical < Abstract
8
+
9
+ # Regular expression to match the text of the file, contains two match
10
+ # groups; the first matches the yaml part, the second any text.
11
+ YAML_REGEX = /\A---\n^(.*?)\n^---\n?(.*)\z/m
12
+
13
+ # @param site [Site] Site the file is in.
14
+ # @param path [Pathname] Path to the file.
15
+ def initialize(site, path)
16
+ @site = site.safe
17
+ @path = path
18
+ end
19
+
20
+ # Allow yaml attributes to be accessed in templates.
21
+ def method_missing(sym, *args, &block)
22
+ if yaml.key?(sym)
23
+ yaml[sym]
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ # Allow template to be set, needed for Template to work properly.
30
+ def template
31
+ @template || yaml[:template]
32
+ end
33
+
34
+ def yield
35
+ text
36
+ end
37
+
38
+ # @return [String] Text of the file.
39
+ def raw_text
40
+ read[1]
41
+ end
42
+
43
+ # @return [Path] If a permalink has been set in the yaml frontmatter uses
44
+ # that, otherwise uses the path to the file.
45
+ def path
46
+ if yaml.key?(:permalink)
47
+ if yaml[:permalink].start_with?('/')
48
+ Path @site.root, yaml[:permalink][1..-1]
49
+ else
50
+ Path @site.root, yaml[:permalink]
51
+ end
52
+
53
+ else
54
+ rel = @path
55
+
56
+ if @path.same_type?(@site.source)
57
+ rel = @path.relative_path_from(@site.source)
58
+ end
59
+
60
+ if @path.basename.to_s.count('.') == 1
61
+ Path @site.root, rel
62
+ else
63
+ ext = @path.extname
64
+ file = rel.to_s[0..-ext.size-1]
65
+ Path @site.root, file
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ # Reads the file, splitting it in to two parts; the yaml and the text.
73
+ #
74
+ # @example
75
+ #
76
+ # file = File.new(site, "hello-world.md")
77
+ # file.read
78
+ # #=> ["title: Hello World\ndate: 2012-02-03",
79
+ # # "Hello, world!"]
80
+ #
81
+ # @return [Array<String>] An array of two parts. The first is the yaml part
82
+ # of the file, the second is the text part.
83
+ def read
84
+ contents = @path.read || ""
85
+ if match = contents.match(YAML_REGEX)
86
+ match.to_a[1..2]
87
+ else
88
+ ['', contents]
89
+ end
90
+ rescue
91
+ ['', contents]
92
+ end
93
+
94
+ # @return [Hash{Symbol=>Object}] Returns the data loaded from the file's
95
+ # yaml frontmatter.
96
+ def yaml
97
+ loaded = Henshin.load_yaml read[0]
98
+
99
+ singleton_class.ancestors.find_all {|klass|
100
+ klass.singleton_class.include?(Attributes)
101
+
102
+ }.map {|klass|
103
+ klass.required.to_a
104
+
105
+ }.flatten.reject {|key|
106
+ respond_to?(key) || loaded.key?(key)
107
+
108
+ }.each {|key|
109
+ UI.fail(inspect + " requires #{key}.")
110
+ }
111
+
112
+ loaded
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,64 @@
1
+ module Henshin
2
+
3
+ class File
4
+ # A post, stored in the /posts folder.
5
+ module Post
6
+ include Templatable
7
+ extend Attributes
8
+
9
+ requires :title, :date
10
+ template 'post'
11
+
12
+ def rss_date
13
+ date.rfc2822
14
+ end
15
+
16
+ def next=(post)
17
+ @next = post
18
+ end
19
+
20
+ def prev=(post)
21
+ @prev = post
22
+ end
23
+
24
+ def next_post
25
+ @next
26
+ end
27
+
28
+ def prev_post
29
+ @prev
30
+ end
31
+
32
+ def published?
33
+ true
34
+ end
35
+
36
+ def path
37
+ style = @site.config[:permalink]
38
+
39
+ data = {
40
+ year: date.strftime('%Y'),
41
+ month: date.strftime('%m'),
42
+ day: date.strftime('%d'),
43
+ title: title.slugify
44
+ }
45
+
46
+ url = data.inject(style) {|res, (tok, val)|
47
+ res.gsub /:#{Regexp.escape(tok)}/, val
48
+ }
49
+
50
+ Path @site.root, url[1..-1]
51
+ end
52
+
53
+ # Compares posts on date, then on permalink if dates are the same.
54
+ def <=>(other)
55
+ c = other.respond_to?(:date) ? other.date <=> date : 0
56
+ c.zero? ? super : c
57
+ end
58
+
59
+ end
60
+
61
+ apply %r{(^|/)posts/}, Post
62
+
63
+ end
64
+ end
@@ -0,0 +1,29 @@
1
+ module Henshin
2
+
3
+ class File
4
+
5
+ module Templatable
6
+
7
+ # Overrides #text in the included class so that the result from it is passed
8
+ # into a template. Uses the template set with .template, or if not found, the
9
+ # default template.
10
+ #
11
+ # @return [String]
12
+ def text
13
+ res = raw_text
14
+ data = safe
15
+
16
+ return res if data.template == "none"
17
+
18
+ data.singleton_class.send(:define_method, :text) { res }
19
+
20
+ default = nil
21
+ singleton_class.ancestors.find {|klass|
22
+ default = klass.default_template if klass.respond_to?(:default_template)
23
+ }
24
+
25
+ @site.template(default, Henshin::DEFAULT_TEMPLATE).render(data)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ module Henshin
2
+
3
+ class File
4
+
5
+ # Used to extend template files. These will generally be written in a language
6
+ # such as slim, as they have the ability to include data.
7
+ #
8
+ # @example
9
+ #
10
+ # t = SlimFile.new(site, path)
11
+ # t.extend Template
12
+ #
13
+ # t.template other_file
14
+ # #=> "..."
15
+ #
16
+ module Template
17
+
18
+ # Name used to refer to the template.
19
+ # @return [String]
20
+ def name
21
+ @path.basename.to_s.split('.').first
22
+ end
23
+
24
+ # Sets the data and then uses the superclasses #text method to render the
25
+ # template.
26
+ #
27
+ # @param data [Hash]
28
+ def template(data)
29
+ data.template = 'none'
30
+ @data = data.safe
31
+ text
32
+ end
33
+
34
+ alias_method :render, :template
35
+
36
+ # @return [Hash] The data set by #template.
37
+ def data
38
+ @data
39
+ end
40
+ end
41
+
42
+ apply %r{(^|/)templates/}, Template
43
+
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ module Henshin
2
+
3
+ class File
4
+
5
+ # Renders a file using an engine from Tilt. This particular class deals
6
+ # with file types which would never use templates.
7
+ #
8
+ # @see http://github.com/rtomayko/tilt
9
+ class Tilt < Physical
10
+
11
+ EXTENSIONS = %w(sass scss less coffee).map(&:to_sym)
12
+
13
+ # Renders the files text using the appropriate Tilt engine. Also gets the
14
+ # configuration for the engine, if any, from {Site#config}.
15
+ #
16
+ # @return [String] The rendered file contents
17
+ def raw_text
18
+ ext = @path.extname[1..-1].to_sym
19
+ config = (@site.config[ext] || {}).to_hash.symbolise
20
+ ::Tilt[ext].new(nil, nil, config) { super }.render
21
+ end
22
+ end
23
+
24
+ register /\.(#{Tilt::EXTENSIONS.join('|')})\Z/, Tilt
25
+
26
+
27
+ # Renders a file using an engine from Tilt. This class, unlike {Tilt} deals
28
+ # with file types which generally would use a template. To prevent a
29
+ # template being used, you need to add to the YAML frontmatter:
30
+ #
31
+ # template: none
32
+ #
33
+ # And to use a different template than 'default' simply use the template's
34
+ # name instead of "none". For example, to use the template "page" I would
35
+ # add:
36
+ #
37
+ # template: page
38
+ #
39
+ class TiltWithTemplate < Tilt
40
+ include Templatable
41
+
42
+ EXTENSIONS = %w(str markdown mkd md textile rdoc wiki
43
+ creaole mediawiki mw).map(&:to_sym)
44
+
45
+ end
46
+
47
+ register /\.(#{TiltWithTemplate::EXTENSIONS.join('|')})\Z/, TiltWithTemplate
48
+
49
+ end
50
+ end