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,45 @@
1
+ module Henshin
2
+
3
+ class File
4
+
5
+ class TiltTemplate < Physical
6
+
7
+ EXTENSIONS = %w(erb rhtml erubis
8
+ haml
9
+ nokogiri
10
+ builder
11
+ mab
12
+ liquid
13
+ radius
14
+ slim
15
+ yajl).map(&:to_sym)
16
+
17
+ def data
18
+ self.safe
19
+ end
20
+
21
+ def text
22
+ ext = @path.extname[1..-1].to_sym
23
+ scope = data
24
+
25
+ text = ::Tilt[ext].new(nil, nil, (@site.config[ext] || {}).to_hash.symbolise) {
26
+ raw_text
27
+ }.render(scope) { scope.yield }
28
+
29
+ return text if scope.template == 'none'
30
+
31
+ default = nil
32
+ singleton_class.ancestors.find {|klass|
33
+ default = klass.default_template if klass.respond_to?(:default_template)
34
+ }
35
+
36
+ templates = [scope.template, default, Henshin::DEFAULT_TEMPLATE].compact
37
+
38
+ @site.template(*templates).render(data)
39
+ end
40
+ end
41
+
42
+ register /\.(#{TiltTemplate::EXTENSIONS.join('|')})\Z/, TiltTemplate
43
+
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ module Henshin
2
+
3
+ # @abstract Must implement {#path}.
4
+ # Concatenates multiple files into one, compressed file.
5
+ class Package < File::Abstract
6
+
7
+ # @param site [Site]
8
+ # @param compressor [Compressor]
9
+ def initialize(site, compressor)
10
+ @site = site
11
+ @compressor = compressor
12
+ end
13
+
14
+ def enabled?
15
+ true
16
+ end
17
+
18
+ # @return [String]
19
+ def text
20
+ enabled? ? @compressor.compress : @compressor.join
21
+ end
22
+
23
+ def path
24
+ # Path site, '...'
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module Henshin
2
+
3
+ class Package
4
+ # Concatenates script files (java/coffee-script) then minifies them.
5
+ class Script < Package
6
+
7
+ # @param site [Site]
8
+ # @param paths [Array<Pathname>]
9
+ def initialize(site, paths)
10
+ compressor = Compressor::Js.new(paths.map {|p| File.create(site, p) })
11
+ super(site, compressor)
12
+ end
13
+
14
+ def enabled?
15
+ @site.config[:compress][:scripts]
16
+ end
17
+
18
+ def path
19
+ Path @site.root, 'script.js'
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module Henshin
2
+
3
+ class Package
4
+ class Style < Package
5
+
6
+ def initialize(site, paths)
7
+ compressor = Compressor::Css.new(paths.map {|p| File.create(site, p) })
8
+ super(site, compressor)
9
+ end
10
+
11
+ def enabled?
12
+ @site.config[:compress][:styles]
13
+ end
14
+
15
+ def path
16
+ Path @site.root, 'style.css'
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,143 @@
1
+ module Henshin
2
+
3
+ # @example
4
+ #
5
+ # path = Path('/blog', 'tag', 'code', 'index.html')
6
+ # path.extension #=> 'html'
7
+ # path.permalink #=> '/blog/tag/code/index.html'
8
+ # path.url #=> '/blog/tag/code'
9
+ #
10
+ # path == '/blog/tag/code/'
11
+ # #=> true
12
+ #
13
+ class Path
14
+
15
+ # Creates a new Path instance for the site given with the path provided.
16
+ # The path is constructed from the parts passed in.
17
+ #
18
+ # @param path [String, Pathname] The path to the file the Path instance
19
+ # refers to.
20
+ #
21
+ # @example
22
+ #
23
+ # Path.new('/', 'folder', 'another-folder', 'file.txt')
24
+ # #=> #<Henshin::Path /folder/another-folder/file.txt>
25
+ #
26
+ def initialize(*path)
27
+ @path = path.flatten.map {|p| Pathname.new(p) }
28
+ end
29
+
30
+ # @return [String] Extension of the Path
31
+ def extension
32
+ ::File.extname @path.last
33
+ end
34
+
35
+ # Returns a full url path to the location specified.
36
+ #
37
+ # @return [String] Full url to Path
38
+ # @example
39
+ #
40
+ # Path('/', 'blog', '2011', 'hello-world', 'index.html').permalink
41
+ # #=> '/blog/2011/hello-world/index.html'
42
+ #
43
+ def permalink
44
+ url = @path.inject(:+).to_s
45
+
46
+ Henshin.local? ? url[1..-1] : url
47
+ end
48
+
49
+ alias_method :to_s, :permalink
50
+
51
+ # Returns the url without +index.html+ at the end if possible.
52
+ #
53
+ # @return [String] Pretty url to Path
54
+ # @example
55
+ #
56
+ # Path('/', 'blog', '2011', 'hello-world', 'index.html').permalink
57
+ # #=> '/blog/2011/hello-world/'
58
+ #
59
+ def url
60
+ if Henshin.local?
61
+ permalink
62
+ else
63
+ permalink.sub /index\.html$/, ''
64
+ end
65
+ end
66
+
67
+ # Appends a path onto the Path.
68
+ #
69
+ # @param other [String, Pathname]
70
+ # @return [Path]
71
+ # @example
72
+ #
73
+ # path = Path('folder')
74
+ # path << 'another' << 'file.txt'
75
+ # path #=> #<Henshin::Path folder/another/file.txt>
76
+ #
77
+ def << other
78
+ @path << other
79
+ self
80
+ end
81
+
82
+ # Checks (strictly) whether a Path instance is equal to another. This
83
+ # compares them using {#to_s}, so both Paths will need to have the same
84
+ # root _and_ path to be equal.
85
+ #
86
+ # @param other [Path]
87
+ # @example
88
+ #
89
+ # a = Path 'a', 'test.txt'
90
+ # b = Path 'b', 'test.txt'
91
+ #
92
+ # a == b
93
+ # #=> false
94
+ #
95
+ # a == Path('a', 'test.txt')
96
+ # #=> true
97
+ #
98
+ def == other
99
+ return false unless other.is_a?(Path)
100
+ self.to_s == other.to_s
101
+ end
102
+
103
+ # Checks (loosely) whether this Path instance is equal to the given
104
+ # argument. The argument can be another Path instance or a String, in which
105
+ # case the String is checked for equality against the {#url} and
106
+ # {#permalink}.
107
+ #
108
+ # @param other [Path, String]
109
+ # @example
110
+ #
111
+ # index = Path '/blog', 'index.html'
112
+ # index.permalink #=> '/blog/index.html'
113
+ # index.url #=> '/blog/'
114
+ #
115
+ # index === '/blog/index.html'
116
+ # index === '/blog/'
117
+ # index === '/blog'
118
+ #
119
+ def === other
120
+ case other
121
+ when String
122
+ url == other || url[0..-2] == other || permalink == other
123
+ when Path
124
+ self == other
125
+ else
126
+ false
127
+ end
128
+ end
129
+
130
+ def inspect
131
+ "#<#{self.class} #{to_s}>"
132
+ end
133
+
134
+ end
135
+ end
136
+
137
+ module Kernel
138
+
139
+ # @see Henshin::Path#initialize
140
+ def Path(*args)
141
+ Henshin::Path.new(*args)
142
+ end
143
+ end
@@ -0,0 +1,42 @@
1
+ module Henshin
2
+
3
+ # Publishes a site to your server.
4
+ class Publisher < Writer
5
+
6
+ # @return [#write]
7
+ def self.create(opts={})
8
+
9
+ end
10
+
11
+ # Checks that +hash+ contains all keys in +list+, raises error if not.
12
+ #
13
+ # @param hash [Hash]
14
+ # @param list [Array]
15
+ def self.requires_keys(hash, list)
16
+ list.each do |key|
17
+ unless hash.key?(key)
18
+ UI.fail "Publish hash must contain :#{name} key."
19
+ end
20
+ end
21
+ true
22
+ end
23
+
24
+ def self.get_required_opt(hsh, name)
25
+ hsh[name] || UI.fail("Must give :#{name} option to publish.")
26
+ end
27
+
28
+ def self.get_password(hsh, name)
29
+ if hsh.key?(name)
30
+ val = hsh[name]
31
+ if val =~ /^\$(\w+)\s+(.*)$/
32
+ `#{$1} -c '#{$2}'`.chomp
33
+ else
34
+ val
35
+ end
36
+ else
37
+ HighLine.new.ask("Enter password: ") {|q| q.echo = false }
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,79 @@
1
+ require 'net/sftp'
2
+
3
+ module Henshin
4
+
5
+ class Publisher
6
+
7
+ # Publishes the site using sftp. Writes files directly to the server. Requires
8
+ # the following configuration in config.yml:
9
+ #
10
+ # publish:
11
+ # host: sftp.example.com
12
+ # base: /path/to/public
13
+ # user: your_username
14
+ # pass: your_password
15
+ #
16
+ # It's a bad idea writing down your password in plain text so you can set
17
+ # pass to be a shell command which returns your password, for example:
18
+ #
19
+ # publish:
20
+ # pass: $sh get-sftp-password
21
+ #
22
+ # would run +sh -c 'get-sftp-password'+.
23
+ #
24
+ class Sftp < Publisher
25
+
26
+ def self.create(opts={})
27
+ opts[:pass] = get_password(opts, :pass)
28
+ requires_keys(opts, [:host, :base, :user, :pass])
29
+ opts[:base] = Pathname.new(opts[:base])
30
+
31
+ sftp = nil
32
+ unless Henshin.dry_run?
33
+ sftp = Net::SFTP.start(opts[:host], opts[:user], password: opts[:pass])
34
+ end
35
+
36
+ new(sftp, opts[:base])
37
+ end
38
+
39
+ # @param sftp [Net::SFTP::Session] Connected session to use for transfers
40
+ # @param root [Pathname] Directory to write Site into
41
+ def initialize(sftp, root)
42
+ @sftp = sftp
43
+ @root = root
44
+ end
45
+
46
+ private
47
+
48
+ # @param path [Pathname] Path to check
49
+ # @return Whether +path+ exists
50
+ def exist?(path)
51
+ @sftp.lstat!(path.to_s)
52
+ true
53
+ rescue Net::SFTP::StatusException
54
+ false
55
+ end
56
+
57
+ # Creates a directory at +dir+.
58
+ #
59
+ # @param dir [Pathname] Path of directory to create
60
+ def write_dir(dir)
61
+ dir.descend do |sub|
62
+ @sftp.mkdir!(sub) unless exist?(sub)
63
+ end
64
+ end
65
+
66
+ # Writes the file at +path+ with the +contents+ given.
67
+ #
68
+ # @param path [Pathname] Path to file to be written
69
+ # @param contents [String] Text to write to the file
70
+ def write_file(path, contents)
71
+ file = @sftp.file
72
+ file.open(path.to_s, 'w') do |file|
73
+ file.puts contents.force_encoding('binary')
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,68 @@
1
+ module Henshin
2
+
3
+ class Reader
4
+
5
+ RESERVED_DIRS = %w(assets drafts posts templates build)
6
+
7
+ # @param root [Pathname] Path to read under.
8
+ def initialize(root)
9
+ @root = root
10
+ @ignore = ['config.yml', 'init.rb']
11
+ end
12
+
13
+ # Adds the list of files given to the list to be ignored.
14
+ #
15
+ # @param files [Array<String>]
16
+ def ignore(*files)
17
+ @ignore += files
18
+ end
19
+
20
+ # @param path [String]
21
+ # @return Whether the path given is to be ignored. Returns true if the path
22
+ # is to be ignored, see #ignore; or if the path, or any directory above,
23
+ # begins with an underscore.
24
+ def ignore?(path)
25
+ path = Pathname.new(path).relative_path_from(@root)
26
+
27
+ path.ascend do |part|
28
+ return true if part.basename.to_s[0] == "_"
29
+ return true if @ignore.include?(part.to_s)
30
+ end
31
+ end
32
+
33
+ # Reads the files using the glob pattern given. Ignores files matching
34
+ # {#ignore?}.
35
+ #
36
+ # @example
37
+ #
38
+ # reader.read('assets', '**', '*')
39
+ # #=> [#<Pathname:assets/styles/screen.sass>, ...]
40
+ #
41
+ # @param path [Array<String>]
42
+ # @return [Array<Pathname>]
43
+ def read(*path)
44
+ glob = path.flatten.inject(@root, :+)
45
+ Pathname.glob(glob).reject {|p| p.directory? || ignore?(p) }
46
+ end
47
+
48
+ # Reads all files under the path given.
49
+ #
50
+ # @param path [Array<String>]
51
+ # @return [Array<Pathname>]
52
+ def read_all(*path)
53
+ read(path << '*')
54
+ end
55
+
56
+ alias_method :all, :read_all
57
+
58
+ # Reads all paths under the root this Reader was initialised with, but
59
+ # ignores all those under "reserved directories". These are directories used
60
+ # by henshin for posts, templates, etc.
61
+ #
62
+ # @return [Array<Pathname>]
63
+ def safe_paths
64
+ read_all('**') - read_all("{#{RESERVED_DIRS.join(',')}}", '**')
65
+ end
66
+
67
+ end
68
+ end