middleman-apps 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +14 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +19 -0
  6. data/LICENSE.txt +19 -0
  7. data/README.md +185 -0
  8. data/Rakefile +14 -0
  9. data/features/activation.feature +53 -0
  10. data/features/build.feature +20 -0
  11. data/features/child_app.feature +18 -0
  12. data/features/complex_app.feature +66 -0
  13. data/features/directory_indexes.feature +52 -0
  14. data/features/not_found_rack.feature +53 -0
  15. data/features/not_found_server.feature +8 -0
  16. data/features/step_definitions/rack_app_steps.rb +15 -0
  17. data/features/support/env.rb +18 -0
  18. data/features/verbose.feature +23 -0
  19. data/fixtures/complex-app/apps/awesome_api.rb +11 -0
  20. data/fixtures/complex-app/apps/child_app.rb +15 -0
  21. data/fixtures/complex-app/apps/test_app.rb +12 -0
  22. data/fixtures/complex-app/build/error.html +1 -0
  23. data/fixtures/complex-app/build/index.html +2 -0
  24. data/fixtures/complex-app/config.rb +1 -0
  25. data/fixtures/complex-app/source/index.html.erb +2 -0
  26. data/fixtures/simple-app/apps/test_app.rb +8 -0
  27. data/fixtures/simple-app/build/error.html +1 -0
  28. data/fixtures/simple-app/build/index.html +2 -0
  29. data/fixtures/simple-app/config.rb +1 -0
  30. data/fixtures/simple-app/source/index.html.erb +2 -0
  31. data/lib/middleman.rb +1 -0
  32. data/lib/middleman/apps.rb +65 -0
  33. data/lib/middleman/apps/base.rb +36 -0
  34. data/lib/middleman/apps/extension.rb +231 -0
  35. data/lib/middleman/apps/rack_contrib.rb +77 -0
  36. data/lib/middleman/apps/version.rb +5 -0
  37. data/middleman-apps.gemspec +27 -0
  38. metadata +145 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 07e504c02695ba4f0387c2d974c49030b12a0655
4
+ data.tar.gz: 46a2badf1b57e7572d6f71232999ce7783c8f50b
5
+ SHA512:
6
+ metadata.gz: 110ab87281cb3a4109e9c3ef5b73d4db382a8622a7b34534b4b01ef31aaeab30af06e783bde2cccb727c9d043f32004eaf7d3e5227dfeafd00db1c293eff973e
7
+ data.tar.gz: 5f8a7f7edc8b1785e3ff7da9ace36d4e731c7ec01319256adea95d71cdf4f200e49ee6c7cb0b6a5b8ef84d1798d8d6f01a95cc2e826dacb7483d978efe225a8d
@@ -0,0 +1,8 @@
1
+ # Ignore bundler lock file
2
+ /Gemfile.lock
3
+
4
+ # Ignore pkg folder
5
+ /pkg
6
+
7
+ /tmp
8
+ /doc
@@ -0,0 +1,14 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'lib/middleman/apps/rack_contrib.rb'
4
+ - 'tmp/aruba'
5
+
6
+ Metrics/ClassLength:
7
+ Exclude:
8
+ - 'lib/middleman/apps/extension.rb'
9
+
10
+ Style/Documentation:
11
+ Exclude:
12
+ - 'spec/**/*'
13
+ - 'test/**/*'
14
+
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.4.0
4
+ - 2.2.2 # rack 2.0 requires ruby >= 2.2.2
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ # If you do not have OpenSSL installed, update
2
+ # the following line to use "http://" instead
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in middleman-apps.gemspec
6
+ gemspec
7
+
8
+ group :development do
9
+ gem 'rake'
10
+ gem 'rdoc'
11
+ gem 'yard'
12
+ end
13
+
14
+ group :test do
15
+ gem 'aruba'
16
+ gem 'capybara'
17
+ gem 'cucumber'
18
+ gem 'rspec'
19
+ end
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2017 Nikhil Gupta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,185 @@
1
+ ## Middleman Apps
2
+
3
+ `middleman-apps` is an extension for the [Middleman] static site
4
+ generator that allows you to run truly dynamic pages within your static
5
+ site using Rack-compatible (e.g. Sinatra) based child apps.
6
+
7
+ You can create dynamic pages using this extension. Maybe you want to:
8
+
9
+ - Create simple APIs that can be consumed by your static app via AJAX
10
+ - Showcase Code snippets alongside your blog.
11
+ - Provide simple Demos of a tech writeup, etc.
12
+ - Display a dynamic Gallery of your most recent timeline from Flickr?
13
+ - ... suggest one? ...
14
+
15
+ The best way to get started with this extension is to have a look at the
16
+ various `features` in the test suite. There is a test for each feature
17
+ that exists for extension, such as:
18
+
19
+ - Mount Rack apps by, simply, placing them in `MM_ROOT/apps` directory.
20
+ - Allow running child apps in-tandem with the static (built) MM app, and
21
+ wrap them both in a single `Rack::App`, which can be run with e.g.
22
+ `rackup`.
23
+ - Use a specific `404` error page that is common across your static
24
+ website, and child apps mounted using this extension.
25
+ - Discovery of mountable child apps and automatic mounting.
26
+ - Optionally, specify a URL where a child app should be mounted.
27
+ - Optionally, specify a Class name or namespace for the child app.
28
+ - Optionally, inherit from `Middleman::Apps::BaseApp` for helper methods
29
+ and added goodies for your apps.
30
+
31
+ ### Example
32
+
33
+ You can, e.g. create a simple JSON API endpoint at: `/base64-api/`
34
+ by creating a file named `apps/base64_api.rb` with:
35
+
36
+ ```ruby
37
+ # config.rb
38
+ ...
39
+ activate :apps
40
+ ...
41
+
42
+ # apps/base64_api.rb
43
+ require 'sinatra'
44
+ class Base64Api < Sinatra::Base
45
+ get '/decode/:str' do
46
+ Base64.decode64(params['str'])
47
+ end
48
+ get '/encode/:str' do
49
+ Base64.encode64(params['str'])
50
+ end
51
+ end
52
+ ```
53
+
54
+ Run/Build your Middleman site now, and visit:
55
+ `/base64-api/encode/somestring`. Voila! It just works!
56
+
57
+ A `config.ru` is, also, generated for you, so that you can keep
58
+ using these dynamic pages/endpoints using `rackup`. Try running
59
+ `rackup`, and visiting the above endpoint on that server instance.
60
+
61
+ ## Installation
62
+
63
+ If you're just getting started, install the `middleman` gem and generate
64
+ a new project:
65
+
66
+ ```
67
+ gem install middleman
68
+ middleman init MY_PROJECT
69
+ ```
70
+
71
+ If you already have a Middleman project: Add `gem "middleman-apps"`
72
+ to your `Gemfile` and run `bundle install`.
73
+
74
+ ## Configuration
75
+
76
+ ```
77
+ activate :apps,
78
+ map: {}, # Mappings for custom URL and Class for child apps
79
+ verbose: false, # Display warnings if any when building main app
80
+ not_found: "404.html", # Render this page for 404 errors, if it exists
81
+ namespace: nil # By default, use a namespace for finding Class
82
+ # of a child app
83
+ ```
84
+
85
+ A `config.ru` will be generated for you (if one does not exist already),
86
+ when you preview/build your MM site. Your child apps will be mounted in
87
+ both development (preview) mode (e.g. via `middleman server`) as well in
88
+ production (build) mode of MM (e.g. running the built app using `puma`
89
+ or `rackup`).
90
+
91
+ ## Options
92
+
93
+ ### `not_found: '404.html'`
94
+
95
+ This option defines a custom HTML page to use for 404 errors. By
96
+ default, HTML from `404.html` is served if it exists. Otherwise,
97
+ a default 404 response is sent.
98
+
99
+ A warning is generated in `verbose` mode, if this file does not exist!
100
+ Set this option to `false`, if you prefer not to use a 404 page and
101
+ would rather stick with default 404 response from Rack.
102
+
103
+ ### `verbose: false`
104
+
105
+ If true, display warnings such as non-existent 404 error page, and any
106
+ child apps that were ignored when starting server in
107
+ development/production mode.
108
+
109
+ ### `namespace: nil`
110
+
111
+ Specify a global namespace to find child apps in. Look at `map` option
112
+ below for better clarification.
113
+
114
+ ### `map: {}`
115
+
116
+ This option can be used to specify a custom URL endpoint for a specific
117
+ child app, or different class name for your child app if different than
118
+ what is guessed (`str.classify.constantize`).
119
+
120
+ ```ruby
121
+ activate :apps,
122
+ namespace: 'DynamicPages',
123
+ map: {
124
+ test_app: 'test',
125
+ awesome_api: {
126
+ url: 'api',
127
+ class: "Project::AwesomeAPI"
128
+ }
129
+ }
130
+ ```
131
+
132
+ With the above configuration in place, here is what happens:
133
+
134
+ - class `DynamicPages::TestApp` should exist inside
135
+ `MM_ROOT/apps/test_app.rb` file, and it will be mounted at: `/test`
136
+ endpoint.
137
+
138
+ - class `Project::AwesomeAPI` should exist inside
139
+ `MM_ROOT/apps/awesome_api.rb` file, and it will be mounted at: `/api`
140
+ endpoint.
141
+
142
+ - If another child app `DynamicPages::OtherMiniProject` exists in:
143
+ `MM_ROOT/apps/other_mini_project.rb`, it will be mounted at:
144
+ `/other-mini-project` endpoint.
145
+
146
+ ## Community
147
+
148
+ The official community forum is available at: http://forum.middlemanapp.com
149
+
150
+ ## Bug Reports
151
+
152
+ Github Issues are used for managing bug reports and feature requests. If
153
+ you run into issues, please search the issues and submit new problems:
154
+ https://github.com/middleman/middleman-blog/issues
155
+
156
+ The best way to get quick responses to your issues and swift fixes to
157
+ your bugs is to submit detailed bug reports, include test cases and
158
+ respond to developer questions in a timely manner. Even better, if you
159
+ know Ruby, you can submit
160
+ [Pull Requests](https://help.github.com/articles/using-pull-requests)
161
+ containing Cucumber Features which describe how your feature should work
162
+ or exploit the bug you are submitting.
163
+
164
+ ## How to Run Cucumber Tests
165
+
166
+ - Checkout Repository:
167
+ `git clone https://github.com/nikhgupta/middleman-apps.git`
168
+
169
+ - Install Bundler: `gem install bundler`
170
+
171
+ - Run `bundle install` inside the project root to install the gem
172
+ dependencies.
173
+
174
+ - Run test cases: `bundle exec rake test`
175
+
176
+ ## Donate
177
+
178
+ [Click here to lend your support to Middleman](https://spacebox.io/s/4dXbHBorC3)
179
+
180
+ ## License
181
+
182
+ Copyright (c) 2018 Nikhil Gupta. MIT Licensed, see [LICENSE] for details.
183
+
184
+ [middleman]: http://middlemanapp.com
185
+ [LICENSE]: https://github.com/nikhgupta/middleman-apps/blob/master/LICENSE.txt
@@ -0,0 +1,14 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'cucumber/rake/task'
5
+
6
+ Cucumber::Rake::Task.new(:cucumber, 'Run features that should pass') do |t|
7
+ t.cucumber_opts = '--color --tags ~@wip --strict'
8
+ end
9
+
10
+ require 'rake/clean'
11
+
12
+ task test: ['cucumber']
13
+
14
+ task default: :test
@@ -0,0 +1,53 @@
1
+ Feature: Activating MiddlemanApps
2
+
3
+ Scenario: Without `middleman-apps`
4
+ Given a fixture app "simple-app"
5
+ And a file named "config.rb" with:
6
+ """
7
+ """
8
+ And the Server is running at "simple-app"
9
+ When I go to "/"
10
+ Then I should see "<h1>Middleman</h1>"
11
+ When I go to "/test-app"
12
+ Then the status code should be "404"
13
+ And I should see "<h1>File Not Found</h1>"
14
+
15
+ Scenario: Without `middleman-apps` with Rack
16
+ Given a fixture app "simple-app"
17
+ And a file named "config.rb" with:
18
+ """
19
+ require 'sinatra'
20
+ require_relative 'apps/test_app'
21
+ map("/test-app") { run TestApp }
22
+ """
23
+ And the Server is running at "simple-app"
24
+ When I go to "/"
25
+ Then I should see "<h1>Middleman</h1>"
26
+ When I go to "/test-app"
27
+ Then I should see "fail"
28
+ When I go to "/test-app/?test=1"
29
+ Then I should see "pass"
30
+
31
+ Scenario: With `middleman-apps`
32
+ Given a fixture app "simple-app"
33
+ And a file named "config.rb" with:
34
+ """
35
+ activate :apps
36
+ """
37
+ And the Server is running at "simple-app"
38
+ When I go to "/"
39
+ Then I should see "<h1>Middleman</h1>"
40
+ When I go to "/test-app"
41
+ Then I should see "fail"
42
+ When I go to "/test-app/?test=1"
43
+ Then I should see "pass"
44
+
45
+ Scenario: Adds a `config.ru`
46
+ Given a fixture app "simple-app"
47
+ Then the file "config.ru" should not exist
48
+ Given a file named "config.rb" with:
49
+ """
50
+ activate :apps
51
+ """
52
+ And the Server is running at "simple-app"
53
+ Then the file "config.ru" should exist
@@ -0,0 +1,20 @@
1
+ Feature: Built app
2
+
3
+ Scenario: Builds successfully
4
+ Given a successfully built app at "simple-app"
5
+ Then the file "config.ru" should exist
6
+ And the file "build/index.html" should exist
7
+ And the file "build/apps/test_app.rb" should not exist
8
+
9
+ Scenario: Running built app
10
+ Given a fixture app "simple-app"
11
+ And app is running with config:
12
+ """
13
+ activate :apps
14
+ """
15
+ When I go to "/"
16
+ Then I should see "<h1>Middleman</h1>"
17
+ When I go to "/test-app"
18
+ Then I should see "fail"
19
+ When I go to "/test-app?test=1"
20
+ Then I should see "pass"
@@ -0,0 +1,18 @@
1
+ Feature: 404 error pages for Child apps
2
+
3
+ Scenario: Custom 404 response from child app
4
+ Given a fixture app "complex-app"
5
+ And a file named "source/custom.html.erb" with:
6
+ """
7
+ <h2><%= 404 %> Custom Not Found!</h2>
8
+ """
9
+ And app is running with config:
10
+ """
11
+ activate :apps, not_found: "custom.html", namespace: :complex_app
12
+ """
13
+ When I go to "/child-app"
14
+ Then I should see "hello"
15
+ When I go to "/child-app/unknown"
16
+ Then the status code should be "404"
17
+ And I should see "<h2>404 Custom Not Found!</h2>"
18
+
@@ -0,0 +1,66 @@
1
+ Feature: Real world JSON API example
2
+
3
+ Scenario: Allows namespacing applications
4
+ Given a fixture app "complex-app"
5
+ And app is running with config:
6
+ """
7
+ activate :apps, namespace: "ComplexApp::SomeNamespace"
8
+ """
9
+ When I go to "/test-app?test=1"
10
+ Then the status code should be "200"
11
+ And I should see "pass"
12
+
13
+ Scenario: Allows namespacing applications via underscored module path
14
+ Given a fixture app "complex-app"
15
+ And app is running with config:
16
+ """
17
+ activate :apps, namespace: "complex_app/some_namespace"
18
+ """
19
+ When I go to "/test-app?test=1"
20
+ Then the status code should be "200"
21
+ And I should see "pass"
22
+
23
+ Scenario: Ignores modular apps that have no direct mapping
24
+ Given a fixture app "complex-app"
25
+ And app is running with config:
26
+ """
27
+ activate :apps, namespace: "other_namespacee"
28
+ """
29
+ When I go to "/test-app?test=1"
30
+ Then the status code should be "404"
31
+
32
+ Scenario: Allows specifying Application Name
33
+ Given a fixture app "complex-app"
34
+ And app is running with config:
35
+ """
36
+ activate :apps, namespace: :complex_app,
37
+ map: { child_app: "/some-other-path/abcd" }
38
+ """
39
+ When I go to "/some-other-path/abcd"
40
+ Then the status code should be "200"
41
+ And I should see "hello"
42
+
43
+ Scenario: Allows specifying URL path for application
44
+ Given a fixture app "complex-app"
45
+ And app is running with config:
46
+ """
47
+ activate :apps,
48
+ namespace: 'complex_app/some_namespace',
49
+ map: {
50
+ test_app: 'test',
51
+ awesome_api: {
52
+ url: 'api',
53
+ class: "OtherNamespace::AwesomeAPI"
54
+ }
55
+ }
56
+ """
57
+ When I go to "/test-app?test=1"
58
+ Then the status code should be "404"
59
+ When I go to "/test?test=1"
60
+ Then the status code should be "200"
61
+ And I should see "pass"
62
+ When I go to "/awesome-api/ping"
63
+ Then the status code should be "404"
64
+ When I go to "/api/ping"
65
+ Then the status code should be "200"
66
+ And I should see "pong"
@@ -0,0 +1,52 @@
1
+ Feature: Compatibility with other Extensions
2
+
3
+ # Use custom error page for `middleman-apps`:
4
+ # with Directory Indexes on - using `find_resource_by_path`
5
+ Scenario: Custom error page with Directory Indexes
6
+ Given a fixture app "simple-app"
7
+ And a file named "source/custom.html.erb" with:
8
+ """
9
+ <h2><%= 404 %> Custom Not Found!</h2>
10
+ """
11
+ And app is running with config:
12
+ """
13
+ activate :directory_indexes
14
+ activate :apps, not_found: "custom.html"
15
+ """
16
+ When I go to "/unknown-app"
17
+ Then the status code should be "404"
18
+ And I should see "<h2>404 Custom Not Found!</h2>"
19
+
20
+ # Use custom error page for `middleman-apps`:
21
+ # with Directory Indexes on - using `find_resource_by_destination_path`
22
+ Scenario: Custom error page with Directory Indexes
23
+ Given a fixture app "simple-app"
24
+ And a file named "source/custom.html.erb" with:
25
+ """
26
+ <h2><%= 404 %> Custom Not Found!</h2>
27
+ """
28
+ And app is running with config:
29
+ """
30
+ activate :directory_indexes
31
+ activate :apps, not_found: "custom/index.html"
32
+ """
33
+ When I go to "/unknown-app"
34
+ Then the status code should be "404"
35
+ And I should see "<h2>404 Custom Not Found!</h2>"
36
+
37
+ # Use custom error page for `middleman-apps`:
38
+ # with Directory Indexes on - using `find_resource_by_page_id`
39
+ Scenario: Custom error page with Directory Indexes
40
+ Given a fixture app "simple-app"
41
+ And a file named "source/custom.html.erb" with:
42
+ """
43
+ <h2><%= 404 %> Custom Not Found!</h2>
44
+ """
45
+ And app is running with config:
46
+ """
47
+ activate :directory_indexes
48
+ activate :apps, not_found: "custom"
49
+ """
50
+ When I go to "/unknown-app"
51
+ Then the status code should be "404"
52
+ And I should see "<h2>404 Custom Not Found!</h2>"
@@ -0,0 +1,53 @@
1
+ Feature: 404 error pages using Rack and MiddlemanApps
2
+
3
+ # Rack::NotFound's default response
4
+ Scenario: Default 404 response from Built app
5
+ Given a fixture app "simple-app"
6
+ And app is running
7
+ When I go to "/unknown-app"
8
+ Then the status code should be "404"
9
+ And I should see "Not found"
10
+
11
+ # Use default 404 error page for `middleman-apps`:
12
+ # Serve 404 pages from file: build/404.html
13
+ Scenario: 404 response from Built app when `404.html` exists
14
+ Given a fixture app "simple-app"
15
+ And a file named "source/404.html.erb" with:
16
+ """
17
+ <h1><%= (400+4).to_s + ' - Not Found' %></h1>
18
+ """
19
+ And app is running
20
+ When I go to "/unknown-app"
21
+ Then the status code should be "404"
22
+ And I should see "<h1>404 - Not Found</h1>"
23
+
24
+ # Use custom error page for `middleman-apps`:
25
+ Scenario: With a custom error page
26
+ Given a fixture app "simple-app"
27
+ And a file named "source/custom.html.erb" with:
28
+ """
29
+ <h2><%= 404 %> Custom Not Found!</h2>
30
+ """
31
+ And app is running with config:
32
+ """
33
+ activate :apps, not_found: "custom.html"
34
+ """
35
+ When I go to "/unknown-app"
36
+ Then the status code should be "404"
37
+ And I should see "<h2>404 Custom Not Found!</h2>"
38
+
39
+ # Use custom error page for `middleman-apps`:
40
+ # Ensure that `build/404.html` is not being used for 404 pages now.
41
+ Scenario: No default 404 with custom error page
42
+ Given a fixture app "simple-app"
43
+ And a file named "source/404.html.erb" with:
44
+ """
45
+ <h1>Not Found</h1>
46
+ """
47
+ And app is running with config:
48
+ """
49
+ activate :apps, not_found: "custom.html"
50
+ """
51
+ When I go to "/unknown-app"
52
+ Then the status code should be "404"
53
+ And I should see "Not found"
@@ -0,0 +1,8 @@
1
+ Feature: 404 error pages with Middleman Preview Server
2
+
3
+ Scenario: Fixed 404 response with Server
4
+ Given a fixture app "simple-app"
5
+ And the Server is running at "simple-app"
6
+ When I go to "/unknown-app"
7
+ Then the status code should be "404"
8
+ And I should see "<h1>File Not Found</h1>"
@@ -0,0 +1,15 @@
1
+ Given(/app is running(?: with config:)?/) do |*args|
2
+ step %(I overwrite the file named "config.rb" with:), args[0] if args.any?
3
+ step %(I run `middleman build --verbose`)
4
+ step %(was successfully built)
5
+
6
+ app = nil
7
+ path = File.expand_path(expand_path('.'))
8
+ ENV['MM_ROOT'] = path
9
+
10
+ Dir.chdir(path) do
11
+ app, = Rack::Builder.parse_file('config.ru')
12
+ end
13
+
14
+ Capybara.app = app.to_app
15
+ end
@@ -0,0 +1,18 @@
1
+ PROJECT_ROOT_PATH = File.dirname(File.dirname(File.dirname(__FILE__)))
2
+ require 'middleman-core'
3
+ require 'middleman-core/step_definitions'
4
+ require File.join(PROJECT_ROOT_PATH, 'lib', 'middleman/apps')
5
+
6
+ Before do
7
+ delete_environment_variable 'MM_ROOT'
8
+ end
9
+
10
+ module Aruba
11
+ module Platforms
12
+ # Turn off deprecation warnings from Aruba,
13
+ # atleast on my current system :)
14
+ class UnixPlatform
15
+ def deprecated(*_args); end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ Feature: Verbose mode
2
+
3
+ Scenario: Display list of child apps which were ignored
4
+ Given a fixture app "complex-app"
5
+ And I overwrite the file named "config.rb" with:
6
+ """
7
+ activate :apps, verbose: true
8
+ """
9
+ And I run `middleman build --verbose`
10
+ And the aruba exit timeout is 2 seconds
11
+ And I run `rackup -p 17283` in background
12
+ Then the output should match:
13
+ """
14
+ Ignored child app:.*apps\/awesome_api\.rb
15
+ """
16
+ And the output should match:
17
+ """
18
+ Ignored child app:.*apps\/test_app\.rb
19
+ """
20
+ And the output should match:
21
+ """
22
+ Ignored child app:.*apps\/child_app\.rb
23
+ """
@@ -0,0 +1,11 @@
1
+ require 'sinatra'
2
+
3
+ module OtherNamespace
4
+ # Child app that tests specifying a specific namespace/class
5
+ # for a single URL endpoint
6
+ class AwesomeAPI < Sinatra::Base
7
+ get '/ping' do
8
+ 'pong'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ require 'middleman/apps/base'
2
+
3
+ module ComplexApp
4
+ # Child app that inherits from Middleman::Apps::BaseApp (which in turn
5
+ # inherits from Sinatra::Base) for additional features, such as:
6
+ # - error pages will be same as your main middleman static app
7
+ # - views and public folders set appropriately
8
+ # - handly helper methods, etc.
9
+ #
10
+ class ChildApp < ::Middleman::Apps::Base
11
+ get '/' do
12
+ 'hello'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ require 'sinatra'
2
+
3
+ module ComplexApp
4
+ module SomeNamespace
5
+ # Child app that is used by option: namespace
6
+ class TestApp < Sinatra::Base
7
+ get '/' do
8
+ params[:test] ? 'pass' : 'fail'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1 @@
1
+ <h1>Error!!</h1>
@@ -0,0 +1,2 @@
1
+ <h1>Middleman</h1>
2
+
@@ -0,0 +1 @@
1
+ activate :apps # , not_found: 'error.html'
@@ -0,0 +1,2 @@
1
+ <h1>Middleman</h1>
2
+
@@ -0,0 +1,8 @@
1
+ require 'sinatra'
2
+
3
+ # Test application fixture
4
+ class TestApp < Sinatra::Base
5
+ get '/' do
6
+ params[:test] ? 'pass' : 'fail'
7
+ end
8
+ end
@@ -0,0 +1 @@
1
+ <h1>Error!!</h1>
@@ -0,0 +1,2 @@
1
+ <h1>Middleman</h1>
2
+
@@ -0,0 +1 @@
1
+ activate :apps # , not_found: 'error.html'
@@ -0,0 +1,2 @@
1
+ <h1>Middleman</h1>
2
+
@@ -0,0 +1 @@
1
+ require 'middleman/apps'
@@ -0,0 +1,65 @@
1
+ require 'middleman-core'
2
+
3
+ # Load extension nonetheless, as child apps may/will require this file.
4
+ require 'middleman/apps/extension'
5
+
6
+ # Register this extension with the name of `apps`
7
+ Middleman::Extensions.register :apps, Middleman::Apps::Extension
8
+
9
+ module Middleman
10
+ # Base namespace for `middleman-apps` extension.
11
+ #
12
+ module Apps
13
+ # Environment in which MM should be run
14
+ ENVIRONMENT = (ENV['MM_ENV'] || ENV['RACK_ENV'] || 'development').to_sym
15
+
16
+ # Middleman options that would be passed to create a reference instance.
17
+ MIDDLEMAN_OPTIONS = {
18
+ mode: :config,
19
+ watcher_disable: true,
20
+ exit_before_ready: true,
21
+ environment: ENVIRONMENT
22
+ }.freeze
23
+
24
+ # Middleman app instance for reference to configuration, etc.
25
+ #
26
+ # @return [Middleman::Application] an instance of {Middleman::Application}
27
+ # using configuration in {MIDDLEMAN_OPTIONS}
28
+ #
29
+ def self.middleman_app
30
+ Middleman.setup_load_paths
31
+
32
+ ::Middleman::Application.new do
33
+ MIDDLEMAN_OPTIONS.each do |key, val|
34
+ config[key] = val
35
+ end
36
+ end
37
+ end
38
+
39
+ # Evaluate some code within the context of this extension.
40
+ #
41
+ # @param [Proc] block block to be executed
42
+ # @return [Any] - result of execution of the provided block
43
+ #
44
+ # @see .rack_app `.rack_app` method which uses this internally
45
+ #
46
+ def self.within_extension(&block)
47
+ app = middleman_app
48
+ options = app.extensions[:apps].options.to_h
49
+ Middleman::Apps::Extension.new(app, options).instance_eval(&block)
50
+ end
51
+
52
+ # Rack app comprising of the static (middleman) app with 404 pages, and
53
+ # child apps properly mounted.
54
+ #
55
+ # This method can be used directly to create a Rack app. Refer to the
56
+ # generated `config.ru` for an example.
57
+ #
58
+ # @return [Rack::App] rack application configuration
59
+ def self.rack_app
60
+ within_extension do
61
+ mount_child_apps(middleman_static_app)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,36 @@
1
+ require 'sinatra'
2
+
3
+ module Middleman
4
+ module Apps
5
+ # Base application class for creating child applications.
6
+ #
7
+ # Inheriting from this class should provide better syncronization with the
8
+ # static middleman app.
9
+ #
10
+ class Base < ::Sinatra::Base
11
+ # set :static, true
12
+
13
+ # set :mm_root, File.dirname(File.dirname(__FILE__))
14
+ # set :mm_dir, settings.development? ? 'source' : 'build'
15
+ # set :public_folder, File.join(settings.mm_root, settings.mm_dir)
16
+ # set :views, settings.public_folder
17
+
18
+ not_found do
19
+ send_file path_for_not_found, status: 404
20
+ end
21
+
22
+ protected
23
+
24
+ def middleman_app
25
+ Middleman::Apps.middleman_app
26
+ end
27
+
28
+ def path_for_not_found
29
+ Middleman::Apps.within_extension do
30
+ path = find_resource(options.not_found)
31
+ app.root_path.join(app.config.build_dir, path).to_s
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,231 @@
1
+ require 'middleman-core'
2
+ require 'middleman-core/load_paths'
3
+
4
+ module Middleman
5
+ module Apps
6
+ # A Middleman extension to serve Rack applications (e.g. created via
7
+ # Sinatra) or other such apps when previewing using Middleman Preview
8
+ # server, as well as in the production (build) mode by creating an umbrella
9
+ # Rack app.
10
+ #
11
+ # Usage examples can be seen in README for this extension.
12
+ #
13
+ class Extension < ::Middleman::Extension
14
+
15
+ # @!group Options for Extension
16
+
17
+ # @!macro [attach] option
18
+ # @!method $1(value)
19
+ # Extension Option - $3 - Default: $2
20
+ # @param value value for this option - Default: `$2`
21
+ option :not_found, '404.html', 'Path to 404 error page'
22
+ option :namespace, nil, 'Namespace for the child apps'
23
+ option :map, {}, 'Mappings for differently named child apps'
24
+ option :verbose, false, 'Displays list of child apps that were ignored'
25
+
26
+ # @!endgroup
27
+
28
+ def initialize(app, options_hash = {}, &block)
29
+ super
30
+ # useful for converting file names to ruby classes
31
+ require 'active_support/core_ext/string/inflections'
32
+ end
33
+
34
+ # Mount all child apps on a specific Rack app (or current app)
35
+ #
36
+ # @param [Rack::App] rack_app app on which to mount child apps
37
+ # Default: app from MM configuration
38
+ #
39
+ # @return [Rack::App] rack_app with child apps mounted on top
40
+ #
41
+ def mount_child_apps(rack_app = nil)
42
+ rack_app ||= app
43
+ child_apps.each do |url, klass|
44
+ rack_app.map(url) { run klass }
45
+ end
46
+ rack_app
47
+ end
48
+
49
+ # Get a hash of all child applications URLs paths matched to corresponding
50
+ # Ruby classes.
51
+ #
52
+ # Warning is raised (if `verbose` option is `true`) when a child app was
53
+ # found, but could not be mapped due to the specified config.
54
+ #
55
+ # @return [Hash] - child application URL vs Ruby class
56
+ #
57
+ def child_apps
58
+ apps_list.map do |mapp|
59
+ require mapp
60
+ klass = get_application_class_for(mapp)
61
+ warn "Ignored child app: #{mapp}" unless klass
62
+ [get_application_url_for(mapp), klass] if klass
63
+ end.compact.to_h
64
+ end
65
+
66
+ # Get a Rack::App that can serve the MM app's build directory.
67
+ #
68
+ # Directory paths, and 404 error page are deduced from extensions' options.
69
+ #
70
+ # @return [Rack::App] Rack::TryStatic app for MM app's build directory.
71
+ #
72
+ def middleman_static_app
73
+ not_found = options.not_found
74
+ return create_static_app(root) unless not_found
75
+
76
+ not_found_path = File.join(build_dir, find_resource(not_found))
77
+ create_static_app build_dir, not_found_path
78
+ end
79
+
80
+ # Get a list of all child apps that are found in `MM_ROOT/apps` directory.
81
+ #
82
+ def apps_list
83
+ pattern = File.join(app.root, 'apps', '*.rb')
84
+ Dir[pattern].map do |file|
85
+ File.realpath(file) if File.file?(file)
86
+ end.compact
87
+ end
88
+
89
+ # Run `after_configuration` hook passed on by MM
90
+ #
91
+ # After configuration for middleman has been finalized,
92
+ # create a `config.ru` in the root directory, and mount all child
93
+ # apps, if we are on a preview server.
94
+ #
95
+ # @return [nil]
96
+ #
97
+ # @private
98
+ # @api private
99
+ #
100
+ def after_configuration
101
+ create_config_ru
102
+ return unless app.server?
103
+ mount_child_apps(app)
104
+ end
105
+
106
+ # Create a `config.ru` file, if one does not exist, yet.
107
+ #
108
+ # This is done whenever `middleman` cli is run for building, or previewing
109
+ # the static app.
110
+ #
111
+ # @return [nil]
112
+ #
113
+ # @private
114
+ # @api private
115
+ #
116
+ def create_config_ru
117
+ path = File.join(app.root, 'config.ru')
118
+ return if File.exist?(path)
119
+
120
+ content = <<-CONTENT.gsub(/^ {6}/, '')
121
+ ENV['RACK_ENV'] = 'production'
122
+ require 'middleman/apps'
123
+ run Middleman::Apps.rack_app
124
+ CONTENT
125
+
126
+ File.open(path, 'wb') { |file| file.puts content }
127
+ end
128
+
129
+ # Create a Rack::TryStatic application for the given directory root.
130
+ #
131
+ # @param [String] root - path to directory root
132
+ # @param [String] path - path to not found error page
133
+ # If not provided, default 404 response from Rack
134
+ # is served.
135
+ #
136
+ # @return [Rack::App] static app for the `root` directory
137
+ #
138
+ # @api private
139
+ #
140
+ def create_static_app(root, path = nil)
141
+ unless File.exist?(path)
142
+ warn("Could not find: #{path}")
143
+ path = nil
144
+ end
145
+
146
+ # require 'rack/contrib'
147
+ require 'middleman/apps/rack_contrib'
148
+ ::Rack::Builder.new do
149
+ use ::Rack::TryStatic, urls: ['/'], root: root,
150
+ try: ['.html', 'index.html', '/index.html']
151
+ run ::Rack::NotFound.new(path)
152
+ end
153
+ end
154
+
155
+ # Find a resource given its path, destination path, or page_id.
156
+ #
157
+ # @param [String] name - identifier for this resource
158
+ # @return [String] relative path to resource
159
+ #
160
+ # @api private
161
+ #
162
+ def find_resource(name)
163
+ sitemap = app.sitemap
164
+ resource = sitemap.find_resource_by_path(name)
165
+ resource ||= sitemap.find_resource_by_destination_path(name)
166
+ resource ||= sitemap.find_resource_by_page_id(name)
167
+ resource ? resource.destination_path : name
168
+ end
169
+
170
+ private
171
+
172
+ # Warn user about message if `verbose` option is on.
173
+ #
174
+ # @param [String] message - message to display
175
+ #
176
+ # @private
177
+ # @api private
178
+ #
179
+ def warn(message)
180
+ logger.warn(message) if logger && options.verbose
181
+ end
182
+
183
+ # Get path to MM's build dir.
184
+ #
185
+ # @return [String] path to build dir
186
+ #
187
+ def build_dir
188
+ File.expand_path(app.config.build_dir.to_s)
189
+ end
190
+
191
+ # Convert options data to a hash for easy searches.
192
+ #
193
+ # @api private
194
+ # @return [Hash] options data
195
+ #
196
+ def mappings
197
+ options.map.map { |key, val| [key.to_s, val] }.to_h
198
+ end
199
+
200
+ # Get URL at which given child app should be mounted.
201
+ #
202
+ # @api private
203
+ # @param [String] file - path to child app
204
+ # @return [String] url component for the child app
205
+ #
206
+ def get_application_url_for(file)
207
+ name = File.basename(file, '.rb')
208
+ url = mappings[name]
209
+ url = url[:url] if url.is_a?(Hash)
210
+ '/' + (url ? url.to_s.gsub(%r{^\/}, '') : name.titleize.parameterize)
211
+ end
212
+
213
+ # Get Application Class for the child app.
214
+ #
215
+ # @api private
216
+ # @param [String] file - path to child app
217
+ # @return [Class, nil] Class for the child app, if exists.
218
+ #
219
+ def get_application_class_for(file)
220
+ name = File.basename(file, '.rb')
221
+ namespace = options.namespace
222
+
223
+ klass = mappings[name][:class] if mappings[name].is_a?(Hash)
224
+ klass ||= namespace ? "#{namespace}/#{name}" : name
225
+ klass.to_s.classify.constantize
226
+ rescue NameError
227
+ return nil
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,77 @@
1
+ # `rack-contrib` depends on `rack` v1.4, which is quite outdated now, and also,
2
+ # limits us to Sinatra v1.4, while Sinatra v2.0 is out.
3
+ #
4
+ # Since, `middleman-apps` uses 2 tiny classes from `rack-contrib`, I copied them
5
+ # here to remove `rack-contrib` from dependency list.
6
+ #
7
+ # Once `rack-contrib` supports `rack` v2.0, we can switch back to using it,
8
+ # instead of this file.
9
+ #
10
+ # @todo
11
+ # [MAYBE] Merge the two Rack apps below into a single concise Rack app?
12
+ module ::Rack
13
+
14
+ # The Rack::TryStatic middleware delegates requests to Rack::Static middleware
15
+ # trying to match a static file
16
+ #
17
+ # Examples
18
+ #
19
+ # use Rack::TryStatic,
20
+ # :root => "public", # static files root dir
21
+ # :urls => %w[/], # match all requests
22
+ # :try => ['.html', 'index.html', '/index.html'] # try these postfixes sequentially
23
+ #
24
+ # uses same options as Rack::Static with extra :try option which is an array
25
+ # of postfixes to find desired file
26
+
27
+ class TryStatic
28
+
29
+ def initialize(app, options)
30
+ @app = app
31
+ @try = ['', *options[:try]]
32
+ @static = ::Rack::Static.new(
33
+ lambda { |_| [404, {}, []] },
34
+ options)
35
+ end
36
+
37
+ def call(env)
38
+ orig_path = env['PATH_INFO']
39
+ found = nil
40
+ @try.each do |path|
41
+ resp = @static.call(env.merge!({'PATH_INFO' => orig_path + path}))
42
+ break if !(403..405).include?(resp[0]) && found = resp
43
+ end
44
+ found or @app.call(env.merge!('PATH_INFO' => orig_path))
45
+ end
46
+ end
47
+
48
+ # Rack::NotFound is a default endpoint. Optionally initialize with the
49
+ # path to a custom 404 page, to override the standard response body.
50
+ #
51
+ # Examples:
52
+ #
53
+ # Serve default 404 response:
54
+ # run Rack::NotFound.new
55
+ #
56
+ # Serve a custom 404 page:
57
+ # run Rack::NotFound.new('path/to/your/404.html')
58
+
59
+ class NotFound
60
+ F = ::File
61
+
62
+ def initialize(path = nil, content_type = 'text/html')
63
+ if path.nil?
64
+ @content = "Not found\n"
65
+ else
66
+ @content = F.read(path)
67
+ end
68
+ @length = @content.size.to_s
69
+
70
+ @content_type = content_type
71
+ end
72
+
73
+ def call(env)
74
+ [404, {'Content-Type' => @content_type, 'Content-Length' => @length}, [@content]]
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,5 @@
1
+ module Middleman
2
+ module Apps
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+ require 'middleman/apps/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'middleman-apps'
6
+ s.version = Middleman::Apps::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ['Nikhil Gupta']
9
+ s.email = ['me@nikhgupta.com']
10
+ s.homepage = 'https://github.com/nikhgupta/middleman-apps'
11
+ s.summary = 'Middleman extension to run dynamic pages using Sinatra'
12
+ s.description = 'Middleman extension to run dynamic pages using Sinatra'
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.require_paths = ['lib']
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map do |f|
18
+ File.basename(f)
19
+ end
20
+
21
+ s.add_runtime_dependency('middleman-core', ['~> 4.2'])
22
+ s.add_runtime_dependency('sinatra', ['~> 2.0'])
23
+ s.add_runtime_dependency('activesupport', ['>= 4.2'])
24
+ # s.add_runtime_dependency('rack-contrib', ['>= 1.7.0'])
25
+
26
+ s.add_development_dependency('middleman-cli')
27
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: middleman-apps
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nikhil Gupta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: middleman-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '4.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '4.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: middleman-cli
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Middleman extension to run dynamic pages using Sinatra
70
+ email:
71
+ - me@nikhgupta.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rubocop.yml"
78
+ - ".travis.yml"
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - features/activation.feature
84
+ - features/build.feature
85
+ - features/child_app.feature
86
+ - features/complex_app.feature
87
+ - features/directory_indexes.feature
88
+ - features/not_found_rack.feature
89
+ - features/not_found_server.feature
90
+ - features/step_definitions/rack_app_steps.rb
91
+ - features/support/env.rb
92
+ - features/verbose.feature
93
+ - fixtures/complex-app/apps/awesome_api.rb
94
+ - fixtures/complex-app/apps/child_app.rb
95
+ - fixtures/complex-app/apps/test_app.rb
96
+ - fixtures/complex-app/build/error.html
97
+ - fixtures/complex-app/build/index.html
98
+ - fixtures/complex-app/config.rb
99
+ - fixtures/complex-app/source/index.html.erb
100
+ - fixtures/simple-app/apps/test_app.rb
101
+ - fixtures/simple-app/build/error.html
102
+ - fixtures/simple-app/build/index.html
103
+ - fixtures/simple-app/config.rb
104
+ - fixtures/simple-app/source/index.html.erb
105
+ - lib/middleman.rb
106
+ - lib/middleman/apps.rb
107
+ - lib/middleman/apps/base.rb
108
+ - lib/middleman/apps/extension.rb
109
+ - lib/middleman/apps/rack_contrib.rb
110
+ - lib/middleman/apps/version.rb
111
+ - middleman-apps.gemspec
112
+ homepage: https://github.com/nikhgupta/middleman-apps
113
+ licenses: []
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.6.10
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Middleman extension to run dynamic pages using Sinatra
135
+ test_files:
136
+ - features/activation.feature
137
+ - features/build.feature
138
+ - features/child_app.feature
139
+ - features/complex_app.feature
140
+ - features/directory_indexes.feature
141
+ - features/not_found_rack.feature
142
+ - features/not_found_server.feature
143
+ - features/step_definitions/rack_app_steps.rb
144
+ - features/support/env.rb
145
+ - features/verbose.feature