Exports all your Sinatra application routes to static files in your public folder.
# Gemfile
gem 'sinatra-export'
# Rakefile
APP_FILE = 'app.rb'
APP_CLASS = 'Sinatra::Application'
require 'sinatra/export/rake'
Sample Sinatra application building static pages :
require 'sinatra'
require 'sinatra/export'
get '/' do
"<h1>My homepage</h1>"
end
get '/contact' do
"<h1>My contact page<h1>"
end
get '/data.json' do
"{test: 'ok'}"
end
Running your app ex. rake sinatra:export
will automatically generate theses files :
public/index.html -> "<h1>My homepage</h1>"
public/contact/index.html -> "<h1>My contact page<h1>"
public/data.json -> "{test: 'ok'}"
$ rake sinatra:export
Or invoke it manually within ruby code :
Sinatra::Application.export!
If you wish to specify specific paths to be visited (only):
Sinatra::Application.export! paths: ["/", "/contact"]
Only the homepage and the contact page would be visited (these would be visited anyway, but lets start off simple!) If you wanted the paths you specify and any paths that Sinatra::AdvancedRoutes can find then you could use:
Sinatra::Application.export! paths: ["/", "/contact"], use_routes: true
Now all the routes listed above would be found. But what if you have some routes with wildcards or named captures?
get '/articles/:slug' do
# an article is retrieved via params["slug"]
# but we'll stub one in for this example:
markdown("# I have wonderful news! #\n\nYou can use wildcard routes now.\n")
end
You could access that route as well via:
Sinatra::Application.export! paths: ["/articles/i-have-wonderful-news"], use_routes: true
Perhaps you would like a static 404 page.
not_found do
halt 404, haml(:not_found)
end
By default, Sinatra Export will only use routes that return an HTTP status code of 200. If you want non 200 pages then supply the path with the expected status in an array, for example:
Sinatra::Application.export! paths: ["/articles/i-have-wonderful-news",["/404.html",400]], use_routes: true
Among the static files output you will find 404.html.
If you want to ignore certain pages no matter what, supply them via the skip
keyword in a list:
Sinatra::Application.export! skips: ["/contact","/data.json"]
Only the "/" route will be output. This will work with supplied paths or routes found via use_routes
.
By default, Sinatra Export will place the generated static files into the Sinatra app's public folder. If you want to put them somewhere else then you can use the EXPORT_BUILD_DIR
environment variable. For example:
ENV["EXPORT_BUILD_DIR"] = File.join ENV["HOME"], "projects/static"
Sinatra::Application.export!
The files would be in "~/projects/static"
By default, Sinatra Export will skip routes that are non 200 status unless you supply the expected status for a page. When it hits an unexpected status it will output an error in red text to the terminal and continue processing. If you want to change this, you can supply your own error handler. For example, to stop processing when you hit an unexpected status code:
Sinatra::Application.export! paths: ["/this-path-doesnt-exist"], error_handler: ->(desc){ fail "Didn't expect that! #{desc}" }
All that's needed is something that responds to call
- so a proc, block or lambda - that takes 1 argument, a description string of the error.
export!
can take a block that will be run for every page that is processed. Inside the block, and instance of the Builder
class (the one that does all the work, see the API docs via rake yard
for more) will be accessible. For example, let's add a path during the processing:
get '/this-route-has-an-internal-link' do
"<a href='/articles/i-have-wonderful-news'>Follow this link!</a>"
end
Now to find that link:
require 'hpricot' # nokogiri is available too
Sinatra::Application.export! do |builder|
doc = Hpricot(builder.last_response.body)
(doc/"a").map{|elem| URI( elem.attributes["href"] ) }
.map(&:path).each do |path|
builder.paths.push path unless builder.paths.include? path
end
end
You'd probably want to check the links weren't external too.
Note! If you know something about arrays and some of the set like methods available then you'll think that the last block given to each could've been made shorter by using |=
instead of push
with unless
. Be warned that under the hood the Builder is using an Enumerator to check each of the paths
, and by using |=
the paths will somehow become disassociated with the enumerator and your work will be in vain!
There's other stuff you could do in that block, the builder gives you access to paths
, skips
(both read/write); visited
(a list of the paths visited so far), errored
(a list of the paths that have called the error handler), the last_path
(which inside the block will be the current path) and the last_response
, so you can access things like the last_response.status
and last_response.body
.
Another example, filtering while processing:
Sinatra::Application.export! do |builder|
# set it using an array because Rack::Response#body is actually
# an array that is joined to output a string
builder.last_response.body = [builder.last_response.body.upcase!]
end
Now all the output would be upcased. There is more on filtering below, but as you can see, you can process things on the fly.
If you want to apply a filter to every path that is written then you can supply those via the filters
keyword:
require 'hpricot' # nokogiri is available too
Sinatra::Application.export! filters: [->(text){ text.upcase }]
That would upcase everything. If you wanted you could do things like remove mentions of "localhost" or whatever.
require 'hpricot' # nokogiri is available too
Sinatra::Application.export! filters: [->(text){ text.gsub("localhost", "example.org" }, ->(text){ text.gsub("http://", "https://" }]
filter
takes an array, each item should respond to call
and take 1 argument, the text to be filtered. Each filter will be applied in the order of the array.
See CONTRIBUTING.md for more details on contributing and running test.
sinatra-export is maintained and funded by hooktstudios