middleman-apps 0.1.0

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.
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