alula 0.2.3 → 0.4.0b

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +7 -0
  3. data/Guardfile +9 -0
  4. data/Rakefile +12 -1
  5. data/VERSION +1 -1
  6. data/alula.gemspec +20 -4
  7. data/lib/alula/attachment_processor.rb +77 -0
  8. data/lib/alula/cdn.rb +30 -0
  9. data/lib/alula/cdns/edgecast.rb +16 -0
  10. data/lib/alula/cdns/hosts.rb +14 -0
  11. data/lib/alula/cli.rb +90 -39
  12. data/lib/alula/compressors.rb +22 -10
  13. data/lib/alula/config.rb +141 -0
  14. data/lib/alula/content.rb +113 -0
  15. data/lib/alula/contents/attachment.rb +8 -0
  16. data/lib/alula/contents/item.rb +409 -0
  17. data/lib/alula/contents/metadata.rb +73 -0
  18. data/lib/alula/contents/page.rb +9 -0
  19. data/lib/alula/contents/post.rb +32 -0
  20. data/lib/alula/context.rb +72 -0
  21. data/lib/alula/core_ext.rb +5 -0
  22. data/lib/alula/core_ext/environment.rb +20 -0
  23. data/lib/alula/core_ext/filter.rb +20 -0
  24. data/lib/alula/core_ext/filters/smilies.rb +36 -0
  25. data/lib/alula/core_ext/manifest.rb +30 -0
  26. data/lib/alula/core_ext/tag.rb +100 -0
  27. data/lib/alula/core_ext/tags/attachment.rb +28 -0
  28. data/lib/alula/core_ext/tags/blockquote.rb +21 -0
  29. data/lib/alula/core_ext/tags/image.rb +48 -0
  30. data/lib/alula/core_ext/tags/locale.rb +17 -0
  31. data/lib/alula/core_ext/tags/video.rb +103 -0
  32. data/lib/alula/generator.rb +31 -0
  33. data/lib/alula/generators/feedbuilder.rb +44 -0
  34. data/lib/alula/generators/paginate.rb +88 -0
  35. data/lib/alula/generators/sitemap.rb +26 -0
  36. data/lib/alula/helpers.rb +2 -0
  37. data/lib/alula/helpers/addons.rb +12 -0
  38. data/lib/alula/helpers/assets.rb +56 -0
  39. data/lib/alula/helpers/url_helpers.rb +16 -0
  40. data/lib/alula/plugin.rb +32 -0
  41. data/lib/alula/processor.rb +86 -0
  42. data/lib/alula/processors/dummy.rb +24 -0
  43. data/lib/alula/processors/image.rb +52 -0
  44. data/lib/alula/processors/magick.rb +83 -0
  45. data/lib/alula/processors/video.rb +97 -0
  46. data/lib/alula/processors/zencoder.rb +199 -0
  47. data/lib/alula/progress.rb +95 -0
  48. data/lib/alula/progressbar.rb +66 -0
  49. data/lib/alula/site.rb +331 -262
  50. data/lib/alula/storage.rb +46 -0
  51. data/lib/alula/storages/file_item.rb +43 -0
  52. data/lib/alula/storages/filestorage.rb +96 -0
  53. data/lib/alula/storages/item.rb +12 -0
  54. data/lib/alula/support/commonlogger.rb +30 -0
  55. data/lib/alula/theme.rb +70 -13
  56. data/lib/alula/theme/layout.rb +56 -0
  57. data/lib/alula/theme/view.rb +43 -0
  58. data/lib/alula/version.rb +1 -1
  59. data/locales/en.yml +9 -0
  60. data/locales/fi.yml +10 -0
  61. data/locales/l10n/ar.yml +199 -0
  62. data/locales/l10n/az.yml +199 -0
  63. data/locales/l10n/bg.yml +199 -0
  64. data/locales/l10n/bn-IN.yml +182 -0
  65. data/locales/l10n/bs.yml +242 -0
  66. data/locales/l10n/ca.yml +199 -0
  67. data/locales/l10n/cs.yml +198 -0
  68. data/locales/l10n/csb.yml +210 -0
  69. data/locales/l10n/cy.yml +199 -0
  70. data/locales/l10n/da.yml +199 -0
  71. data/locales/l10n/de-AT.yml +203 -0
  72. data/locales/l10n/de-CH.yml +203 -0
  73. data/locales/l10n/de.yml +203 -0
  74. data/locales/l10n/dsb.yml +215 -0
  75. data/locales/l10n/el.yml +199 -0
  76. data/locales/l10n/en-AU.yml +205 -0
  77. data/locales/l10n/en-CA.yml +214 -0
  78. data/locales/l10n/en-GB.yml +205 -0
  79. data/locales/l10n/en-IN.yml +205 -0
  80. data/locales/l10n/en-US.yml +205 -0
  81. data/locales/l10n/en.yml +205 -0
  82. data/locales/l10n/eo.yml +201 -0
  83. data/locales/l10n/es-AR.yml +205 -0
  84. data/locales/l10n/es-CL.yml +199 -0
  85. data/locales/l10n/es-CO.yml +205 -0
  86. data/locales/l10n/es-MX.yml +205 -0
  87. data/locales/l10n/es-PE.yml +181 -0
  88. data/locales/l10n/es-VE.yml +205 -0
  89. data/locales/l10n/es.yml +199 -0
  90. data/locales/l10n/et.yml +199 -0
  91. data/locales/l10n/eu.yml +199 -0
  92. data/locales/l10n/fa.yml +199 -0
  93. data/locales/l10n/fi.yml +199 -0
  94. data/locales/l10n/fr-CA.yml +207 -0
  95. data/locales/l10n/fr-CH.yml +207 -0
  96. data/locales/l10n/fr.yml +222 -0
  97. data/locales/l10n/fur.yml +199 -0
  98. data/locales/l10n/gl-ES.yml +178 -0
  99. data/locales/l10n/gsw-CH.yml +199 -0
  100. data/locales/l10n/he.yml +201 -0
  101. data/locales/l10n/hi-IN.yml +199 -0
  102. data/locales/l10n/hi.yml +199 -0
  103. data/locales/l10n/hr.yml +237 -0
  104. data/locales/l10n/hsb.yml +214 -0
  105. data/locales/l10n/hu.yml +199 -0
  106. data/locales/l10n/id.yml +200 -0
  107. data/locales/l10n/is.yml +213 -0
  108. data/locales/l10n/it.yml +205 -0
  109. data/locales/l10n/ja.yml +197 -0
  110. data/locales/l10n/kn.yml +199 -0
  111. data/locales/l10n/ko.yml +197 -0
  112. data/locales/l10n/lo.yml +186 -0
  113. data/locales/l10n/lt.yml +182 -0
  114. data/locales/l10n/lv.yml +215 -0
  115. data/locales/l10n/mk.yml +170 -0
  116. data/locales/l10n/mn.yml +205 -0
  117. data/locales/l10n/nb.yml +207 -0
  118. data/locales/l10n/nl.yml +199 -0
  119. data/locales/l10n/nn.yml +160 -0
  120. data/locales/l10n/pl.yml +221 -0
  121. data/locales/l10n/pt-BR.yml +207 -0
  122. data/locales/l10n/pt-PT.yml +207 -0
  123. data/locales/l10n/quotes.yml +24 -0
  124. data/locales/l10n/rm.yml +182 -0
  125. data/locales/l10n/ro.yml +199 -0
  126. data/locales/l10n/ru.yml +257 -0
  127. data/locales/l10n/sk.yml +213 -0
  128. data/locales/l10n/sl.yml +210 -0
  129. data/locales/l10n/sr-Latn.yml +170 -0
  130. data/locales/l10n/sr.yml +170 -0
  131. data/locales/l10n/sv-SE.yml +199 -0
  132. data/locales/l10n/sw.yml +197 -0
  133. data/locales/l10n/th.yml +173 -0
  134. data/locales/l10n/tl.yml +229 -0
  135. data/locales/l10n/tr.yml +199 -0
  136. data/locales/l10n/uk.yml +257 -0
  137. data/locales/l10n/vi.yml +201 -0
  138. data/locales/l10n/wo.yml +205 -0
  139. data/locales/l10n/zh-CN.yml +199 -0
  140. data/locales/l10n/zh-TW.yml +199 -0
  141. data/template/Gemfile.erb +14 -4
  142. data/template/README +16 -0
  143. data/template/config.yml.erb +42 -38
  144. data/test/fixtures/config_001_simple.yml +2 -0
  145. data/test/fixtures/config_002_l10n.yml +5 -0
  146. data/test/fixtures/pages/invalid-page.markdown +1 -0
  147. data/test/fixtures/pages/multilingual-page.markdown +20 -0
  148. data/test/fixtures/pages/section/subpage.markdown +5 -0
  149. data/test/fixtures/pages/simple-page.markdown +7 -0
  150. data/test/fixtures/posts/2012-07-02-invalid-post.markdown +1 -0
  151. data/test/fixtures/posts/2012-07-02-simple.markdown +7 -0
  152. data/test/fixtures/posts/2012-07-03-full-metadata.markdown +8 -0
  153. data/test/fixtures/posts/2012-07-03-multilingual-full-metadata.markdown +20 -0
  154. data/test/fixtures/theme/test/layouts/default.html.erb +1 -0
  155. data/test/fixtures/theme/test/views/page.html.erb +1 -0
  156. data/test/fixtures/theme/test/views/post.html.erb +1 -0
  157. data/test/minitest_helper.rb +14 -0
  158. data/test/test_config.rb +33 -0
  159. data/test/test_content.rb +30 -0
  160. data/test/test_metadata.rb +83 -0
  161. data/test/test_page.rb +81 -0
  162. data/test/test_post.rb +123 -0
  163. data/test/test_storage.rb +23 -0
  164. data/test/test_storage_file.rb +32 -0
  165. data/test/test_theme.rb +45 -0
  166. data/vendor/assets/images/favicon.png +0 -0
  167. data/vendor/assets/images/grey.gif +0 -0
  168. data/vendor/assets/javascripts/jquery.alula.js.coffee +16 -0
  169. data/vendor/{javascripts → assets/javascripts}/jquery.js +0 -0
  170. data/vendor/assets/javascripts/jquery.lazyload.js +210 -0
  171. data/vendor/assets/javascripts/lazyload.js.coffee +15 -0
  172. data/vendor/layouts/feed.xml.builder +19 -0
  173. data/vendor/layouts/sitemap.xml.builder +10 -0
  174. data/vendor/views/feed_post.html.haml +1 -0
  175. metadata +529 -50
  176. data/lib/alula.rb +0 -5
  177. data/lib/alula/assethelper.rb +0 -75
  178. data/lib/alula/plugins.rb +0 -23
  179. data/lib/alula/plugins/assets.rb +0 -82
  180. data/lib/alula/plugins/pagination.rb +0 -121
  181. data/lib/alula/rake_tasks.rb +0 -42
  182. data/lib/alula/tasks.rb +0 -2
@@ -0,0 +1,95 @@
1
+ require 'alula/progressbar'
2
+
3
+ module Alula
4
+ class Progress
5
+ def initialize(options)
6
+ @pbars = {}
7
+ @interval = 1.0
8
+ @options = options
9
+ @display = false
10
+
11
+ @@lock = Mutex.new
12
+ end
13
+
14
+ def create(identifier, opts)
15
+ if @pbars[identifier]
16
+ @pbars[identifier].finish
17
+ end
18
+
19
+ @@lock.synchronize do
20
+ @pbars[identifier] = ProgressBar.new(opts[:title], opts[:total] == 0 ? 0.1 : opts[:total])
21
+ if @options[:debug]
22
+ @pbars[identifier].settings.force_mode = :notty
23
+ end
24
+ end
25
+ end
26
+
27
+ def step(identifier)
28
+ if @pbars[identifier]
29
+ @pbars[identifier].step
30
+ _display
31
+ end
32
+ end
33
+
34
+ def set(identifier, value)
35
+ if @pbars[identifier]
36
+ @pbars[identifier].set(value)
37
+ end
38
+ end
39
+
40
+ def title(identifier, title)
41
+ if @pbars[identifier]
42
+ @pbars[identifier].message = title
43
+ end
44
+ end
45
+
46
+ def set_file_transfer(identifier)
47
+ if @pbars[identifier]
48
+ @pbars[identifier].file_transfer_mode
49
+ end
50
+ end
51
+
52
+ def finish(identifier)
53
+ if @pbars[identifier]
54
+ @pbars[identifier].finish
55
+ _display
56
+ @@lock.synchronize do
57
+ @pbars.delete(identifier)
58
+ end
59
+ end
60
+ end
61
+
62
+ def display
63
+ @display = true
64
+ _display(true)
65
+ unless @options[:debug]
66
+ @update_thread = Thread.new {
67
+ loop {
68
+ sleep(@interval)
69
+ _display
70
+ }
71
+ }
72
+ end
73
+ end
74
+
75
+ def hide
76
+ @display = false
77
+ if @update_thread
78
+ Thread.kill(@update_thread)
79
+ end
80
+ end
81
+
82
+ private
83
+ def _display(first = false)
84
+ return unless @display
85
+
86
+ @@lock.synchronize do
87
+ output = @pbars.collect {|identifier, pbar| pbar.render }
88
+ unless @options[:debug] or first
89
+ print "\e[#{output.count}F"
90
+ end
91
+ puts output.join("\n")
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,66 @@
1
+ require 'powerbar'
2
+
3
+ module Alula
4
+ class ProgressBar < PowerBar
5
+ attr_accessor :showing
6
+ attr_accessor :message
7
+
8
+ def initialize(message, total, opts = {})
9
+ super(opts)
10
+
11
+ @message = "%20s" % message
12
+ @done = 0
13
+ @total = total
14
+
15
+ @showing = true
16
+
17
+ self.settings.tty.finite.template.main = '${<msg>}: ${<percent>% } ${[<bar>] }${ ETA: <eta>}'
18
+ self.settings.tty.finite.template.barchar = 'o'
19
+ self.settings.tty.finite.template.padchar = ' '
20
+ end
21
+
22
+ def file_transfer_mode
23
+ self.settings.tty.finite.template.main = '${<msg>}: ${<percent>% } ${[<bar>] }${<rate>/s }${<done>}${ ETA: <eta>}'
24
+ end
25
+
26
+ def render(opts = {})
27
+ super({msg: @message, done: @done, total: @total}.merge(opts))
28
+ end
29
+
30
+ def step
31
+ @done += 1
32
+ end
33
+
34
+ def set(value)
35
+ @done = value
36
+ end
37
+
38
+ def vanish
39
+ self.wipe
40
+ end
41
+
42
+ def finish(fill = true)
43
+ # self.close(true)
44
+ render(
45
+ {
46
+ :done => fill && !state.total.is_a?(Symbol) ? state.total : state.done,
47
+ :tty => {
48
+ :finite => { :show_eta => false },
49
+ :infinite => { :show_eta => false },
50
+ },
51
+ :notty => {
52
+ :finite => { :show_eta => false },
53
+ :infinite => { :show_eta => false },
54
+ },
55
+ })
56
+ # scope.output.call(scope.template.close) unless scope.template.close.nil?
57
+ state.closed = true
58
+ end
59
+
60
+ private
61
+ # Monkey-patch to force percentage always being three characters long
62
+ def h_percent
63
+ sprintf "%3d", percent
64
+ end
65
+ end
66
+ end
@@ -1,336 +1,405 @@
1
- require 'yaml'
2
- require 'jekyll'
3
- require 'sprockets'
4
- require 'active_support/inflector/methods'
5
- require 'progressbar'
6
- require 'stringex'
7
-
8
- require 'alula/theme'
9
- require 'alula/plugins'
10
- require 'alula/assethelper'
1
+ require 'alula/core_ext'
11
2
 
12
- # Compressors
3
+ require 'alula/config'
4
+ require 'alula/plugin'
5
+ require 'alula/storage'
6
+ require 'alula/content'
7
+ require 'alula/context'
8
+ require 'alula/generator'
9
+ require 'alula/attachment_processor'
13
10
  require 'alula/compressors'
11
+ require 'alula/cdn'
12
+ require 'alula/helpers'
13
+ require 'alula/progress'
14
+
15
+ require 'thor'
16
+ require 'sprockets'
17
+ require 'i18n'
18
+ require 'parallel'
19
+ require 'hashie/mash'
14
20
 
15
- # Jekyll extensions, plugins, tags
16
- # These are used always in every blog, i.e. mandatory plugins
17
- require 'alula/plugins/assets'
18
- require 'alula/plugins/pagination'
21
+ # Silence Tilt
22
+ require 'sass'
23
+ require 'coffee-script'
19
24
 
20
25
  module Alula
21
26
  class Site
27
+ def self.instance; @@instance; end
28
+
29
+ # Global configuration
30
+ attr_reader :config
31
+
32
+ # Storage
33
+ attr_reader :storage
34
+
35
+ # Context for rendering
36
+ attr_reader :context
37
+
38
+ # Progress displayer
39
+ attr_reader :progress
40
+
41
+ # CDN Resolver for Site
42
+ attr_reader :cdn
43
+
44
+ # Site Plugins
45
+ attr_reader :plugins
46
+
47
+ # Site filters
48
+ attr_reader :filters
49
+
50
+ # Compressors
51
+ attr_reader :compressors
52
+
53
+ # Site metadata information
54
+ attr_reader :metadata
22
55
 
23
- attr_reader :config, :jekyll
56
+ # Site attachment mapping
57
+ attr_reader :attachments
24
58
 
25
- def initialize(override = {})
26
- # Load configuration
27
- @config = YAML.load_file('config.yml').deep_merge(override)
28
-
29
- # Register local theme path
30
- Alula::Theme.register("themes")
31
-
32
- # Initialize Jekyll
33
- options = Alula::DEFAULTS.deep_merge({
34
- # Site options
35
- 'title' => @config["title"],
36
- 'tagline' => @config["tagline"],
37
- 'author' => @config["author"],
38
- 'root' => @config["root"],
39
- 'asset_path' => File.join(@config["root"], "assets"),
40
- 'permalink' => @config["permalink"],
41
- 'paginate' => @config["paginate"],
42
- 'pagination_dir' => @config["pagination_dir"],
43
- 'excerpt_link' => @config["excerpt_link"],
59
+ # Theme
60
+ attr_reader :theme
61
+
62
+ # User generated content
63
+ attr_reader :content
64
+
65
+ # System generated content, pages, pagination, etc.
66
+ attr_reader :generated
67
+
68
+ def initialize(options)
69
+ @@instance = self
70
+
71
+ # Read local config
72
+ @config = Config.new(options)
73
+
74
+ @storage = Storage.load(site: self)
75
+
76
+ @metadata = Content::Metadata.new({
77
+ base_locale: @config.locale,
78
+ environment: @config.environment,
79
+
80
+ title: @config.title,
81
+ author: @config.author,
82
+ tagline: @config.tagline,
83
+ url: @config.url,
84
+
85
+ theme: @config.theme,
86
+
87
+ # Use this to store information of GIT site or note
88
+ git: ::File.directory?(".git"),
44
89
  })
45
90
 
46
- @jekyll = Jekyll::Site.new(options)
91
+ # Progress displayer
92
+ @progress = Progress.new(debug: options["debug"])
47
93
 
48
- @themepath = Alula::Theme.find_theme(@config['theme'])
49
- unless @themepath
50
- raise "Cannot find theme #{@config['theme']}"
94
+ # Compressors
95
+ compressors = if @config.assets.compress
96
+ {
97
+ html: Alula::Compressors::HTMLCompressor.new,
98
+ css: Alula::Compressors::CSSCompressor.new,
99
+ js: Alula::Compressors::JSCompressor.new,
100
+ }
101
+ else
102
+ {
103
+ html: Alula::Compressors::DummyCompressor.new,
104
+ css: Alula::Compressors::DummyCompressor.new,
105
+ js: Alula::Compressors::DummyCompressor.new,
106
+ }
51
107
  end
108
+ @compressors = Hashie::Mash.new(compressors)
52
109
 
53
- # Initialize Sprockets
54
- @sprockets = Sprockets::Environment.new
55
- # Set our compressor
56
- if @config['asset_compress']
57
- @sprockets.css_compressor = Alula::Compressors::CSSCompressor.new
58
- @sprockets.js_compressor = Alula::Compressors::JSCompressor.new
59
-
60
- [Jekyll::Post, Jekyll::Page].each do |klass|
61
- klass.send(:include, Alula::Compressors::HTMLCompressor)
62
- klass.send(:alias_method, :output_without_compression, :output)
63
- klass.send(:alias_method, :output, :output_with_compression)
64
- end
65
- end
66
-
67
- # Add theme to asset paths
68
- @sprockets.append_path File.join(@themepath, @config['theme'], "stylesheets")
69
- @sprockets.append_path File.join(@themepath, @config['theme'], "javascripts")
70
-
71
- # Generated assets
72
- @sprockets.append_path File.join("_tmp", "assets")
110
+ @attachments = AttachmentProcessor.new(site: self)
73
111
 
74
- # Attachments
75
- @sprockets.append_path File.join("attachments", "_generated")
112
+ # Set up CDN resolver
113
+ @cdn = CDN.load(site: self)
76
114
 
77
- # Vendor assets
78
- vendor_path = File.expand_path(File.join(File.dirname(__FILE__), *%w{.. .. vendor}))
79
- @sprockets.append_path File.join(vendor_path, "stylesheets")
80
- @sprockets.append_path File.join(vendor_path, "javascripts")
115
+ @plugins = {}
116
+
117
+ @filters = {}
118
+
119
+ # Set up I18n
120
+ l10n_path = File.join(File.dirname(__FILE__), "..", "..", "locales", "l10n", "*.yml")
121
+ locale_path = File.join(File.dirname(__FILE__), "..", "..", "locales", "*.yml")
122
+ custom_locale_path = File.join(@storage.path(:custom, "locales"), "*.yml")
123
+ I18n.load_path += Dir[l10n_path]
124
+ I18n.load_path += Dir[locale_path]
125
+ I18n.load_path += Dir[custom_locale_path]
126
+ I18n.default_locale = @config.locale
127
+
128
+ # Set up default head addons
129
+ Alula::Plugin.addon(:head, "<meta name=\"generator\" content=\"Alula #{Alula::VERSION}\">")
130
+ Alula::Plugin.addon(:head, ->(context){"<link rel=\"icon\" type=\"image/png\" href=\"#{context.asset_url('favicon.png')}\">"})
81
131
 
82
- # Initialize blog plugins
83
- if @config["plugins"] != nil
84
- @config["plugins"].each do |plugin, opts|
85
- require "alula/plugins/#{plugin}"
86
-
87
- plugin_class = Alula::Plugins.const_get(ActiveSupport::Inflector.camelize(plugin, true))
88
- path = plugin_class.install(opts)
89
- @sprockets.append_path File.join(path, "stylesheets")
90
- @sprockets.append_path File.join(path, "javascripts")
91
- end
92
- end
93
132
  end
94
133
 
134
+ # Compiles a site to static website
95
135
  def generate
96
- puts "==> Generating blog"
136
+ # Load our plugins and filters
137
+ load_plugins
138
+ load_filters
139
+
140
+ # Prepare public folder
141
+ prepare(true)
97
142
 
98
- # Prepare Jekyll environment
99
- prepare
143
+ load_content
100
144
 
101
- # Generate missing assets
102
- assetgen
145
+ process_attachments
103
146
 
104
- # Generate asset manifest
105
- compile
147
+ compile_assets
106
148
 
107
- # Execute jekyll
108
- process
149
+ render
109
150
 
110
- # Cleanup
111
151
  cleanup
152
+ # Store cached version of configuration
153
+ cached_config = File.join(storage.path(:cache), "config.yml")
154
+ @config.write_cache(cached_config)
112
155
  end
113
156
 
114
- def preview
115
- generate
116
-
117
- require 'webrick'
118
- # include WEBrick
119
-
120
- FileUtils.mkdir_p("public")
121
-
122
- mime_types = WEBrick::HTTPUtils::DefaultMimeTypes
123
- mime_types.store 'js', 'application/javascript'
124
-
125
- s = WEBrick::HTTPServer.new(
126
- :Port => 3000,
127
- :MimeTypes => mime_types
128
- )
129
- s.mount('/', WEBrick::HTTPServlet::FileHandler, "public")
130
- t = Thread.new {
131
- s.start
132
- }
133
-
134
- trap("INT") { s.shutdown }
135
- t.join()
157
+ # Proxy to metadata
158
+ def method_missing(meth, *args, &blk)
159
+ # Proxy to metadata
160
+ if !meth[/=$/] and self.metadata.respond_to?(meth)
161
+ args.unshift(self.context.locale || self.config.locale) if args.empty?
162
+ self.metadata.send(meth, *args)
163
+ else
164
+ super
165
+ end
136
166
  end
137
167
 
138
- def asset_attach(a_post, assets)
139
- # Find the post
140
- post = find_post(a_post) or raise "Cannot find post #{a_post}"
141
-
142
- /(?<date>(\d{4}-\d{2}-\d{2}))/ =~ post
143
- date = Time.parse(date)
144
- asset_path = File.join(%w{%Y %m %d}.collect{|f| date.strftime(f) })
145
-
146
- helper = Alula::AssetHelper.new(asset_path, @config)
147
-
148
- post_io = File.open(post, "a")
149
- assets.each do |asset|
150
- type, generated = helper.process(asset, :type => :attachment)
151
- tn_type, tn_generated = helper.process(asset, :type => :thumbnail)
152
- if generated and tn_generated
153
- # Asset processed
154
- puts "(#{asset}) done."
155
- if handler = Alula::Plugins.attachment_handler(type)
156
- post_io.puts handler.call(generated[0])
157
- else
158
- post_io.puts "{% image _images/#{generated[0]} %}"
159
- end
160
- else
161
- puts "(#{asset}) cannot process."
168
+ private
169
+ def load_plugins
170
+ config.plugins.each do |name, options|
171
+ if plugin = Alula::Plugin.load(name, options)
172
+ @plugins[name] = plugin
162
173
  end
163
174
  end
164
175
  end
165
176
 
166
- def clean
167
- cleanup
168
- FileUtils.rm_rf(Dir[File.join("attachments", "_images", "*")])
169
- FileUtils.rm_rf(Dir[File.join("attachments", "_thumbnails", "*")])
177
+ def load_filters
178
+ config.content.filters.each do |name, options|
179
+ if filter = Alula::Filter.load(name, options)
180
+ @filters[name] = filter
181
+ end
182
+ end
170
183
  end
171
184
 
172
- private
173
- def find_post(post)
174
- if File.exists?(post)
175
- return post
176
- elsif File.exists?(File.join("posts", post))
177
- return File.join("posts", post)
178
- else
179
- # Try to find by title
180
- title = post.to_url
181
- posts = Dir[File.join("posts", "*")].select { |p| p =~ /#{title}/ }
182
- if posts.count == 1
183
- return posts.first
185
+ def prepare(preserve = false)
186
+ say "==> Preparing environment" + (preserve ? " (preserving existing files)" : "")
187
+
188
+ # Delegate preparations to storage module
189
+ self.storage.prepare(preserve)
190
+
191
+ # Load theme
192
+ @context = Alula::Context.new(site: self, storage: self.storage)
193
+ @context.send(:extend, Helpers)
194
+
195
+ @theme = Alula::Theme.load(site: self)
196
+
197
+ # Create our asset environment
198
+ @environment = Environment.new
199
+ # Add compressor support
200
+ # if config.environment == "production"
201
+ @environment.css_compressor = @compressors.css
202
+ @environment.js_compressor = @compressors.js
203
+ # end
204
+ @environment.context_class.class_eval do
205
+ # include Helpers
206
+ def context; Alula::Site.instance.context; end
207
+ def method_missing(meth, *args, &blk)
208
+ return context.send(meth, *args, &blk) if context.respond_to?(meth)
209
+ super
184
210
  end
185
211
  end
212
+ @context.environment = @environment
213
+ @context.attachments = self.attachments
214
+
215
+ # Add generated attachements
216
+ @environment.append_path @storage.path(:cache, "attachments")
217
+
218
+ # Add generated assets
219
+ @environment.append_path @storage.path(:cache, "assets")
220
+
221
+ # Theme, plugins, vendor and customisation
222
+ [
223
+ self.theme.path,
224
+ *plugins.collect{|name, plugin| plugin.asset_path},
225
+ ::File.join(File.dirname(__FILE__), "..", "..", "vendor"),
226
+ ].each do |path|
227
+ %w{javascripts stylesheets images}.each {|p|
228
+ @environment.append_path ::File.join(path, "assets", p)
229
+ }
230
+ end
231
+
232
+ # Customisation
233
+ %w{javascripts stylesheets images}.each do |path|
234
+ @environment.prepend_path @storage.path(:custom, path)
235
+ end
186
236
  end
187
237
 
188
- def prepare
189
- puts "==> Prepare"
190
- # Clean our temporary folder
191
- FileUtils.rm_rf "_tmp"
192
- FileUtils.mkdir "_tmp"
193
- FileUtils.rm_rf "public"
194
- FileUtils.mkdir "public"
195
-
196
- # Copy Jekyll files
197
- # Install our theme
198
- FileUtils.mkdir_p File.join("_tmp", "_layouts")
199
- FileUtils.mkdir_p File.join("_tmp", "_includes")
200
-
201
- FileUtils.cp_r Dir[File.join(@themepath, @config['theme'], "layouts", "*")], File.join("_tmp", "_layouts")
202
- FileUtils.cp_r Dir[File.join(@themepath, @config['theme'], "includes", "*")], File.join("_tmp", "_includes")
203
-
204
- FileUtils.cp_r Dir[File.join(@themepath, @config['theme'], "site", "*")], "_tmp"
205
-
206
- # Copy posts
207
- FileUtils.mkdir_p File.join("_tmp", "_posts")
208
- FileUtils.cp_r Dir[File.join("posts", "*")], File.join("_tmp", "_posts")
238
+ def load_content
239
+ say "==> Loading site content"
209
240
 
210
- # Copy pages
211
- Dir[File.join("pages", "**", "*")].each do |page|
212
- next unless File.file?(page)
213
- page = File.join(page.split("/")[1..-1])
214
-
215
- FileUtils.mkdir_p File.join("_tmp", File.dirname(page))
216
- FileUtils.cp File.join("pages", page), File.join("_tmp", page)
241
+ # Read site content
242
+ @content = Content.new(site: self)
243
+ @content.load
244
+
245
+ # Do we have index page defined
246
+ if self.config.index
247
+ index_page = @content.by_slug(self.config.index)
248
+ if index_page
249
+ index_page.metadata.slug = "index"
250
+ index_page.metadata.template = "/:locale/:slug"
251
+ index_page.metadata.title = Hash[index_page.metadata.languages.collect{|lang| [lang, metadata.title(lang)]}]
252
+ end
217
253
  end
218
-
219
- FileUtils.mkdir_p File.join("_tmp", "assets")
220
254
  end
221
255
 
222
- def assetgen
223
- puts "==> Generating assets"
224
-
225
- # width, height = @config["images"]["thumbnails"].split("x").collect {|i| i.to_i }
256
+ def process_attachments
257
+ puts "==> Processing attachments"
226
258
 
227
- # Get all attachements
228
- attachments_path = File.join("attachments", "originals")
229
- images_path = File.join("attachments", "_generated", "images")
230
- thumbnails_path = File.join("attachments", "_generated", "thumbnails")
259
+ progress.create :attachments, title: "Attachments", total: self.content.attachments.count
260
+ progress.display
231
261
 
232
- attachments = Dir[File.join(attachments_path, "**", "*")]
233
- .select {|f| File.file?(f) }
234
- .collect {|f| File.join(f.split("/")[2..-1])}
235
- pb = ProgressBar.new "Assets", attachments.count
262
+ @@lock = Mutex.new
236
263
 
237
- attachments.each do |attachment|
238
- unless File.exists?(File.join(images_path, attachment))
239
- helper = Alula::AssetHelper.new(File.dirname(attachment), @config)
240
- type, generated = helper.process(File.join("attachments", "originals", attachment), :type => :attachment)
264
+ Parallel.map(self.content.attachments, :in_threads => Parallel.processor_count) do |attachment|
265
+ if processor = attachments.get(attachment)
266
+ processor.process
241
267
  end
242
-
243
- unless File.exists?(File.join(thumbnails_path, attachment))
244
- helper = Alula::AssetHelper.new(File.dirname(attachment), @config)
245
- tn_type, tn_generated = helper.process(File.join("attachments", "originals", attachment), :type => :thumbnail)
268
+ @@lock.synchronize do
269
+ progress.step(:attachments)
246
270
  end
247
-
248
- pb.inc
249
271
  end
250
-
251
- pb.finish
272
+
273
+ progress.finish(:attachments)
274
+ progress.hide
275
+
276
+ # DEBUG
277
+ require 'json'
278
+ File.open(self.storage.path(:cache) + "/mapping.json", 'w') {|io| io.puts self.attachments.mapping.to_json}
252
279
  end
253
280
 
254
- def compile
281
+ def compile_assets
255
282
  puts "==> Compiling assets"
256
283
 
257
- # Package stylesheet
258
- File.open(File.join("_tmp", "assets", "styles.css"), "w") do |tf|
259
- tf.puts "/*"
260
- tf.puts " *=require #{@config["theme"]}"
284
+ # Generate stylesheet
285
+ @storage.output(:cache, "assets/style.css") do |io|
286
+ io.puts "/*"
287
+
288
+ # Theme style
289
+ io.puts " *= require #{self.config.theme}"
290
+
261
291
  # Plugins
262
- if @config["plugins"]
263
- @config["plugins"].each { |plugin, opts| tf.puts " *=require #{plugin}" }
292
+ @plugins.each do |name, plugin|
293
+ io.puts " *= require #{name}"
294
+ end
295
+
296
+ # Vendored
297
+
298
+ # Blog customization
299
+ @storage.custom(/stylesheets\/.*.css.*$/).each do |name, item|
300
+ name = File.basename(name).gsub(/(\.\S+)$/, '')
301
+ io.puts " *= require #{name}"
264
302
  end
265
- tf.puts " */"
303
+
304
+ io.puts "*/"
266
305
  end
306
+ # Add stlesheet to template
307
+ Alula::Plugin.addon(:head, ->(context){ context.stylesheet_link("style") })
267
308
 
268
- # Package javascript
269
- File.open(File.join("_tmp", "assets", "scripts.js"), "w") do |tf|
270
- tf.puts "//=require #{@config["theme"]}"
309
+ # Generate javascript
310
+ @storage.output(:cache, "assets/script.js") do |io|
311
+ io.puts "/*"
312
+
313
+ # Theme scripts
314
+ io.puts " *= require #{self.config.theme}"
315
+
271
316
  # Plugins
272
- if @config["plugins"]
273
- @config["plugins"].each { |plugin, opts| tf.puts "//=require #{plugin}" }
317
+ @plugins.each do |name, plugin|
318
+ io.puts " *= require #{name}"
274
319
  end
275
- end
276
320
 
277
- File.open(File.join("_tmp", "assets", "scripts_body.js"), "w") do |tf|
278
- tf.puts "//=require #{@config["theme"]}_body"
279
- # Plugins
280
- if @config["plugins"]
281
- @config["plugins"].each { |plugin, opts| tf.puts "//=require #{plugin}_body" }
321
+ # Vendored
322
+ io.puts " *= require lazyload" if self.config.attachments.image.lazyload
323
+
324
+ # Customisation
325
+ @storage.custom(/javascripts\/.*.js.*$/).each do |name, item|
326
+ name = File.basename(name).gsub(/(\.\S+)$/, '')
327
+ io.puts " *= require #{name}"
282
328
  end
329
+ io.puts " */"
283
330
  end
331
+ # Add javascript to end of body
332
+ Alula::Plugin.addon(:body, ->(context){ context.javascript_link("script") })
284
333
 
334
+ # Compile all assets
335
+ progress.create :assets, title: "Compiling assets", total: @environment.each_logical_path.count
336
+ progress.display
337
+
338
+ @manifest = Manifest.new(@environment, @storage.path(:assets))
339
+ @manifest.progress = -> { progress.step(:assets) }
285
340
 
286
- @manifest = Sprockets::Manifest.new(@sprockets, File.join("public", "assets"))
287
-
288
- # Compile assets
289
341
  @manifest.compile
290
- # @manifest.compile([
291
- # # Stylesheet
292
- # "styles.css",
293
- #
294
- # # Javascript
295
- # "javascripts.js",
296
- #
297
- # # Attachments
298
- # Dir[File.join("attachments", "**", "*")]
299
- # .select {|f| File.file?(f) }
300
- # .collect {|f| File.join(f.split("/")[1..-1])},
301
- # ])
302
-
303
- # Inject our manifest to jekyll
304
- @jekyll.config["manifest"] = @manifest
305
-
306
- # Cleanup
307
- %w{styles.css scripts.js scripts_body.js}.each do |f|
308
- FileUtils.rm(File.join("_tmp", "assets", f))
309
- end
342
+
343
+ progress.finish(:assets)
344
+ progress.hide
310
345
  end
311
346
 
312
- def process
313
- puts "==> Processing Jekyll site"
314
-
315
- @jekyll.reset
316
- @jekyll.read
317
- @jekyll.generate
318
- @jekyll.render
319
- @jekyll.write
347
+ def render
348
+ say "==> Render site"
349
+
350
+ progress.create :render, title: "Rendering content", total: (self.content.posts.count + self.content.pages.count)
351
+ progress.display
352
+
353
+ # Render all user content, parallel...
354
+ (self.content.posts + self.content.pages).each do |content|
355
+ # Write content to file
356
+ content.write
357
+
358
+ progress.title(:render, "%20s" % content.name[0..19]) if self.config.debug
359
+ progress.step(:render)
360
+ end
361
+
362
+ progress.finish(:render)
363
+
364
+ # Copy static content
365
+ progress.create :static, title: "Copy statics", total: self.content.statics.count
366
+ self.content.statics.each do |static|
367
+ @storage.output_public(static.name) { |io| io.write(static.read) }
368
+
369
+ progress.step :static
370
+ end
371
+ progress.finish :static
372
+
373
+ progress.hide
320
374
  end
321
375
 
322
376
  def cleanup
323
- puts "==> Cleaning up"
377
+ say "==> Cleaning up"
378
+
379
+ asset_path = @storage.path(:assets)
380
+ assets = @environment.used
381
+ .collect{|u| @environment[u]}
382
+ .reject{|u| u.nil?}
383
+ .collect{|u| File.join(asset_path, u.digest_path)}
384
+ outputted = @storage.outputted.reject{|o|o[/^#{asset_path}/]}
385
+
386
+ keep = assets + outputted
387
+ Dir[File.join(@storage.path(:public), "**", "*")].each do |entry|
388
+ next unless File.file?(entry)
389
+ FileUtils.rm entry if File.file?(entry) and !keep.include?(entry)
390
+ end
324
391
 
325
- FileUtils.rm_rf "_tmp"
392
+ # Clean up empty directories
393
+ Dir[File.join(@storage.path(:public), "**", "*")].each do |entry|
394
+ next unless File.directory?(entry)
395
+ FileUtils.rmdir entry if Dir[File.join(entry, "**", "*")].count == 0
396
+ end
326
397
  end
327
- end
328
-
329
- DEFAULTS = Jekyll::DEFAULTS.deep_merge({
330
- 'source' => '_tmp',
331
- 'destination' => 'public',
332
- 'markdown' => 'kramdown',
333
398
 
334
- 'pagination_dir' => '/page/',
335
- })
399
+ # Output helpers
400
+ def say(msg)
401
+ @shell ||= Thor::Shell::Basic.new
402
+ @shell.say msg
403
+ end
404
+ end
336
405
  end