middleman-core 4.0.0.beta.1 → 4.0.0.beta.2

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.
@@ -0,0 +1,61 @@
1
+ require 'hamster'
2
+ require 'middleman-core/contracts'
3
+
4
+ # Immutable Callback Management, complete with Contracts validation.
5
+ module Middleman
6
+ class CallbackManager
7
+ include Contracts
8
+
9
+ Contract Any
10
+ def initialize
11
+ @callbacks = ::Hamster.hash
12
+ @subscribers = ::Hamster.vector
13
+ end
14
+
15
+ Contract RespondTo[:define_singleton_method], ArrayOf[Symbol] => Any
16
+ def install_methods!(install_target, names)
17
+ manager = self
18
+
19
+ names.each do |method_name|
20
+ install_target.define_singleton_method(method_name) do |*keys, &b|
21
+ key_set = keys.unshift(method_name)
22
+ manager.add(key_set.length > 1 ? key_set : key_set[0], &b)
23
+ end
24
+ end
25
+
26
+ install_target.define_singleton_method(:execute_callbacks) do |*args|
27
+ keys = args.shift
28
+ manager.execute(keys, args[0], self)
29
+ end
30
+
31
+ install_target.define_singleton_method(:callbacks_for, &method(:callbacks_for))
32
+ install_target.define_singleton_method(:subscribe_to_callbacks, &method(:subscribe))
33
+ end
34
+
35
+ Contract Or[Symbol, ArrayOf[Symbol]], Proc => Any
36
+ def add(keys, &block)
37
+ immutable_keys = keys.is_a?(Symbol) ? keys : ::Hamster::Vector.new(keys)
38
+
39
+ @callbacks = @callbacks.put(immutable_keys) do |v|
40
+ v.nil? ? ::Hamster::Vector.new([block]) : v.push(block)
41
+ end
42
+ end
43
+
44
+ Contract Proc => Any
45
+ def subscribe(&block)
46
+ @subscribers = @subscribers.push(block)
47
+ end
48
+
49
+ Contract Or[Symbol, ArrayOf[Symbol]], Maybe[ArrayOf[Any]], Maybe[RespondTo[:instance_exec]] => Any
50
+ def execute(keys, args=[], scope=self)
51
+ callbacks_for(keys).each { |b| scope.instance_exec(*args, &b) }
52
+ @subscribers.each { |b| scope.instance_exec(keys, args, &b) }
53
+ end
54
+
55
+ Contract Or[Symbol, ArrayOf[Symbol]] => ::Hamster::Vector
56
+ def callbacks_for(keys)
57
+ immutable_keys = keys.is_a?(Symbol) ? keys : ::Hamster::Vector.new(keys)
58
+ @callbacks.get(immutable_keys) || ::Hamster.vector
59
+ end
60
+ end
61
+ end
@@ -18,6 +18,13 @@ module Middleman::Cli
18
18
  method_option :port,
19
19
  aliases: '-p',
20
20
  desc: 'The port Middleman will listen on'
21
+ method_option :https,
22
+ type: :boolean,
23
+ desc: 'Serve the preview server over SSL/TLS'
24
+ method_option :ssl_certificate,
25
+ desc: 'Path to an X.509 certificate to use for the preview server'
26
+ method_option :ssl_private_key,
27
+ desc: "Path to an RSA private key for the preview server's certificate"
21
28
  method_option :verbose,
22
29
  type: :boolean,
23
30
  default: false,
@@ -63,6 +70,9 @@ module Middleman::Cli
63
70
  params = {
64
71
  port: options['port'],
65
72
  host: options['host'],
73
+ https: options['https'],
74
+ ssl_certificate: options['ssl_certificate'],
75
+ ssl_private_key: options['ssl_private_key'],
66
76
  environment: options['environment'],
67
77
  debug: options['verbose'],
68
78
  instrumenting: options['instrument'],
@@ -1,4 +1,5 @@
1
1
  require 'rack/mime'
2
+ require 'middleman-core/callback_manager'
2
3
 
3
4
  module Middleman
4
5
  class ConfigContext
@@ -14,10 +15,11 @@ module Middleman
14
15
  @app = app
15
16
  @template_context_class = template_context_class
16
17
 
17
- @ready_callbacks = []
18
- @after_build_callbacks = []
19
- @after_configuration_callbacks = []
20
- @configure_callbacks = {}
18
+ @callbacks = ::Middleman::CallbackManager.new
19
+ @callbacks.install_methods!(self, [:before_build, :after_build, :configure, :after_configuration, :ready])
20
+
21
+ # Trigger internal callbacks when app level are executed.
22
+ app.subscribe_to_callbacks(&method(:execute_callbacks))
21
23
  end
22
24
 
23
25
  def helpers(*helper_modules, &block)
@@ -43,48 +45,6 @@ module Middleman
43
45
  instance_eval File.read(other_config), other_config, 1
44
46
  end
45
47
 
46
- def ready(&block)
47
- @ready_callbacks << block
48
- end
49
-
50
- def execute_ready_callbacks
51
- @ready_callbacks.each do |b|
52
- instance_exec(&b)
53
- end
54
- end
55
-
56
- def after_build(&block)
57
- @after_build_callbacks << block
58
- end
59
-
60
- def execute_after_build_callbacks(*args)
61
- @after_build_callbacks.each do |b|
62
- instance_exec(*args, &b)
63
- end
64
- end
65
-
66
- def after_configuration(&block)
67
- @after_configuration_callbacks << block
68
- end
69
-
70
- def execute_after_configuration_callbacks
71
- @after_configuration_callbacks.each do |b|
72
- instance_exec(&b)
73
- end
74
- end
75
-
76
- def configure(key, &block)
77
- @configure_callbacks[key] ||= []
78
- @configure_callbacks[key] << block
79
- end
80
-
81
- def execute_configure_callbacks(key)
82
- @configure_callbacks[key] ||= []
83
- @configure_callbacks[key].each do |b|
84
- instance_exec(&b)
85
- end
86
- end
87
-
88
48
  def set(key, default=nil, &block)
89
49
  config.define_setting(key, default) unless config.defines_setting?(key)
90
50
  @app.config[key] = block_given? ? block : default
@@ -1,5 +1,6 @@
1
1
  if ENV['TEST'] || ENV['CONTRACTS'] == 'true'
2
2
  require 'contracts'
3
+ require 'hamster'
3
4
 
4
5
  module Contracts
5
6
  class IsA
@@ -27,21 +28,7 @@ if ENV['TEST'] || ENV['CONTRACTS'] == 'true'
27
28
  end
28
29
  end
29
30
 
30
- # class MethodDefined
31
- # def self.[](val)
32
- # @lookup ||= {}
33
- # @lookup[val] ||= new(val)
34
- # end
35
-
36
- # def initialize(val)
37
- # @val = val
38
- # end
39
-
40
- # def valid?(val)
41
- # val.method_defined? @val
42
- # end
43
- # end
44
-
31
+ VectorOf = Contracts::CollectionOf::Factory.new(::Hamster::Vector)
45
32
  ResourceList = Contracts::ArrayOf[IsA['Middleman::Sitemap::Resource']]
46
33
  end
47
34
  else
@@ -125,8 +112,8 @@ else
125
112
  class Frozen < Callable
126
113
  end
127
114
 
128
- # class MethodDefined < Callable
129
- # end
115
+ class VectorOf < Callable
116
+ end
130
117
  end
131
118
  end
132
119
 
@@ -1,3 +1,5 @@
1
+ require 'hamster'
2
+
1
3
  module Middleman
2
4
  module CoreExtensions
3
5
  module Collections
@@ -9,13 +11,13 @@ module Middleman
9
11
  attr_reader :descriptors
10
12
 
11
13
  def initialize
12
- @descriptors = []
14
+ @descriptors = ::Hamster.set
13
15
  end
14
16
 
15
17
  def method_missing(name, *args, &block)
16
18
  internal = :"_internal_#{name}"
17
19
  if respond_to?(internal)
18
- @descriptors << send(internal, *args, &block)
20
+ @descriptors = @descriptors.add(send(internal, *args, &block))
19
21
  else
20
22
  super
21
23
  end
@@ -10,6 +10,25 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
10
10
  # Exposes `langs` to templates
11
11
  expose_to_template :langs
12
12
 
13
+ def initialize(*)
14
+ super
15
+
16
+ require 'i18n'
17
+
18
+ # Don't fail on invalid locale, that's not what our current
19
+ # users expect.
20
+ ::I18n.enforce_available_locales = false
21
+
22
+ # This is for making the tests work - since the tests
23
+ # don't completely reload middleman, I18n.load_path can get
24
+ # polluted with paths from other test app directories that don't
25
+ # exist anymore.
26
+ app.after_configuration_eval do
27
+ ::I18n.load_path.delete_if { |path| path =~ %r{tmp/aruba} }
28
+ ::I18n.reload!
29
+ end if ENV['TEST']
30
+ end
31
+
13
32
  def after_configuration
14
33
  # See https://github.com/svenfuchs/i18n/wiki/Fallbacks
15
34
  unless options[:no_fallbacks]
@@ -8,7 +8,7 @@ module Middleman::CoreExtensions
8
8
 
9
9
  return if app.config.defines_setting? :show_exceptions
10
10
 
11
- app.config.define_setting :show_exceptions, !!ENV['TEST'], 'Whether to catch and display exceptions'
11
+ app.config.define_setting :show_exceptions, ENV['TEST'] ? false : true, 'Whether to catch and display exceptions'
12
12
  end
13
13
 
14
14
  def after_configuration
@@ -380,6 +380,7 @@ module Middleman
380
380
 
381
381
  def bind_after_configuration
382
382
  ext = self
383
+
383
384
  @app.after_configuration do
384
385
  ext.after_configuration if ext.respond_to?(:after_configuration)
385
386
 
@@ -8,6 +8,17 @@ module Middleman
8
8
  def initialize(app)
9
9
  @app = app
10
10
  @activated = {}
11
+
12
+ manager = self
13
+ {
14
+ before_sitemap: :before_sitemap,
15
+ initialized: :before_configuration
16
+ }.each do |key, value|
17
+ cb = proc { manager.auto_activate(value) }
18
+ @app.send(key, &cb)
19
+ end
20
+
21
+ @app.after_configuration_eval(&method(:activate_all))
11
22
  end
12
23
 
13
24
  def auto_activate(key)
@@ -59,14 +59,10 @@ module Middleman
59
59
  options = options.deep_merge(options[:renderer_options]) if options[:renderer_options]
60
60
 
61
61
  template_class = ::Tilt[path]
62
+
62
63
  # Allow hooks to manipulate the template before render
63
- @app.class.callbacks_for_hook(:before_render).each do |callback|
64
- newbody = if callback.respond_to?(:call)
65
- callback.call(body, path, locs, template_class)
66
- elsif callback.respond_to?(:evaluate)
67
- callback.evaluate(self, body, path, locs, template_class)
68
- end
69
- body = newbody if newbody # Allow the callback to return nil to skip it
64
+ body = @app.callbacks_for(:before_render).reduce(body) do |sum, callback|
65
+ callback.call(sum, path, locs, template_class) || sum
70
66
  end
71
67
 
72
68
  # Read compiled template from disk or cache
@@ -80,14 +76,8 @@ module Middleman
80
76
  end
81
77
 
82
78
  # Allow hooks to manipulate the result after render
83
- @app.class.callbacks_for_hook(:after_render).each do |callback|
84
- # Uber::Options::Value doesn't respond to call
85
- newcontent = if callback.respond_to?(:call)
86
- callback.call(content, path, locs, template_class)
87
- elsif callback.respond_to?(:evaluate)
88
- callback.evaluate(self, content, path, locs, template_class)
89
- end
90
- content = newcontent if newcontent # Allow the callback to return nil to skip it
79
+ content = @app.callbacks_for(:before_render).reduce(content) do |sum, callback|
80
+ callback.call(sum, path, locs, template_class) || sum
91
81
  end
92
82
 
93
83
  output = ::ActiveSupport::SafeBuffer.new ''
@@ -1,4 +1,6 @@
1
1
  require 'webrick'
2
+ require 'webrick/https'
3
+ require 'openssl'
2
4
  require 'middleman-core/meta_pages'
3
5
  require 'middleman-core/logger'
4
6
  require 'middleman-core/rack'
@@ -9,9 +11,13 @@ module Middleman
9
11
  class << self
10
12
  extend Forwardable
11
13
 
12
- attr_reader :app, :host, :port
14
+ attr_reader :app, :host, :port, :ssl_certificate, :ssl_private_key
13
15
  def_delegator :app, :logger
14
16
 
17
+ def https?
18
+ @https
19
+ end
20
+
15
21
  # Start an instance of Middleman::Application
16
22
  # @return [void]
17
23
  def start(opts={})
@@ -105,6 +111,8 @@ module Middleman
105
111
 
106
112
  config[:host] = opts[:host] if opts[:host]
107
113
  config[:port] = opts[:port] if opts[:port]
114
+ config[:ssl_certificate] = opts[:ssl_certificate] if opts[:ssl_certificate]
115
+ config[:ssl_private_key] = opts[:ssl_private_key] if opts[:ssl_private_key]
108
116
 
109
117
  ready do
110
118
  match_against = [
@@ -123,6 +131,10 @@ module Middleman
123
131
 
124
132
  @host = app.config[:host]
125
133
  @port = app.config[:port]
134
+ @https = app.config[:https]
135
+
136
+ @ssl_certificate = app.config[:ssl_certificate]
137
+ @ssl_private_key = app.config[:ssl_private_key]
126
138
 
127
139
  app.files.on_change :reload do
128
140
  $mm_reload = true
@@ -163,6 +175,22 @@ module Middleman
163
175
  DoNotReverseLookup: true
164
176
  }
165
177
 
178
+ if https?
179
+ http_opts[:SSLEnable] = true
180
+
181
+ if ssl_certificate || ssl_private_key
182
+ raise 'You must provide both :ssl_certificate and :ssl_private_key' unless ssl_private_key && ssl_certificate
183
+ http_opts[:SSLCertificate] = OpenSSL::X509::Certificate.new File.read ssl_certificate
184
+ http_opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new File.read ssl_private_key
185
+ else
186
+ # use a generated self-signed cert
187
+ http_opts[:SSLCertName] = [
188
+ %w(CN localhost),
189
+ %w(CN #{host})
190
+ ].uniq
191
+ end
192
+ end
193
+
166
194
  if is_logging
167
195
  http_opts[:Logger] = FilteredWebrickLog.new
168
196
  else
@@ -203,7 +231,8 @@ module Middleman
203
231
  # @return [URI]
204
232
  def uri
205
233
  host = (@host == '0.0.0.0') ? 'localhost' : @host
206
- URI("http://#{host}:#{@port}")
234
+ scheme = https? ? 'https' : 'http'
235
+ URI("#{scheme}://#{host}:#{@port}")
207
236
  end
208
237
  end
209
238
 
@@ -85,7 +85,7 @@ module Middleman
85
85
  full_request_path = File.join(env['SCRIPT_NAME'], request_path) # Path including rack mount
86
86
 
87
87
  # Run before callbacks
88
- @middleman.run_hook :before
88
+ @middleman.execute_callbacks(:before)
89
89
 
90
90
  # Get the resource object for this path
91
91
  resource = @middleman.sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20'))
@@ -13,9 +13,7 @@ module Middleman
13
13
  ::Tilt.register 'coffee', DebuggingCoffeeScriptTemplate
14
14
  ::Tilt.prefer(DebuggingCoffeeScriptTemplate)
15
15
 
16
- app.before_configuration do
17
- DebuggingCoffeeScriptTemplate.middleman_app = self
18
- end
16
+ DebuggingCoffeeScriptTemplate.middleman_app = app
19
17
  end
20
18
 
21
19
  # A Template for Tilt which outputs debug messages
@@ -12,7 +12,7 @@ module Middleman
12
12
  def initialize(app, config={}, &block)
13
13
  super
14
14
 
15
- # Array of callbacks which can ass ignored
15
+ # Array of callbacks which can assign ignored
16
16
  @ignored_callbacks = Set.new
17
17
 
18
18
  @app.sitemap.define_singleton_method(:ignored?, &method(:ignored?))
@@ -5,21 +5,18 @@ module Middleman
5
5
  module Sitemap
6
6
  module Extensions
7
7
  class OnDisk < Extension
8
- attr_accessor :waiting_for_ready
9
-
10
8
  def initialize(app, config={}, &block)
11
9
  super
12
10
 
13
11
  @file_paths_on_disk = Set.new
14
-
15
- scoped_self = self
16
12
  @waiting_for_ready = true
13
+ end
17
14
 
18
- @app.ready do
19
- scoped_self.waiting_for_ready = false
20
- # Make sure the sitemap is ready for the first request
21
- sitemap.ensure_resource_list_updated!
22
- end
15
+ def ready
16
+ @waiting_for_ready = false
17
+
18
+ # Make sure the sitemap is ready for the first request
19
+ app.sitemap.ensure_resource_list_updated!
23
20
  end
24
21
 
25
22
  Contract Any
@@ -27,6 +24,7 @@ module Middleman
27
24
  app.files.on_change(:source, &method(:update_files))
28
25
  end
29
26
 
27
+ Contract IsA['Middleman::SourceFile'] => Bool
30
28
  def ignored?(file)
31
29
  @app.config[:ignored_sitemap_matchers].any? do |_, callback|
32
30
  callback.call(file, @app)
@@ -48,9 +46,10 @@ module Middleman
48
46
 
49
47
  # Force sitemap rebuild so the next request is ready to go.
50
48
  # Skip this during build because the builder will control sitemap refresh.
51
- @app.sitemap.ensure_resource_list_updated! unless waiting_for_ready || @app.build?
49
+ @app.sitemap.ensure_resource_list_updated! unless @waiting_for_ready || @app.build?
52
50
  end
53
51
 
52
+ Contract ArrayOf[IsA['Middleman::SourceFile']]
54
53
  def files_for_sitemap
55
54
  @app.files.by_type(:source).files.reject(&method(:ignored?))
56
55
  end