hooktstudios / sinatra-export

Export your Sinatra application to static files (html, csv, txt, etc)
https://rubygems.org/gems/sinatra-export
Other
22 stars 6 forks source link

sinatra-export

Exports all your Sinatra application routes to static files in your public folder.

Build Status Dependency Status Code Climate Gem Version

Installation

# Gemfile
gem 'sinatra-export'
# Rakefile
APP_FILE  = 'app.rb'
APP_CLASS = 'Sinatra::Application'

require 'sinatra/export/rake'

Quick Start

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'}"

Usage

$ rake sinatra:export

Or invoke it manually within ruby code :

Sinatra::Application.export!

Advanced usage

Supplied paths

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

Supplying statuses

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.

Skipping pages

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.

Non standard directory for output

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"

Super advanced usage

Error handling

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.

Supplying a process block

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.

Filtering

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.

Other resources

Contributing

See CONTRIBUTING.md for more details on contributing and running test.

Credits

hooktstudios

sinatra-export is maintained and funded by hooktstudios