├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── autotest └── discover.rb ├── bin └── frank ├── frank-1.0.12.gem ├── frank.gemspec ├── lib ├── frank.rb ├── frank │ ├── base.rb │ ├── cli.rb │ ├── compile.rb │ ├── lorem.rb │ ├── middleware │ │ ├── imager.rb │ │ ├── refresh.rb │ │ └── statik.rb │ ├── publish.rb │ ├── publish │ │ ├── base.rb │ │ ├── ftp.rb │ │ ├── ftptls.rb │ │ ├── scp.rb │ │ ├── sftp.rb │ │ └── shell_scp.rb │ ├── rescue.rb │ ├── settings.rb │ ├── template_helpers.rb │ ├── templates │ │ ├── 404.haml │ │ ├── 500.haml │ │ ├── frank-404.png │ │ ├── frank-500.png │ │ └── imager │ │ │ ├── frank0.jpg │ │ │ ├── frank1.jpg │ │ │ ├── frank2.jpg │ │ │ ├── frank3.jpg │ │ │ ├── frank4.jpg │ │ │ ├── frank5.jpg │ │ │ ├── frank6.jpg │ │ │ ├── frank7.jpg │ │ │ ├── frank8.jpg │ │ │ └── frank9.jpg │ ├── tilt_setup.rb │ ├── upgrades.rb │ └── version.rb └── template │ ├── dynamic │ ├── css │ │ └── frank.sass │ └── index.haml │ ├── helpers.rb │ ├── layouts │ └── default.haml │ ├── setup.rb │ └── static │ ├── favicon.ico │ ├── images │ └── frank-med.png │ └── js │ └── frank.js └── spec ├── base_spec.rb ├── compile_spec.rb ├── publish ├── base_spec.rb ├── ftp_spec.rb ├── ftptls_spec.rb ├── scp_spec.rb └── sftp_spec.rb ├── publish_spec.rb ├── render_spec.rb ├── spec_helper.rb ├── template ├── dynamic │ ├── 500.haml │ ├── _partial.haml │ ├── _partial_with_locals.haml │ ├── builder.builder │ ├── coffee.coffee │ ├── content_for_erb.erb │ ├── content_for_haml.haml │ ├── erb.erb │ ├── helper_test.haml │ ├── index.haml │ ├── layout2_test.haml │ ├── liquid.liquid │ ├── lorem_test.haml │ ├── markdown.md │ ├── markdown_in_haml.md │ ├── nested │ │ ├── child.haml │ │ └── deeper │ │ │ └── deep.haml │ ├── no_layout.haml │ ├── partial_locals_test.haml │ ├── partial_test.haml │ ├── redcloth.textile │ ├── refresh.haml │ ├── setting_in_layout.haml │ └── stylesheets │ │ ├── less.less │ │ ├── sass.sass │ │ ├── sass_with_compass.sass │ │ └── scss_with_compass.scss ├── helpers.rb ├── layouts │ ├── default.haml │ ├── explicit │ │ └── layout2.haml │ └── nested │ │ └── default.haml ├── setup.rb └── static │ └── files │ └── static.html └── template_helpers_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | pkg 3 | lib/frank/output_old.rb 4 | .rvmrc 5 | .sass-cache 6 | .bundle 7 | spec/template/exported 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Dependencies are in `frank.gemspec' 4 | gemspec -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | frank (1.0.11) 5 | haml (~> 3.0) 6 | mongrel (~> 1.2.0.pre2) 7 | net-ssh (~> 2.0) 8 | rack (~> 1.1) 9 | tilt (~> 1.3) 10 | 11 | GEM 12 | remote: http://rubygems.org/ 13 | specs: 14 | RedCloth (4.2.3) 15 | abstract (1.0.0) 16 | builder (2.1.2) 17 | coffee-script (2.2.0) 18 | coffee-script-source 19 | execjs 20 | coffee-script-source (1.1.2) 21 | compass (0.10.2) 22 | haml (>= 3.0.4) 23 | daemons (1.0.10) 24 | diff-lcs (1.1.2) 25 | erubis (2.6.6) 26 | abstract (>= 1.0.0) 27 | execjs (1.2.4) 28 | multi_json (~> 1.0) 29 | gem_plugin (0.2.3) 30 | haml (3.0.13) 31 | less (1.2.21) 32 | mutter (>= 0.4.2) 33 | treetop (>= 1.4.2) 34 | liquid (2.1.2) 35 | metaclass (0.0.1) 36 | mocha (0.10.5) 37 | metaclass (~> 0.0.1) 38 | mongrel (1.2.0.pre2) 39 | daemons (~> 1.0.10) 40 | gem_plugin (~> 0.2.3) 41 | multi_json (1.0.3) 42 | mutter (0.5.3) 43 | net-scp (1.0.4) 44 | net-ssh (>= 1.99.1) 45 | net-sftp (2.0.5) 46 | net-ssh (>= 2.0.9) 47 | net-ssh (2.3.0) 48 | polyglot (0.3.1) 49 | rack (1.2.1) 50 | rack-test (0.5.4) 51 | rack (>= 1.0) 52 | rake (0.8.7) 53 | rdiscount (1.6.5) 54 | rspec (2.6.0) 55 | rspec-core (~> 2.6.0) 56 | rspec-expectations (~> 2.6.0) 57 | rspec-mocks (~> 2.6.0) 58 | rspec-core (2.6.4) 59 | rspec-expectations (2.6.0) 60 | diff-lcs (~> 1.1.2) 61 | rspec-mocks (2.6.0) 62 | tilt (1.3.3) 63 | treetop (1.4.8) 64 | polyglot (>= 0.3.1) 65 | 66 | PLATFORMS 67 | ruby 68 | 69 | DEPENDENCIES 70 | RedCloth 71 | builder 72 | coffee-script (~> 2.2.0) 73 | compass (~> 0.10.2) 74 | erubis 75 | frank! 76 | less 77 | liquid 78 | mocha 79 | net-scp 80 | net-sftp 81 | rack-test (~> 0.5) 82 | rake 83 | rdiscount 84 | rspec (~> 2.6.0) 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Travis Dunn 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Frank 2 | ========= 3 | 4 | Inspired by [Sinatra][0]'s simplicity and ease of use, Frank lets you build 5 | static sites using your favorite libs. Frank has a built in development server 6 | for previewing work as you develop, an "export" command for compiling and saving 7 | your work out to static html and css, and a publish command for copying your 8 | exported pages to a server. 9 | 10 | Frank uses [Tilt][1], so it 11 | comes with support for [Haml & Sass][2], [LESS][10], [Builder][3], [ERB][4], and 12 | [Liquid][5]. 13 | 14 | Overview 15 | -------- 16 | 17 | Create a new project with: 18 | 19 | $ frank new 20 | 21 | Then `cd ` and start up the server with: 22 | 23 | $ frank server 24 | 25 | ----------------------- 26 | Frank's holdin' it down... 27 | 0.0.0.0:3601 28 | 29 | And you're ready to get to work. By default, dynamic templates are served from the `dynamic` folder, 30 | static files are served from the `static` folder, and layouts are served from the `layouts` folder. 31 | 32 | When you're done working: 33 | 34 | $ frank export 35 | 36 | to compile templates and copy them--along with static your assets--into `` (or to `export/` if you don't specify an ``). 37 | 38 | Or, 39 | 40 | $ frank export --production 41 | 42 | to compile & copy over, but organized to work as a static website in production. (e.g. folders named after your views, with an `index.html` inside) 43 | 44 | You can add publish settings in setup.rb and publish directly to a server via scp. 45 | 46 | $ frank publish 47 | 48 | Upgrading 49 | ------------------------- 50 | 51 | As of version 0.4, Frank no longer uses settings.yml. However you can use `frank upgrade` in order convert your old settings.yml to the new setup.rb format. 52 | 53 | 54 | Frank Templates 55 | ------------------------- 56 | 57 | Frank (as of 1.0) has support for saving "templates" in `~/.frank_templates`. This is very handy if find yourself wanting a custom starting point. All you have to do to use the feature is create a `~/.frank_templates` folder and start putting templates in it. 58 | 59 | Once you have a few templates saved, when you run `frank new ` you'll be presented with a list of templates to choose from as the starting point for the project. 60 | 61 | Views & Meta Data 62 | ------------------------- 63 | 64 | All of your templates, less, sass &c. go into `/dynamic` by default. 65 | You can organize them into subfolders if you've got lots. 66 | 67 | ### Views 68 | 69 | Writing views is simple. Say you've got a `blog.haml` in `/dynamic`; just browse over to 70 | `http://0.0.0.0:3601/blog` and your view will be parsed and served up as html. 71 | 72 | 73 | ### Meta Data 74 | 75 | Frank was designed to make controllers unnecessary. But, sometimes it's nice to have 76 | variables in your templates / layouts. This is particularly handy if you want to set the page 77 | title (in the layout) according to the view. This is simple, now, with meta data. 78 | 79 | Meta fields go at the top of any view, and are written in [YAML][13]. To mark the end 80 | of the meta section, place the meta delimeter, `META---`, on a blank line. You can 81 | use as many hyphens as you'd like (as long as there are 3). 82 | 83 | Meta fields are then available as local variables to all templating languages that 84 | support them--in the view & layout: 85 | 86 | view: 87 | title: My Rad Page 88 | author: My Rad Self 89 | ---------------------------------------------META 90 | 91 | %h1= title 92 | %h3= 'By ' + author 93 | 94 | layout: 95 | %title= title + '--My Rad Site' 96 | 97 | 98 | 99 | Layouts (updated in 0.3) 100 | ----------------------------- 101 | 102 | Layouts are also simple with Frank. Create a `default.haml` (or `.rhtml`, etc.), 103 | in the `layouts` folder, and include a `yield` somewhere therein; views using the 104 | layout will be inserted there. 105 | 106 | ### Namespacing Layouts 107 | You can namespace your layouts using folders: 108 | 109 | * When rendering a view--`dynamic_folder/blog/a-blog-post.haml`, 110 | * Frank would first look for the layout `layouts/blog/default.haml`, 111 | * and if not found use fall back on `layouts/default.haml` 112 | 113 | ### Multiple/No Layouts 114 | 115 | Frank also supports choosing layouts on a view-by-view basis via meta data. Just add a 116 | `layout` meta field: 117 | 118 | layout: my_layout.haml 119 | ---------------------------------------------META 120 | %h1 I'm using my_layout.haml instead of default.haml! 121 | 122 | or if you don't want a layout at all: 123 | 124 | layout: nil 125 | ---------------------------------------------META 126 | %h1 No layout here! 127 | 128 | 129 | 130 | Partials & Helpers 131 | ------------------ 132 | 133 | Frank comes with a helper method, `render_partial`, for including partials 134 | in your views. 135 | 136 | You can also add your own helper methods easily. 137 | 138 | ### Partials 139 | 140 | To create a partial, make a new file like any of your other views, but 141 | prefix its name with an underscore. 142 | 143 | For example, if I have a partial named `_footer.haml`, I can include this 144 | in my Haml views like this: 145 | 146 | = render_partial 'footer' 147 | 148 | You can also send local variables to partials like this: 149 | 150 | = render_partial 'footer', :local_variable_name => 'some_value' 151 | 152 | ### Helpers 153 | 154 | Helper methods are also easy. Just open up `helpers.rb` and add your methods 155 | to the `FrankHelpers` module; that's it. Use them just like `render_partial`. 156 | 157 | 158 | 159 | Built-in Helpers 160 | ---------------- 161 | 162 | ### Auto-Refresh 163 | 164 | Frank has a handy automatic page refreshing helper. Just include `= refresh` 165 | (or equivalent) in your view, and Frank will automatically refresh the page for you whenever you 166 | save a project file. This eliminates the tedium of hundreds of manual refreshes over the course 167 | of building a project. 168 | 169 | When it's time export with `frank export`, Frank will leave out the JavasScript bits of the refresher. 170 | 171 | ### Current Path 172 | 173 | Frank now has a `current_path` variable that you can use to set selected states on nav items. 174 | It will return the path info from the template being processed. You also, have access to the variable from layouts and from the `frank export` command. 175 | 176 | ### Placeholder Text 177 | 178 | You can easily generate dummy text like so: 179 | 180 | %p= lorem.sentences 3 181 | 182 | This will return 3 sentences of standard [Lorem Ipsum][11]. `lorem` also has all of the following methods for generating dummy text: 183 | 184 | lorem.sentence # returns a single sentence 185 | lorem.words 5 # returns 5 individual words 186 | lorem.word 187 | lorem.paragraphs 10 188 | lorem.paragraph 189 | lorem.date # accepts a strftime format argument 190 | lorem.name 191 | lorem.first_name 192 | lorem.last_name 193 | lorem.email 194 | 195 | 196 | ### Placeholder Images 197 | 198 | Frank now uses [placehold.it][14] for placeholder images, the `lorem.image` helper supports background_color, color, random_color, and text options: 199 | 200 | lorem.image('300x400') #=> http://placehold.it/300x400 201 | lorem.image('300x400', :background_color => '333', :color => 'fff') #=> http://placehold.it/300x400/333/fff 202 | lorem.image('300x400', :random_color => true) #=> http://placehold.it/300x400/f47av7/9fbc34d 203 | lorem.image('300x400', :text => 'blah') #=> http://placehold.it/300x400&text=blah 204 | 205 | ### Replacement Text 206 | 207 | All of the lorem helpers accept an optional "replacement" argument. This will be the text rendered when you `frank export`. 208 | 209 | For example `lorem.sentence("<%= page.content %>")` will generate a lorem sentence when you view the page using the `frank server` for development. 210 | However, when you `frank export` the template will render "<%= page.content %>". This is useful if you plan on moving a frank project 211 | into a framework. (e.g. rails, sinatra, django, etc) 212 | 213 | 214 | Configuration 215 | ------------- 216 | 217 | In `setup.rb`, you can change your folder names, and server port & host name. 218 | Check the comments there if you need help. 219 | 220 | Installation 221 | ------------ 222 | 223 | $ gem install frank 224 | 225 | 226 | Contributors (in no particular order) 227 | ------------------------------------- 228 | 229 | * railsjedi (Jacques Crocker) 230 | * asymmetric (Lorenzo Manacorda) 231 | * timmywil (timmywil) 232 | * sce (Sten Christoffer Eliesen) 233 | * btelles (Bernie Telles) 234 | * mitchellbryson (Mitchell Bryson) 235 | * nwah (Noah Burney) 236 | 237 | 238 | [0]: http://www.sinatrarb.com/ 239 | [1]: http://github.com/rtomayko/tilt 240 | [2]: http://haml-lang.com/ 241 | [3]: http://builder.rubyforge.org/ 242 | [4]: http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/ 243 | [5]: http://www.liquidmarkup.org/ 244 | [8]: http://lesscss.org/ 245 | [9]: http://rack.rubyforge.org/ 246 | [10]: http://lesscss.org/ 247 | [11]: http://en.wikipedia.org/wiki/Lorem_ipsum 248 | [12]: http://www.imagemagick.org/script/binary-releases.php?ImageMagick=4pg9cdfr8e6gn7aru9mtelepr3 249 | [13]: http://www.yaml.org/start.html 250 | [14]: http://placehold.it 251 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | desc "Run all specs" 5 | RSpec::Core::RakeTask.new do |t| 6 | t.name = :tests 7 | t.pattern = "spec/**/*_spec.rb" 8 | end 9 | -------------------------------------------------------------------------------- /autotest/discover.rb: -------------------------------------------------------------------------------- 1 | Autotest.add_discovery { 'rspec2' } -------------------------------------------------------------------------------- /bin/frank: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib]) 3 | 4 | require 'frank' 5 | 6 | begin 7 | # try to use bundler if its available 8 | require 'bundler' 9 | begin 10 | Bundler.require 11 | rescue Bundler::GemfileNotFound 12 | # revert to using local frank install 13 | $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib]) 14 | end 15 | rescue LoadError 16 | # revert to using local frank install 17 | $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib]) 18 | end 19 | 20 | Frank::CLI.run 21 | -------------------------------------------------------------------------------- /frank-1.0.12.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/frank-1.0.12.gem -------------------------------------------------------------------------------- /frank.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "frank/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "frank" 7 | s.version = Frank::VERSION 8 | s.authors = ["blahed", "nwah"] 9 | s.email = ["tdunn13@gmail"] 10 | s.description = "Rapidly develop static sites using any supported templating language" 11 | s.summary = "Rapidly develop static sites using any supported templating language" 12 | 13 | s.rubyforge_project = "frank" 14 | 15 | s.files = `git ls-files`.split("\n") 16 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 17 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 18 | s.require_paths = ["lib"] 19 | 20 | s.add_runtime_dependency 'rack', '~> 1.1' 21 | s.add_runtime_dependency 'mongrel', '~> 1.2.0.pre2' 22 | s.add_runtime_dependency 'haml', '~> 3.0' 23 | s.add_runtime_dependency 'tilt', '~> 1.3' 24 | s.add_runtime_dependency 'net-ssh', '~> 2.0' 25 | 26 | s.add_development_dependency 'rspec', '~> 2.6.0' 27 | s.add_development_dependency 'rack-test', '~> 0.5' 28 | s.add_development_dependency 'rake' 29 | s.add_development_dependency 'builder' 30 | s.add_development_dependency 'mocha' 31 | s.add_development_dependency 'erubis' 32 | s.add_development_dependency 'compass', '~> 0.10.2' 33 | s.add_development_dependency 'rdiscount' 34 | s.add_development_dependency 'liquid' 35 | s.add_development_dependency 'less' 36 | s.add_development_dependency 'coffee-script', '~> 2.2.0' 37 | s.add_development_dependency 'RedCloth' 38 | s.add_development_dependency 'net-scp' 39 | s.add_development_dependency 'net-sftp' 40 | end 41 | -------------------------------------------------------------------------------- /lib/frank.rb: -------------------------------------------------------------------------------- 1 | LIBDIR = File.dirname(__FILE__) 2 | 3 | local_helpers = File.join(Dir.pwd, 'helpers.rb') 4 | require local_helpers[0..-4] if File.exists? local_helpers 5 | 6 | module Frank 7 | class TemplateError < StandardError; end 8 | class ConfigError < StandardError; end 9 | end 10 | 11 | require 'rubygems' 12 | require 'yaml' 13 | require 'fileutils' 14 | require 'rack' 15 | require 'frank/version' 16 | require 'frank/settings' 17 | require 'frank/base' 18 | require 'frank/compile' 19 | require 'frank/publish' 20 | require 'frank/cli' 21 | 22 | # relay 23 | module Frank 24 | 25 | # Quickly configure Frank settings. Best used by passing a block. 26 | # 27 | # Example: 28 | # 29 | # Frank.configure do |settings| 30 | # settings.server.handler = "mongrel" 31 | # settings.server.hostname = "0.0.0.0" 32 | # settings.server.port = "3601" 33 | # 34 | # settings.static_folder = "static" 35 | # settings.dynamic_folder = "dynamic" 36 | # settings.layouts_folder = "layouts" 37 | # end 38 | # 39 | # Returns: 40 | # 41 | # The Frank +Settings+ singleton instance. 42 | class << self 43 | def configure 44 | settings = Frank::Settings.instance 45 | block_given? ? yield(settings) : settings 46 | end 47 | end 48 | 49 | # Take all the public instance methods from the Settings singleton and allow 50 | # them to be accessed through the Frank module directly. 51 | # 52 | # Examples: 53 | # 54 | # Frank.server.hander #=> "mongrel" 55 | # Frank.static_folder #=> "static" 56 | Frank::Settings.public_instance_methods(false).each do |name| 57 | (class << self; self; end).class_eval <<-EOT 58 | def #{name}(*args) 59 | configure.send("#{name}", *args) 60 | end 61 | EOT 62 | end 63 | end -------------------------------------------------------------------------------- /lib/frank/base.rb: -------------------------------------------------------------------------------- 1 | require 'frank/tilt_setup' 2 | require 'frank/template_helpers' 3 | require 'frank/rescue' 4 | require 'frank/upgrades' 5 | require 'frank/middleware/statik' 6 | require 'frank/middleware/refresh' 7 | 8 | module Frank 9 | extend Frank::Upgrades 10 | 11 | module Render; end 12 | 13 | class Base 14 | include Rack::Utils 15 | include Frank::Rescue 16 | include Frank::TemplateHelpers 17 | include Frank::Render 18 | 19 | def call(env) 20 | dup.call!(env) 21 | end 22 | 23 | def call!(env) 24 | @env = env 25 | @request = Rack::Request.new(env) 26 | @response = Rack::Response.new 27 | process 28 | @response.close 29 | @response.finish 30 | end 31 | 32 | private 33 | 34 | # attempt to render with the request path, 35 | # if it cannot be found, render error page 36 | def process 37 | load_helpers 38 | @response['Content-Type'] = Rack::Mime.mime_type(File.extname(@request.path), 'text/html') 39 | @response.write render(@request.path) 40 | rescue Frank::TemplateError 41 | @response.write render_404 42 | rescue Exception => e 43 | @response.write render_500(e) 44 | end 45 | 46 | # prints requests and errors to STDOUT 47 | def log_request(status, excp = nil) 48 | out = "\033[1m[#{Time.now.strftime('%Y-%m-%d %H:%M')}]\033[22m (#{@request.request_method}) http://#{@request.host}:#{@request.port}#{@request.fullpath} - #{status}" 49 | out << "\n\n#{excp.message}\n\n#{excp.backtrace.join("\n")} " if excp 50 | puts out 51 | end 52 | 53 | def load_helpers 54 | helpers = File.join(Frank.root, 'helpers.rb') 55 | if File.exist? helpers 56 | load helpers 57 | Frank::TemplateHelpers.class_eval("include FrankHelpers") 58 | end 59 | end 60 | end 61 | 62 | module Render 63 | 64 | TMPL_EXTS = { 65 | :html => %w[haml erb rhtml builder liquid textile md mkd markdown slim], 66 | :css => %w[sass less scss], 67 | :js => %w[coffee] 68 | } 69 | 70 | LAYOUT_EXTS = %w[.haml .erb .rhtml .liquid .slim] 71 | 72 | # render request path or template path 73 | def render(path, partial = false, local_vars = nil) 74 | @current_path = path unless partial 75 | 76 | # normalize the path 77 | path.sub!(/^\/?(.*)$/, '/\1') 78 | path.sub!(/\/$/, '/index.html') 79 | path.sub!(/(\/[\w-]+)$/, '\1.html') 80 | path = to_file_path(path) if defined? @request or path.match(/\/_[^\/]+$/) 81 | 82 | # regex for kinds that don't support meta 83 | # and define the meta delimiter 84 | nometa, delimiter = /\/_|\.(scss|sass|less|coffee)$/, /^META-{3,}\s*$|^-{3,}META\s*$/ 85 | 86 | # set the layout 87 | layout = path.match(nometa) ? nil : layout_for(path) 88 | 89 | template_path = File.join(Frank.root, Frank.dynamic_folder, path) 90 | raise Frank::TemplateError, "Template not found #{template_path}" unless File.exist? template_path 91 | 92 | # read in the template 93 | # check for meta and parse it if it exists 94 | template = File.read(template_path) << "\n" 95 | ext = File.extname(path) 96 | template, meta = template.split(delimiter).reverse 97 | locals = parse_meta_and_set_locals(meta, local_vars) 98 | 99 | # use given layout if defined as a meta field 100 | layout = locals[:layout] == 'nil' ? nil : locals[:layout] if locals.has_key?(:layout) 101 | 102 | page = setup_page 103 | 104 | # let tilt determine the template handler 105 | # and return some template markup 106 | if layout.nil? 107 | tilt(page, ext, template, template_path, locals) 108 | else 109 | layout_path = File.join(Frank.root, Frank.layouts_folder, layout) 110 | # add layout_path to locals 111 | raise Frank::TemplateError, "Layout not found #{layout_path}" unless File.exist? layout_path 112 | 113 | page_content = tilt(page, ext, template, template_path, locals) 114 | tilt(page, File.extname(layout), nil, layout_path, locals) do 115 | page_content 116 | end 117 | end 118 | end 119 | 120 | # converts a request path to a template path 121 | def to_file_path(path) 122 | file_name = File.basename(path, File.extname(path)) 123 | file_ext = File.extname(path).sub(/^\./, '') 124 | folder = File.join(Frank.root, Frank.dynamic_folder) 125 | engine = nil 126 | 127 | TMPL_EXTS.each do |ext, engines| 128 | if ext.to_s == file_ext 129 | engine = engines.reject do |eng| 130 | !File.exist? File.join(folder, path.sub(/\.[\w-]+$/, ".#{eng}")) 131 | end.first 132 | end 133 | end 134 | 135 | raise Frank::TemplateError, "Template not found #{path}" if engine.nil? 136 | 137 | path.sub(/\.[\w-]+$/, ".#{engine}") 138 | end 139 | 140 | # lookup the original ext for given template path 141 | def ext_from_handler(extension) 142 | ext = extension[1..-1] 143 | TMPL_EXTS.each do |orig_ext, engines| 144 | return orig_ext.to_s if engines.include? ext 145 | end 146 | end 147 | 148 | # reverse walks the layouts folder until we find a layout 149 | # returns nil if layout is not found 150 | def layout_for(path) 151 | layout_exts = LAYOUT_EXTS.dup 152 | ext = File.extname(path) 153 | default = 'default' << layout_ext_or_first(layout_exts, ext) 154 | file_path = path.sub(/\/[\w-]+\.[\w-]+$/, '') 155 | folders = file_path.split('/') 156 | 157 | until File.exist? File.join(Frank.root, Frank.layouts_folder, folders, default) 158 | break if layout_exts.empty? && folders.empty? 159 | 160 | if layout_exts.empty? 161 | layout_exts = LAYOUT_EXTS.dup 162 | default = 'default' << layout_ext_or_first(layout_exts, ext) 163 | folders.pop 164 | else 165 | default = 'default' << layout_exts.shift 166 | end 167 | end 168 | 169 | if File.exists? File.join(Frank.root, Frank.layouts_folder, folders, default) 170 | File.join(folders, default) 171 | else 172 | nil 173 | end 174 | end 175 | 176 | # if the given ext is a layout ext, pop it off and return it 177 | # otherwise return the first layout ext 178 | def layout_ext_or_first(layout_exts, ext) 179 | layout_exts.include?(ext) ? layout_exts.delete(ext) : layout_exts.first 180 | end 181 | 182 | # render a page using tilt and get the result template markup back 183 | def tilt(page, ext, source, filename, locals={}, &block) 184 | Tilt[ext].new(filename) do 185 | source || File.read(filename) 186 | end.render(page, locals=locals, &block) 187 | end 188 | 189 | # setup a new page object to be rendered 190 | def setup_page 191 | page = Object.new.extend(TemplateHelpers).extend(Render) 192 | instance_variables.each do |var| 193 | unless ['@response', '@env'].include? var 194 | page.instance_variable_set(var.intern, instance_variable_get(var)) 195 | end 196 | end 197 | page 198 | end 199 | 200 | private 201 | 202 | # parse the given meta string with yaml 203 | # set the current_path local 204 | def parse_meta_and_set_locals(meta, locals = nil) 205 | # parse yaml and symbolize keys 206 | if meta.nil? 207 | meta = {} 208 | else 209 | meta = YAML.load(meta).inject({}) do |options, (key, value)| 210 | options[(key.to_sym rescue key) || key] = value 211 | options 212 | end 213 | end 214 | meta.merge!(locals) unless locals.nil? 215 | meta[:current_path] = @current_path.sub(/\.[\w-]+$/, '').sub(/\/index/, '/') 216 | 217 | meta 218 | end 219 | end 220 | 221 | # Bootstrap will set up Frank up at a root path, and read in the setup.rb 222 | def self.bootstrap(new_root = nil) 223 | Frank.reset 224 | Frank.root = new_root if new_root 225 | 226 | 227 | # setup compass 228 | begin 229 | require 'compass' 230 | 231 | Compass.configuration do |config| 232 | # project_path should be the directory to which the sass directory is relative. 233 | # I think maybe this should be one more directory up from the configuration file. 234 | # Please update this if it is or remove this message if it can stay the way it is. 235 | config.project_path = Frank.root 236 | config.sass_dir = File.join('dynamic', 'stylesheets') 237 | end 238 | 239 | # sass_engine_options returns a hash, you can merge it with other options. 240 | Frank.sass_options = Compass.sass_engine_options 241 | rescue LoadError 242 | # ignore if compass is not there 243 | end 244 | 245 | # try to pull in setup 246 | setup = File.join(Frank.root, 'setup.rb') 247 | 248 | if File.exists?(setup) 249 | load setup 250 | elsif File.exist? File.join(Dir.pwd, 'settings.yml') 251 | puts "\033[31mFrank could not find setup.rb, perhaps you need to upgrade with the `frank upgrade\' command \033[0m" 252 | exit! 253 | end 254 | 255 | end 256 | 257 | # starts the server 258 | def self.new(&block) 259 | base = Base.new(&block) 260 | 261 | builder = Rack::Builder.new do 262 | use Frank::Middleware::Statik, :root => Frank.static_folder 263 | use Frank::Middleware::Refresh, :watch => [ Frank.dynamic_folder, Frank.static_folder, Frank.layouts_folder ] 264 | run base 265 | end 266 | 267 | unless Frank.environment == :test 268 | message = ['got it under control', 'got your back', 'holdin\' it down', 'takin\' care of business', 'workin\' some magic'].sort_by{rand}.first.strip 269 | 270 | puts "\n-----------------------" 271 | if Frank.serving_static? 272 | puts " This doesn't look like a frank project. Frank's serving this folder up his way..." 273 | else 274 | puts " Frank's #{ message }..." 275 | end 276 | puts " #{Frank.server.hostname}:#{Frank.server.port} \n\n" 277 | 278 | begin 279 | server = Rack::Handler.get(Frank.server.handler) 280 | rescue LoadError 281 | puts "\n\nUnable to find handler for: #{Frank.server.handler}" 282 | puts "\nUse `gem install #{Frank.server.handler}' to install it" 283 | puts "\nDefaulting to using webrick" 284 | server = Rack::Handler.get("webrick") 285 | end 286 | 287 | server.run(builder, :Port => Frank.server.port, :Host => Frank.server.hostname) do 288 | trap(:INT) { puts "\n\n-----------------------\n Show's over, fellas.\n\n"; exit } 289 | end 290 | end 291 | 292 | base 293 | 294 | rescue Errno::EADDRINUSE 295 | puts " Hold on a second... Frank works alone.\n \033[31mSomething's already using port #{Frank.server.port}\033[0m\n\n" 296 | end 297 | 298 | # copies over the generic project template 299 | def self.stub(project) 300 | templates_dir = File.join(ENV['HOME'], '.frank_templates') 301 | choice = nil 302 | 303 | puts "\nFrank is...\n - \033[32mCreating\033[0m your project '#{project}'" 304 | 305 | # if user has a ~/.frank_templates folder 306 | # provide an interface for choosing template 307 | if File.exist? templates_dir 308 | templates = %w[default] + Dir[File.join(templates_dir, '**')].map { |d| d.split('/').last } 309 | 310 | puts "\nWhich template would you like to use? " 311 | templates.each_with_index { |t, i| puts " #{i + 1}. #{t}" } 312 | 313 | print '> ' 314 | 315 | # get input and wait for a valid response 316 | trap(:INT) { puts "\nbye"; exit } 317 | choice = STDIN.gets.chomp 318 | until ( choice.match(/^\d+$/) && templates[choice.to_i - 1] ) || choice == '1' 319 | print " `#{choice}' \033[31mis not a valid template choice\033[0m\n> " 320 | choice = STDIN.gets.chomp 321 | end 322 | end 323 | 324 | Dir.mkdir project 325 | template = choice.nil? ? 'default' : templates[choice.to_i - 1] 326 | 327 | puts " - \033[32mCopying\033[0m #{template} Frank template" 328 | 329 | if template == 'default' 330 | FileUtils.cp_r( Dir.glob(File.join(LIBDIR, 'template/*')), project ) 331 | else 332 | FileUtils.cp_r( Dir.glob(File.join(templates_dir, "#{template}/*")), project ) 333 | end 334 | 335 | puts "\n \033[32mCongratulations, '#{project}' is ready to go!\033[0m" 336 | rescue Errno::EEXIST 337 | puts "\n \033[31muh oh, directory '#{project}' already exists...\033[0m" 338 | exit 339 | end 340 | 341 | end 342 | -------------------------------------------------------------------------------- /lib/frank/cli.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | module Frank 4 | class CLI 5 | BANNER = <<-USAGE 6 | Usage: 7 | frank new PROJECT_PATH 8 | frank server [options] 9 | frank export PATH [options] 10 | frank publish 11 | 12 | Description: 13 | The `frank new' command generates a frank template project with the default 14 | directory structure and configuration at the given path. 15 | 16 | Once you have a frank project you can use the `frank server' or the aliased 'frank up' commands 17 | to start the development server and begin developing your project. 18 | 19 | When you are finished working and ready to export you can use 20 | the `frank export' or aliased `frank out' commands. 21 | 22 | Example: 23 | frank new ~/Dev/blah.com 24 | cd ~/Dev/blah.com 25 | frank server 26 | 27 | # do some development 28 | 29 | # export it 30 | frank export ~/Dev/html/blah.com 31 | 32 | # or publish it 33 | frank publish 34 | USAGE 35 | 36 | class << self 37 | 38 | def set_options 39 | @options = {:server => {}} 40 | 41 | @opts = OptionParser.new do |opts| 42 | opts.banner = BANNER.gsub(/^\s{4}/, '') 43 | 44 | opts.separator '' 45 | opts.separator 'Options:' 46 | 47 | opts.on('--server [HANDLER]', 'Set the server handler (frank server)') do |handler| 48 | @options[:server]['handler'] = handler unless handler.nil? 49 | end 50 | 51 | opts.on('--hostname [HOSTNAME]', 'Set the server hostname (frank server)') do |hostname| 52 | @options[:server]['hostname'] = hostname unless hostname.nil? 53 | end 54 | 55 | opts.on('--port [PORT]', 'Set the server port (frank server)') do |port| 56 | @options[:server]['port'] = port unless port.nil? 57 | end 58 | 59 | opts.on('--dynamic_folder [FOLDER]', 'Set the dynamic folder (frank server)') do |folder| 60 | @options[:dynamic_folder] = folder unless folder.nil? 61 | end 62 | 63 | opts.on('--static_folder [FOLDER]', 'Set the static folder (frank server)') do |folder| 64 | @options[:static_folder] = folder unless folder.nil? 65 | end 66 | 67 | opts.on('--production', 'Production ready export (frank export) i.e. ([FOLDER]/index.html)') do |handler| 68 | @options[:production] = true 69 | end 70 | 71 | opts.on('-f', 'Overwrite existing folder on export (frank export)') do |handler| 72 | @options[:force_export] = true 73 | end 74 | 75 | opts.on('-v', '--version', 'Show the frank version and exit') do 76 | puts "Frank v#{Frank::VERSION}" 77 | exit 78 | end 79 | 80 | opts.on( '-h', '--help', 'Display this help' ) do 81 | puts opts 82 | exit 83 | end 84 | end 85 | 86 | @opts.parse! 87 | end 88 | 89 | # parse and set options 90 | # bootstrap if we need to 91 | # and go go go 92 | def run 93 | set_options 94 | 95 | if ARGV.empty? 96 | print_usage_and_exit! 97 | else 98 | bootstrap 99 | run! 100 | end 101 | end 102 | 103 | # determine what the user wants us to do 104 | # and then, just do it 105 | def run! 106 | case ARGV.first 107 | when 'new', 'n' 108 | print_usage_and_exit! unless ARGV[1] 109 | # stub out the project 110 | Frank.stub(ARGV[1]) 111 | when 'server', 's', 'up' 112 | # setup server from options 113 | server_options = @options[:server] 114 | Frank.server.handler = server_options['handler'] if server_options['handler'] 115 | Frank.server.hostname = server_options['hostname'] if server_options['hostname'] 116 | Frank.server.port = server_options['port'] if server_options['port'] 117 | if File.exist? 'setup.rb' 118 | # setup folder options if we have setup.rb 119 | Frank.dynamic_folder = @options[:dynamic_folder] if @options[:dynamic_folder] 120 | Frank.static_folder = @options[:static_folder] if @options[:static_folder] 121 | else 122 | # let frank act like a real grown up server 123 | Frank.serving_static! 124 | Frank.dynamic_folder = '.' 125 | Frank.static_folder = '.' 126 | end 127 | Frank.new 128 | when 'export', 'e', 'out', 'compile' 129 | # compile the project 130 | Frank.exporting! 131 | Frank.production! if @options[:production] 132 | Frank.force_export! if @options[:force_export] 133 | Frank.export.path = ARGV[1] if ARGV[1] 134 | Frank::Compile.export! 135 | when 'publish', 'p' 136 | # compile the project and scp it to server 137 | Frank.publishing! 138 | Frank::Publish.execute! 139 | when 'upgrade' 140 | # upgrade if we need to upgrade 141 | Frank.upgrade! 142 | else 143 | puts "frank doesn't know that one... `frank --help' for usage" 144 | exit 145 | end 146 | end 147 | 148 | # print the usage banner and exit 149 | def print_usage_and_exit! 150 | puts @opts 151 | exit 152 | end 153 | 154 | # bootstrap this project up 155 | # only if we really need to 156 | def bootstrap 157 | if %w[server up export out publish upgrade s e p].include? ARGV.first 158 | Frank.bootstrap(Dir.pwd) 159 | end 160 | end 161 | 162 | end 163 | 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /lib/frank/compile.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | class Compile < Frank::Base 3 | 4 | class << self 5 | include Frank::Render 6 | 7 | # compile the templates 8 | # if production and template isn't index and is html 9 | # name a folder based on the template and compile to index.html 10 | # otherwise compile as is 11 | def compile_templates 12 | dir = File.join(Frank.root, Frank.dynamic_folder) 13 | 14 | Dir[File.join(dir, '**{,/*/**/*}')].each do |path| 15 | if File.file?(path) && !File.basename(path).match(/^(\.|_)/) 16 | path = path[ (dir.size + 1)..-1 ] 17 | ext = File.extname(path) 18 | new_ext = ext_from_handler(ext) 19 | name = File.basename(path, ext) 20 | 21 | if Frank.production? && "#{name}.#{new_ext}" != 'index.html' && new_ext == 'html' 22 | new_file = File.join(Frank.export.path, path.sub(/(\/?[\w-]+)\.[\w-]+$/, "\\1/index.#{new_ext}")) 23 | else 24 | new_file = File.join(Frank.export.path, path.sub(/\.[\w-]+$/, ".#{new_ext}")) 25 | end 26 | 27 | create_dirs(new_file) 28 | File.open(new_file, 'w') {|f| f.write render(path) } 29 | puts " - \033[32mCreating\033[0m '#{new_file}'" unless Frank.silent_export? 30 | end 31 | end 32 | end 33 | 34 | # use path to determine folder name and 35 | # create the required folders if they don't exist 36 | def create_dirs(path) 37 | FileUtils.makedirs path.split('/').reverse[1..-1].reverse.join('/') 38 | end 39 | 40 | # copies over static content 41 | def copy_static 42 | puts " - \033[32mCopying\033[0m static content" unless Frank.silent_export? 43 | static_folder = File.join(Frank.root, Frank.static_folder) 44 | FileUtils.cp_r(File.join(static_folder, '/.'), Frank.export.path) 45 | end 46 | 47 | # ask the user if they want to overwrite the folder 48 | # get the input and return it 49 | def ask_nicely 50 | print "\033[31mA folder named `#{Frank.export.path}' already exists, overwrite it?\033[0m [y/n] " 51 | STDIN.gets.chomp.downcase 52 | end 53 | 54 | # verify that the user wants to overwrite the folder 55 | # remove it if so, exit if not 56 | def verify_overwriting 57 | overwrite = ask_nicely 58 | 59 | while overwrite.empty? 60 | overwrite = ask_nicely 61 | end 62 | 63 | overwrite == 'y' ? FileUtils.rm_rf(Frank.export.path) : exit 64 | end 65 | 66 | # TODO verbose everywhere is lame 67 | # create the dump dir, compile templates, copy over static assets 68 | def export! 69 | folder_exists = File.exist?(Frank.export.path) 70 | 71 | if folder_exists && Frank.export.force 72 | FileUtils.rm_rf(Frank.export.path) 73 | elsif folder_exists 74 | verify_overwriting 75 | end 76 | 77 | FileUtils.mkdir(Frank.export.path) 78 | 79 | unless Frank.silent_export? 80 | puts "\nFrank is..." 81 | puts " - \033[32mCreating\033[0m '#{Frank.export.path}'" 82 | end 83 | 84 | compile_templates 85 | copy_static 86 | 87 | puts "\n \033[32mCongratulations, project dumped to '#{Frank.export.path}' successfully!\033[0m" unless Frank.silent_export? 88 | end 89 | end 90 | 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/frank/lorem.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | class Lorem 3 | WORDS = %w(alias consequatur aut perferendis sit voluptatem accusantium doloremque aperiam eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo aspernatur aut odit aut fugit sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt neque dolorem ipsum quia dolor sit amet consectetur adipisci velit sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem ut enim ad minima veniam quis nostrum exercitationem ullam corporis nemo enim ipsam voluptatem quia voluptas sit suscipit laboriosam nisi ut aliquid ex ea commodi consequatur quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae et iusto odio dignissimos ducimus qui blanditiis praesentium laudantium totam rem voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident sed ut perspiciatis unde omnis iste natus error similique sunt in culpa qui officia deserunt mollitia animi id est laborum et dolorum fuga et harum quidem rerum facilis est et expedita distinctio nam libero tempore cum soluta nobis est eligendi optio cumque nihil impedit quo porro quisquam est qui minus id quod maxime placeat facere possimus omnis voluptas assumenda est omnis dolor repellendus temporibus autem quibusdam et aut consequatur vel illum qui dolorem eum fugiat quo voluptas nulla pariatur at vero eos et accusamus officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae itaque earum rerum hic tenetur a sapiente delectus ut aut reiciendis voluptatibus maiores doloribus asperiores repellat) 4 | 5 | def word(replacement = nil) 6 | words 1, replacement 7 | end 8 | 9 | def words(total, replacement = nil) 10 | if Frank.exporting? && replacement 11 | replacement 12 | else 13 | (1..total).map do 14 | randm(WORDS) 15 | end.join(' ') 16 | end 17 | end 18 | 19 | def sentence(replacement = nil) 20 | sentences 1, replacement 21 | end 22 | 23 | def sentences(total, replacement = nil) 24 | # TODO: Don't capitalize replacement field 25 | if Frank.exporting? && replacement 26 | replacement 27 | else 28 | (1..total).map do 29 | words(randm(4..15)).capitalize 30 | end.join('. ') 31 | end 32 | end 33 | 34 | def paragraph(replacement = nil) 35 | paragraphs 1, replacement 36 | end 37 | 38 | def paragraphs(total, replacement = nil) 39 | if Frank.exporting? && replacement 40 | replacement 41 | else 42 | (1..total).map do 43 | sentences(randm(3..7), replacement).capitalize 44 | end.join("\n\n") 45 | end 46 | end 47 | 48 | def date(fmt = '%a %b %d, %Y', range = 1950..2010, replacement = nil) 49 | if Frank.exporting? && replacement 50 | replacement 51 | else 52 | y = rand(range.last - range.first) + range.first 53 | m = rand(12) + 1 54 | d = rand(31) + 1 55 | Time.local(y,m,d).strftime(fmt) 56 | end 57 | end 58 | 59 | def name(replacement = nil) 60 | if Frank.exporting? && replacement 61 | replacement 62 | else 63 | "#{first_name} #{last_name}" 64 | end 65 | end 66 | 67 | def first_name(replacement = nil) 68 | if Frank.exporting? && replacement 69 | replacement 70 | else 71 | names = %w(Judith Angelo Margarita Kerry Elaine Lorenzo Justice Doris Raul Liliana Kerry Elise Ciaran Johnny Moses Davion Penny Mohammed Harvey Sheryl Hudson Brendan Brooklynn Denis Sadie Trisha Jacquelyn Virgil Cindy Alexa Marianne Giselle Casey Alondra Angela Katherine Skyler Kyleigh Carly Abel Adrianna Luis Dominick Eoin Noel Ciara Roberto Skylar Brock Earl Dwayne Jackie Hamish Sienna Nolan Daren Jean Shirley Connor Geraldine Niall Kristi Monty Yvonne Tammie Zachariah Fatima Ruby Nadia Anahi Calum Peggy Alfredo Marybeth Bonnie Gordon Cara John Staci Samuel Carmen Rylee Yehudi Colm Beth Dulce Darius inley Javon Jason Perla Wayne Laila Kaleigh Maggie Don Quinn Collin Aniya Zoe Isabel Clint Leland Esmeralda Emma Madeline Byron Courtney Vanessa Terry Antoinette George Constance Preston Rolando Caleb Kenneth Lynette Carley Francesca Johnnie Jordyn Arturo Camila Skye Guy Ana Kaylin Nia Colton Bart Brendon Alvin Daryl Dirk Mya Pete Joann Uriel Alonzo Agnes Chris Alyson Paola Dora Elias Allen Jackie Eric Bonita Kelvin Emiliano Ashton Kyra Kailey Sonja Alberto Ty Summer Brayden Lori Kelly Tomas Joey Billie Katie Stephanie Danielle Alexis Jamal Kieran Lucinda Eliza Allyson Melinda Alma Piper Deana Harriet Bryce Eli Jadyn Rogelio Orlaith Janet Randal Toby Carla Lorie Caitlyn Annika Isabelle inn Ewan Maisie Michelle Grady Ida Reid Emely Tricia Beau Reese Vance Dalton Lexi Rafael Makenzie Mitzi Clinton Xena Angelina Kendrick Leslie Teddy Jerald Noelle Neil Marsha Gayle Omar Abigail Alexandra Phil Andre Billy Brenden Bianca Jared Gretchen Patrick Antonio Josephine Kyla Manuel Freya Kellie Tonia Jamie Sydney Andres Ruben Harrison Hector Clyde Wendell Kaden Ian Tracy Cathleen Shawn) 72 | names[rand(names.size)] 73 | end 74 | end 75 | 76 | def last_name(replacement = nil) 77 | if Frank.exporting? && replacement 78 | replacement 79 | else 80 | names = %w(Chung Chen Melton Hill Puckett Song Hamilton Bender Wagner McLaughlin McNamara Raynor Moon Woodard Desai Wallace Lawrence Griffin Dougherty Powers May Steele Teague Vick Gallagher Solomon Walsh Monroe Connolly Hawkins Middleton Goldstein Watts Johnston Weeks Wilkerson Barton Walton Hall Ross Chung Bender Woods Mangum Joseph Rosenthal Bowden Barton Underwood Jones Baker Merritt Cross Cooper Holmes Sharpe Morgan Hoyle Allen Rich Rich Grant Proctor Diaz Graham Watkins Hinton Marsh Hewitt Branch Walton O'Brien Case Watts Christensen Parks Hardin Lucas Eason Davidson Whitehead Rose Sparks Moore Pearson Rodgers Graves Scarborough Sutton Sinclair Bowman Olsen Love McLean Christian Lamb James Chandler Stout Cowan Golden Bowling Beasley Clapp Abrams Tilley Morse Boykin Sumner Cassidy Davidson Heath Blanchard McAllister McKenzie Byrne Schroeder Griffin Gross Perkins Robertson Palmer Brady Rowe Zhang Hodge Li Bowling Justice Glass Willis Hester Floyd Graves Fischer Norman Chan Hunt Byrd Lane Kaplan Heller May Jennings Hanna Locklear Holloway Jones Glover Vick O'Donnell Goldman McKenna Starr Stone McClure Watson Monroe Abbott Singer Hall Farrell Lucas Norman Atkins Monroe Robertson Sykes Reid Chandler Finch Hobbs Adkins Kinney Whitaker Alexander Conner Waters Becker Rollins Love Adkins Black Fox Hatcher Wu Lloyd Joyce Welch Matthews Chappell MacDonald Kane Butler Pickett Bowman Barton Kennedy Branch Thornton McNeill Weinstein Middleton Moss Lucas Rich Carlton Brady Schultz Nichols Harvey Stevenson Houston Dunn West O'Brien Barr Snyder Cain Heath Boswell Olsen Pittman Weiner Petersen Davis Coleman Terrell Norman Burch Weiner Parrott Henry Gray Chang McLean Eason Weeks Siegel Puckett Heath Hoyle Garrett Neal Baker Goldman Shaffer Choi Carver) 81 | names[rand(names.size)] 82 | end 83 | end 84 | 85 | def email(replacement = nil) 86 | if Frank.exporting? && replacement 87 | replacement 88 | else 89 | delimiters = [ '_', '-', '' ] 90 | domains = %w(gmail.com yahoo.com hotmail.com email.com live.com me.com mac.com aol.com fastmail.com mail.com) 91 | username = name.gsub(/[^\w]/, delimiters[rand(delimiters.size)]) 92 | "#{username}@#{domains[rand(domains.size)]}".downcase 93 | end 94 | end 95 | 96 | 97 | def image(size, options={}) 98 | if Frank.exporting? && options[:replacement] 99 | options[:replacement] 100 | else 101 | src = "http://placehold.it/#{size}" 102 | hex = %w[a b c d e f 0 1 2 3 4 5 6 7 8 9] 103 | background_color = options[:background_color] 104 | color = options[:color] 105 | 106 | if options[:random_color] 107 | background_color = hex.shuffle[0...6].join 108 | color = hex.shuffle[0...6].join 109 | end 110 | 111 | src << "/#{background_color.sub(/^#/, '')}" if background_color 112 | src << "/ccc" if background_color.nil? && color 113 | src << "/#{color.sub(/^#/, '')}" if color 114 | src << "&text=#{Rack::Utils::escape(options[:text])}" if options[:text] 115 | 116 | src 117 | end 118 | end 119 | 120 | private 121 | 122 | def randm(range) 123 | a = range.to_a 124 | a[rand(a.length)] 125 | end 126 | 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/frank/middleware/imager.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | module Middleware 3 | 4 | begin 5 | require 'mini_magick' 6 | rescue LoadError 7 | end 8 | 9 | class Imager 10 | 11 | def initialize(app, options={}) 12 | @app = app 13 | end 14 | 15 | # choose a random image if random is in the query 16 | def image_filename(dims, query) 17 | if query.include?('random') 18 | "frank#{rand(10)}.jpg" 19 | else 20 | "frank#{dims.hash.to_s[-1..-1]}.jpg" 21 | end 22 | end 23 | 24 | # catch a request for _img/0x0, get an image, resize it to given dims 25 | def call(env) 26 | path = env['PATH_INFO'] 27 | image_path = File.join(LIBDIR, 'frank/templates/imager/') 28 | 29 | if defined?(MiniMagick) && path.include?('_img') 30 | dims = '!' + path.split('/').last.match(/\d+x\d+/i).to_s 31 | filename = image_filename(dims, env['QUERY_STRING']) 32 | 33 | image = MiniMagick::Image.from_file(image_path + filename) 34 | image.resize dims 35 | return [ 200, { 'Content-Type' => 'image/jpg' }, image.to_blob ] 36 | end 37 | @app.call(env) 38 | end 39 | 40 | end 41 | 42 | end 43 | end -------------------------------------------------------------------------------- /lib/frank/middleware/refresh.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | module Middleware 3 | class Refresh 4 | 5 | def initialize(app, options={}) 6 | @app = app 7 | @folders = options[:watch] 8 | end 9 | 10 | # catch __refrank__ path and 11 | # return the most recent timestamp 12 | def call(env) 13 | request = Rack::Request.new(env) 14 | if request.path_info.match /^\/__refresh__$/ 15 | [ 200, { 'Content-Type' => 'application/json', 'Connection' => 'Keep-Alive', 'Keep-Alive' => 'timeout=60', 'Cache-Control' => 'max-age=0' }, "#{get_mtime}" ] 16 | else 17 | @app.call(env) 18 | end 19 | end 20 | 21 | private 22 | 23 | # build list of mtimes for watched files 24 | # return the most recent 25 | def get_mtime 26 | pwd = Dir.pwd 27 | timestamps = [] 28 | helpers = File.join(pwd, 'helpers.rb') 29 | 30 | timestamps << File.mtime(helpers).to_i if File.exist? helpers 31 | @folders.each do |folder| 32 | Dir[File.join(pwd, folder, '**/*.*')].each do |found| 33 | timestamps << File.mtime(found).to_i unless File.directory?(found) 34 | end 35 | end 36 | timestamps.sort.last 37 | end 38 | 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/frank/middleware/statik.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | module Middleware 3 | class Statik 4 | 5 | def initialize(app, options={}) 6 | @app = app 7 | frank_root = File.expand_path(File.dirname(File.dirname(__FILE__))) + '/templates' 8 | root = options[:root] || Dir.pwd 9 | @frank_server = Rack::File.new(frank_root) 10 | @static_server = Rack::File.new(root) 11 | end 12 | 13 | # handles serving from __frank__ 14 | # looks for static access, if not found, 15 | # passes request to frank 16 | def call(env) 17 | path = env['PATH_INFO'].dup 18 | 19 | if path.include? '__frank__' 20 | env['PATH_INFO'].gsub!('/__frank__', '') 21 | result = @frank_server.call(env) 22 | else 23 | env['PATH_INFO'] << '/' unless path.match(/(\.\w+|\/)$/) 24 | env['PATH_INFO'] << 'index.html' if path[-1..-1] == '/' 25 | result = @static_server.call(env) 26 | end 27 | 28 | # return if static assets found 29 | # else reset the path and pass to frank 30 | if result[0] == 200 || result[0] == 304 31 | result 32 | else 33 | env['PATH_INFO'] = path 34 | @app.call(env) 35 | end 36 | 37 | end 38 | 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/frank/publish.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | module Publish 3 | 4 | 5 | def self.execute! 6 | protocol = exit_unless_configured.to_s 7 | 8 | ok_message "", "\nFrank is..." 9 | ok_message "Exporting templates", " - " 10 | 11 | # upload the files and report progress 12 | ok_message "Publishing to: `#{Frank.publish.host}:#{Frank.publish.path}' via #{protocol}", " - " 13 | 14 | 15 | req = "frank/publish/#{protocol.downcase}" 16 | rescue_load_error protocol do 17 | require req 18 | clazz = Frank::Publish.const_get(protocol.upcase) 19 | publisher = clazz.new(Frank.publish) 20 | publisher.perform! 21 | end 22 | 23 | ok_message "\nPublish complete!" 24 | end 25 | 26 | def self.exit_unless_configured 27 | required_settings = {:host => Frank.publish.host, :path => Frank.publish.path, :username => Frank.publish.username} 28 | 29 | should_exit = false 30 | message = "" 31 | 32 | protocol = Frank.publish.mode || :scp 33 | unless [:ftp, :ftptls, :sftp, :scp].include?(protocol.to_sym) 34 | message << "Frank.publish.mode = #{protocol} is not supported. Supported publish modes are 'ftp', 'ftptls', 'sftp' or 'scp' (default)\n" 35 | should_exit = true 36 | end 37 | 38 | required_settings.each do |name, value| 39 | if value.nil? 40 | message << "Frank.publish.#{name} is required to publish. You can configure it in setup.rb\n" 41 | should_exit = true 42 | end 43 | end 44 | 45 | 46 | if should_exit 47 | err_message message 48 | exit! 49 | end 50 | 51 | protocol 52 | end 53 | 54 | def self.rescue_load_error protocol, &blk 55 | gem = "net-#{protocol}" 56 | begin 57 | yield 58 | rescue LoadError 59 | err_message "Publishing via #{protocol} requires the '#{gem}' gem. `gem install #{gem}'" 60 | exit! 61 | end 62 | end 63 | 64 | 65 | def self.ok_message str, prefix = '' 66 | puts "#{prefix}\033[32m#{str}\033[0m" 67 | end 68 | 69 | def self.err_message str, prefix = '' 70 | puts "#{prefix}\033[31m#{str}\033[0m" 71 | end 72 | 73 | 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /lib/frank/publish/base.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | module Publish 3 | class Base 4 | 5 | attr_accessor :username, :password 6 | attr_accessor :hostname, :port 7 | attr_accessor :remote_path 8 | attr_accessor :local_path 9 | 10 | 11 | def initialize(options) 12 | @username = options.user || options.username 13 | @password = options.password 14 | @hostname = options.host 15 | @remote_path = options.path 16 | @port = options.port 17 | @local_path = "/tmp/frank-publish-#{Frank.proj_name}-#{Time.now.to_i}" 18 | end 19 | 20 | ## 21 | # Performs the backup transfer 22 | def perform! 23 | export! 24 | begin 25 | transfer! 26 | rescue SocketError 27 | err_message "Transfer failed. SocketError. Do you have internet?" 28 | end 29 | cleanup! 30 | end 31 | 32 | 33 | private 34 | 35 | def export! 36 | # remove stale folder if it exists 37 | FileUtils.rm_rf(local_path) if File.exist?(local_path) 38 | 39 | # dump the project in production mode to tmp folder 40 | Frank.export.path = @local_path 41 | Frank.export.silent = true 42 | Frank::Compile.export! 43 | end 44 | 45 | 46 | ## 47 | # returns a local_path relative list of all files to transfer for this export 48 | def files_to_transfer 49 | list = [] 50 | return list unless File.exist?(local_path) 51 | 52 | Dir.chdir(local_path) do 53 | list = Dir.glob("**/*").map do |f| 54 | f unless File.directory? f 55 | end.compact! 56 | end 57 | 58 | list 59 | end 60 | 61 | ## 62 | # returns a local_path relative list of all directories to export 63 | def directories 64 | list = [] 65 | return list unless File.exist?(local_path) 66 | 67 | Dir.chdir(local_path) do 68 | list = Dir.glob("**/*").map do |f| 69 | f if File.directory? f 70 | end.compact! 71 | end 72 | 73 | list 74 | end 75 | 76 | def cleanup! 77 | FileUtils.rm_rf(@local_path) 78 | end 79 | 80 | # ZOMG, we a need a Logger or sth. 81 | def ok_message *args 82 | Frank::Publish.ok_message *args 83 | end 84 | 85 | def err_message *args 86 | Frank::Publish.err_message *args 87 | end 88 | 89 | 90 | end 91 | 92 | 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/frank/publish/ftp.rb: -------------------------------------------------------------------------------- 1 | require 'frank/publish/base' 2 | require 'net/ftp' 3 | 4 | module Frank 5 | module Publish 6 | 7 | class FTP < Base 8 | 9 | def initialize(options, &block) 10 | super(options) 11 | instance_eval(&block) if block_given? 12 | 13 | @port ||= 21 14 | @remote_path = remote_path.sub(/^\~\//, '').sub(/^\//, '') 15 | end 16 | 17 | private 18 | 19 | ## 20 | # Establishes a connection to the remote server 21 | # 22 | # Note: 23 | # Since the FTP port is defined as a constant in the Net::FTP class, and 24 | # might be required to change by the user, we dynamically remove and 25 | # re-add the constant with the provided port value 26 | def connection 27 | if Net::FTP.const_defined?(:FTP_PORT) 28 | Net::FTP.send(:remove_const, :FTP_PORT) 29 | end; Net::FTP.send(:const_set, :FTP_PORT, port) 30 | 31 | Net::FTP.open(hostname, username, password) do |ftp| 32 | ftp.passive = true 33 | yield ftp 34 | end 35 | end 36 | 37 | ## 38 | # Transfers the archived file to the specified remote server 39 | def transfer! 40 | connection do |ftp| 41 | directories.each do |dir| 42 | create_remote_path(File.join(remote_path, dir), ftp) 43 | end 44 | 45 | files_to_transfer.each do |file| 46 | ok_message "Uploading #{file}", " - " 47 | ftp.put( 48 | File.join(local_path, file), 49 | File.join(remote_path, file) 50 | ) 51 | end 52 | end 53 | end 54 | 55 | ## 56 | # Creates (if they don't exist yet) all the directories on the remote 57 | # server in order to upload the backup file. Net::FTP does not support 58 | # paths to directories that don't yet exist when creating new 59 | # directories. Instead, we split the parts up in to an array (for each 60 | # '/') and loop through that to create the directories one by one. 61 | # Net::FTP raises an exception when the directory it's trying to create 62 | # already exists, so we have rescue it 63 | def create_remote_path(remote_path, ftp) 64 | path_parts = [] 65 | remote_path.split('/').each do |path_part| 66 | path_parts << path_part 67 | begin 68 | dir = path_parts.join('/') 69 | ftp.mkdir(dir) unless dir.empty? 70 | rescue Net::FTPPermError; 71 | end 72 | end 73 | end 74 | 75 | 76 | end 77 | 78 | 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/frank/publish/ftptls.rb: -------------------------------------------------------------------------------- 1 | require 'frank/publish/base' 2 | require 'net/ftptls' 3 | 4 | module Frank 5 | module Publish 6 | 7 | class FTPTLS < Base 8 | 9 | def initialize(options, &block) 10 | super(options) 11 | instance_eval(&block) if block_given? 12 | 13 | @port ||= 21 14 | @remote_path = remote_path.sub(/^\~\//, '').sub(/^\//, '') 15 | end 16 | 17 | private 18 | 19 | ## 20 | # Establishes a connection to the remote server 21 | # 22 | # Note: 23 | # Since the FTP port is defined as a constant in the Net::FTP class, and 24 | # might be required to change by the user, we dynamically remove and 25 | # re-add the constant with the provided port value 26 | def connection 27 | if Net::FTPTLS.const_defined?(:FTP_PORT) 28 | Net::FTPTLS.send(:remove_const, :FTP_PORT) 29 | end; Net::FTPTLS.send(:const_set, :FTP_PORT, port) 30 | 31 | Net::FTPTLS.open(hostname, username, password) do |ftp| 32 | ftp.passive = true 33 | yield ftp 34 | end 35 | end 36 | 37 | ## 38 | # Transfers the archived file to the specified remote server 39 | def transfer! 40 | connection do |ftp| 41 | directories.each do |dir| 42 | create_remote_path(File.join(remote_path, dir), ftp) 43 | end 44 | 45 | files_to_transfer.each do |file| 46 | ok_message "Uploading #{file}", " - " 47 | ftp.put( 48 | File.join(local_path, file), 49 | File.join(remote_path, file) 50 | ) 51 | end 52 | end 53 | end 54 | 55 | ## 56 | # Creates (if they don't exist yet) all the directories on the remote 57 | # server in order to upload the backup file. Net::FTP does not support 58 | # paths to directories that don't yet exist when creating new 59 | # directories. Instead, we split the parts up in to an array (for each 60 | # '/') and loop through that to create the directories one by one. 61 | # Net::FTP raises an exception when the directory it's trying to create 62 | # already exists, so we have rescue it 63 | def create_remote_path(remote_path, ftp) 64 | path_parts = [] 65 | remote_path.split('/').each do |path_part| 66 | path_parts << path_part 67 | begin 68 | dir = path_parts.join('/') 69 | ftp.mkdir(dir) unless dir.empty? 70 | rescue Net::FTPPermError; 71 | end 72 | end 73 | end 74 | 75 | 76 | end 77 | 78 | 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/frank/publish/scp.rb: -------------------------------------------------------------------------------- 1 | require 'frank/publish/base' 2 | require 'net/ssh' 3 | require 'net/scp' 4 | 5 | 6 | module Frank 7 | module Publish 8 | class SCP < Base 9 | 10 | def initialize(options, &block) 11 | super(options) 12 | instance_eval(&block) if block_given? 13 | 14 | @port ||= 22 15 | end 16 | 17 | 18 | def connection 19 | Net::SCP.start(hostname, username, :password => password) do |scp| 20 | yield scp 21 | end 22 | end 23 | 24 | def transfer! 25 | connection do |scp| 26 | old_name = '' 27 | scp.upload! local_path, remote_path do |ch, name| 28 | if old_name != name 29 | ok_message "Uploading #{name}", " - " 30 | end 31 | end 32 | end 33 | end 34 | 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/frank/publish/sftp.rb: -------------------------------------------------------------------------------- 1 | require 'frank/publish/base' 2 | require 'net/ssh' 3 | require 'net/sftp' 4 | 5 | 6 | module Frank 7 | module Publish 8 | class SFTP < Base 9 | 10 | def initialize(options, &block) 11 | super(options) 12 | instance_eval(&block) if block_given? 13 | 14 | @port ||= 22 15 | end 16 | 17 | 18 | def connection 19 | Net::SFTP.start(hostname, username, :password => password) do |scp| 20 | yield scp 21 | end 22 | end 23 | 24 | def transfer! 25 | connection do |scp| 26 | old_name = '' 27 | scp.upload! local_path, remote_path do |ch, name| 28 | if old_name != name 29 | ok_message "Uploading #{name}", " - " 30 | end 31 | end 32 | end 33 | end 34 | 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/frank/publish/shell_scp.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | module Publish 3 | 4 | #TODO 5 | 6 | class ShellSCP 7 | 8 | 9 | def self.shell_copy(local_dir, remote_dir, options) 10 | 11 | host = [] 12 | command = ["scp "] 13 | command << "-P #{options[:port]} " if options[:port] 14 | command << "-r #{local_dir}/* " 15 | host << "#{options[:username]}" if options[:username] 16 | host << ":#{options[:password]}" if options[:password] 17 | host << "@#{options[:host]}:#{remote_dir}" 18 | 19 | shell_command = "#{command.join('')}#{host.join('')}" 20 | system(shell_command) 21 | 22 | end 23 | 24 | 25 | end 26 | 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/frank/rescue.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | module Rescue 3 | 4 | def render_404 5 | log_request('404') 6 | template = File.expand_path(File.dirname(__FILE__)) + '/templates/404.haml' 7 | locals = { :request => @env, 8 | :dynamic_folder => Frank.dynamic_folder, 9 | :static_folder => Frank.static_folder, 10 | :environment => Frank.environment } 11 | 12 | @response['Content-Type'] = 'text/html' 13 | @response.status = 404 14 | 15 | obj = Object.new.extend(TemplateHelpers) 16 | Tilt::HamlTemplate.new(template).render(obj, locals = locals) 17 | end 18 | 19 | def render_500(excp) 20 | log_request('500', excp) 21 | template = File.expand_path(File.dirname(__FILE__)) + '/templates/500.haml' 22 | locals = { :request => @env, 23 | :params => @request.params, 24 | :exception => excp } 25 | 26 | @response['Content-Type'] = 'text/html' 27 | @response.status = 500 28 | 29 | obj = Object.new.extend(TemplateHelpers) 30 | Tilt::HamlTemplate.new(template).render(obj, locals = locals) 31 | end 32 | 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/frank/settings.rb: -------------------------------------------------------------------------------- 1 | require 'singleton' 2 | require 'ostruct' 3 | module Frank 4 | class Settings 5 | include Singleton 6 | 7 | attr_accessor :environment 8 | attr_accessor :root 9 | 10 | attr_accessor :server 11 | attr_accessor :options 12 | attr_accessor :static_folder 13 | attr_accessor :dynamic_folder 14 | attr_accessor :layouts_folder 15 | attr_accessor :export 16 | attr_accessor :publish 17 | attr_accessor :sass_options 18 | attr_accessor :haml_options 19 | 20 | def initialize 21 | reset 22 | end 23 | 24 | # Reset settings to the defaults 25 | def reset 26 | # reset server settings 27 | @server = OpenStruct.new 28 | @server.handler = "mongrel" 29 | @server.hostname = "0.0.0.0" 30 | @server.port = "3601" 31 | 32 | # reset options 33 | @options = OpenStruct.new 34 | 35 | # export settings 36 | @export = OpenStruct.new 37 | @export.path = "exported" 38 | @export.silent = false 39 | @export.force = false 40 | 41 | # publish options 42 | @publish = OpenStruct.new 43 | @publish.host = nil 44 | @publish.path = nil 45 | @publish.username = nil 46 | @publish.password = nil 47 | @publish.mode = nil 48 | 49 | # setup folders 50 | @static_folder = "static" 51 | @dynamic_folder = "dynamic" 52 | @layouts_folder = "layouts" 53 | 54 | # setup 3rd party configurations 55 | @sass_options = {} 56 | @haml_options = {} 57 | end 58 | 59 | # return the proj folder name 60 | def proj_name 61 | @root.split('/').last 62 | end 63 | 64 | # Are we serving up a raw static folder? 65 | def serving_static? 66 | @serving_static 67 | end 68 | 69 | # Mark this Frank run as serving static 70 | def serving_static! 71 | @serving_static = true 72 | end 73 | 74 | # Check to see if we're compiling 75 | def exporting? 76 | @exporting 77 | end 78 | 79 | # Mark this Frank run as compiling 80 | def exporting! 81 | @exporting = true 82 | end 83 | 84 | # Silent export if set or in test 85 | def silent_export? 86 | @environment == :test || @export.silent 87 | end 88 | 89 | # Check to see if we're in production mode 90 | def production? 91 | @production 92 | end 93 | 94 | # Mark this Frank run as production 95 | def production! 96 | @production = true 97 | end 98 | 99 | # Force overwrite export folder if it exists 100 | def force_export! 101 | @export.force = true 102 | end 103 | 104 | # Mark this Frank run as publishing 105 | def publishing! 106 | @exporting = true 107 | @production = true 108 | end 109 | 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/frank/template_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'frank/lorem' 2 | 3 | module Frank 4 | module TemplateHelpers 5 | include FrankHelpers if defined? FrankHelpers 6 | 7 | def render_partial(path, *locals) 8 | pieces = path.split('/') 9 | partial = '_' + pieces.pop 10 | locals = locals.empty? ? nil : locals[0] 11 | render(File.join(pieces.join('/'), partial), partial = true, locals) 12 | end 13 | 14 | def lorem 15 | Frank::Lorem.new 16 | end 17 | 18 | def content_for(name, &block) 19 | @content_for ||= {} 20 | 21 | if block_given? 22 | @content_for[name.to_sym] = capture(&block) 23 | else 24 | @content_for[name.to_sym] 25 | end 26 | end 27 | 28 | def content_for?(name) 29 | !@content_for[name.to_sym].nil? 30 | end 31 | 32 | def capture(&block) 33 | erbout = eval('_erbout', block.binding) 34 | erbout_length = erbout.length 35 | 36 | block.call 37 | 38 | erbout_addition = erbout[erbout_length..-1] 39 | erbout[erbout_length..-1] = '' 40 | 41 | erbout_addition 42 | end 43 | 44 | def refresh 45 | if Frank.exporting? 46 | nil 47 | else 48 | <<-JS 49 | 73 | JS 74 | end 75 | end 76 | 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/frank/templates/404.haml: -------------------------------------------------------------------------------- 1 | !!! Strict 2 | %html 3 | %head 4 | %title= "404 – Not Found" 5 | %style{:type=>'text/css'} 6 | :plain 7 | body { color: #222; font:14px "Helvetica", "Arial"; line-height: 20px; } 8 | #wrapper { margin:0px auto; width:300px; } 9 | h1 { margin: 24px -30px 10px -3px; font: bold 48px Georgia; line-height:48px;} 10 | img { margin: 60px 0px 0px -22px; } 11 | tt { color: #4D9EEF; color:#999; font-family: Inconsolata, Monaco, monospace } 12 | =refresh 13 | %body 14 | #wrapper 15 | %img{:src=>'/__frank__/frank-404.png'} 16 | %h1= "Not Found—" 17 | - unless Frank.serving_static? 18 | %p 19 | - if request['REQUEST_PATH'] 20 | - path = request['REQUEST_PATH'][1..-1] 21 | = "Try creating" 22 | 23 | - if path.match(/\.css$/) 24 | = "#{path.match(/([\w\/]+)\./)[1]}.sass" 25 | - if path.match(/\.js$/) 26 | = "#{path.match(/([\w\/]+)\./)[1]}.coffee" 27 | - else 28 | = "#{path.gsub(/\/$/, '')}.haml" 29 | = "in the #{dynamic_folder} folder, or" 30 | 31 | - if path.match(/\.\w+/) 32 | = "#{path}" 33 | - else 34 | = "#{path.gsub(/\/$/, '')}.html" 35 | = "in the #{static_folder} folder." -------------------------------------------------------------------------------- /lib/frank/templates/500.haml: -------------------------------------------------------------------------------- 1 | !!! Strict 2 | %html 3 | %head 4 | %title= "500 – Internal Server Error" 5 | %style{:type=>'text/css'} 6 | :plain 7 | body { color: #222; font:14px "Helvetica", "Arial"; line-height: 20px; } 8 | #wrapper { margin:0px auto; width:600px; } 9 | h1 { margin: 0px -30px 10px -3px; font: bold 48px Georgia; line-height:48px;} 10 | img { margin: 60px 0px 0px -51px; } 11 | tt, pre { color:#666; font-family: Inconsolata, Monaco, monospace; } 12 | pre { width:600px; padding-bottom:40px; } 13 | p.summary { font-size:18px; border-bottom:1px #ccc solid; margin-bottom:60px; padding-bottom:20px; } 14 | =refresh 15 | 16 | %script{:type=>'text/javascript'} 17 | %body 18 | #wrapper 19 | %img{:src=>'/__frank__/frank-500.png'} 20 | %h1= "Something’s Wrong—" 21 | %p.summary= "The error, « #{exception.message.gsub('<','<')} »
occurred in #{exception.backtrace.first.split(':')[0..1].join(' on line ')}." 22 | %pre 23 | :preserve 24 | #{ exception.backtrace.join("\n") } 25 | -------------------------------------------------------------------------------- /lib/frank/templates/frank-404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/frank-404.png -------------------------------------------------------------------------------- /lib/frank/templates/frank-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/frank-500.png -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank0.jpg -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank1.jpg -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank2.jpg -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank3.jpg -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank4.jpg -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank5.jpg -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank6.jpg -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank7.jpg -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank8.jpg -------------------------------------------------------------------------------- /lib/frank/templates/imager/frank9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/frank/templates/imager/frank9.jpg -------------------------------------------------------------------------------- /lib/frank/tilt_setup.rb: -------------------------------------------------------------------------------- 1 | require 'tilt' 2 | 3 | module Frank 4 | 5 | # Scss template implementation. See: 6 | # http://haml.hamptoncatlin.com/ 7 | # 8 | # Sass templates do not support object scopes, locals, or yield. 9 | class SassTemplate < Tilt::SassTemplate 10 | def prepare 11 | @engine = ::Sass::Engine.new(data, sass_options.merge(Frank.sass_options || {}).merge(:syntax => :sass)) 12 | end 13 | end 14 | Tilt.register 'sass', SassTemplate 15 | 16 | # Scss template implementation. See: 17 | # http://haml.hamptoncatlin.com/ 18 | # 19 | # Sass templates do not support object scopes, locals, or yield. 20 | class ScssTemplate < Tilt::SassTemplate 21 | def prepare 22 | @engine = ::Sass::Engine.new(data, sass_options.merge(Frank.sass_options || {}).merge(:syntax => :scss)) 23 | end 24 | end 25 | Tilt.register 'scss', ScssTemplate 26 | 27 | # Haml template implementation. See: 28 | # http://haml.hamptoncatlin.com/ 29 | class HamlTemplate < Tilt::HamlTemplate 30 | def prepare 31 | options = @options.merge(:filename => eval_file, :line => line) 32 | @engine = ::Haml::Engine.new(data, options.merge(Frank.haml_options || {})) 33 | end 34 | end 35 | Tilt.register 'haml', HamlTemplate 36 | 37 | # Radius Template 38 | # http://github.com/jlong/radius/ 39 | class RadiusTemplate < Tilt::Template 40 | def initialize_engine 41 | return if defined? ::Radius 42 | require_template_library 'radius' 43 | end 44 | 45 | def prepare 46 | @context = Class.new(Radius::Context).new 47 | end 48 | 49 | def evaluate(scope, locals, &block) 50 | @context.define_tag("yield") do 51 | block.call 52 | end 53 | (class << @context; self; end).class_eval do 54 | define_method :tag_missing do |tag, attr, &block| 55 | if locals.key?(tag.to_sym) 56 | locals[tag.to_sym] 57 | else 58 | scope.__send__(tag) # any way to support attr as args? 59 | end 60 | end 61 | end 62 | # TODO: how to config tag prefix? 63 | parser = Radius::Parser.new(@context, :tag_prefix => 'r') 64 | parser.parse(data) 65 | end 66 | end 67 | Tilt.register 'radius', RadiusTemplate 68 | end -------------------------------------------------------------------------------- /lib/frank/upgrades.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | module Upgrades 3 | 4 | def upgrade! 5 | version = detect_version 6 | 7 | if version == '0.3' 8 | upgrade_from_0_3! 9 | else 10 | puts "\033[32mLooks like you're already good to go!\033[0m" 11 | end 12 | end 13 | 14 | private 15 | 16 | def upgrade_from_0_3! 17 | settings = YAML.load_file(File.join(Frank.root, 'settings.yml')) 18 | setup = <<-SETUP 19 | # ---------------------- 20 | # Server settings: 21 | # 22 | # Change the server host/port to bind rack to. 23 | # 'server' can be any Rack-supported server, e.g. 24 | # Mongrel, Thin, WEBrick 25 | # 26 | Frank.server.handler = "#{settings['server']['handler']}" 27 | Frank.server.hostname = "#{settings['server']['hostname']}" 28 | Frank.server.port = "#{settings['server']['port']}" 29 | 30 | # ---------------------- 31 | # Static folder: 32 | # 33 | # All files in this folder will be served up 34 | # directly, without interpretation 35 | # 36 | Frank.static_folder = "#{settings['static_folder']}" 37 | 38 | # ---------------------- 39 | # Dynamic folder: 40 | # 41 | # Frank will try to interpret any of the files 42 | # in this folder based on their extension 43 | # 44 | Frank.dynamic_folder = "#{settings['dynamic_folder']}" 45 | 46 | # ---------------------- 47 | # Layouts folder: 48 | # 49 | # Frank will look for layouts in this folder 50 | # the default layout is `default' 51 | # it respects nested layouts that correspond to nested 52 | # folders in the `dynamic_folder' 53 | # for example: a template: `dynamic_folder/blog/a-blog-post.haml' 54 | # would look for a layout: `layouts/blog/default.haml' 55 | # and if not found use the default: `layouts/default.haml' 56 | # 57 | # Frank also supports defining layouts on an 58 | # individual template basis using meta data 59 | # you can do this by defining a meta field `layout: my_layout.haml' 60 | # 61 | Frank.layouts_folder = "#{settings['layouts_folder']}" 62 | 63 | 64 | # ---------------------- 65 | # Initializers: 66 | # 67 | # Add any other project setup code, or requires here 68 | # .... 69 | SETUP 70 | 71 | puts " - \033[32mConverting\033[0m settings.yml => setup.rb" 72 | 73 | File.open(File.join(Frank.root, 'setup.rb'), 'w') { |file| file.write(setup.gsub(/^\s+(?=[^\s])/, '')) } 74 | File.delete(File.join(Frank.root, 'settings.yml')) 75 | 76 | puts "\n \033[32mUpdate is complete, enjoy!\033[0m" 77 | end 78 | 79 | def detect_version 80 | if File.exist? File.join(Frank.root, 'settings.yml') 81 | version = '0.3' 82 | else 83 | version = '<0.3' 84 | end 85 | 86 | version 87 | end 88 | 89 | end 90 | end -------------------------------------------------------------------------------- /lib/frank/version.rb: -------------------------------------------------------------------------------- 1 | module Frank 2 | VERSION = '1.0.12' 3 | end -------------------------------------------------------------------------------- /lib/template/dynamic/css/frank.sass: -------------------------------------------------------------------------------- 1 | body 2 | color: #222 3 | font-family: Helvetica, Arial 4 | font-size: 14px 5 | line-height: 20px 6 | 7 | ul, ol 8 | margin: 0 9 | padding: 0 10 | 11 | h1 12 | margin: 0 0 10px 13 | font: bold 72px Georgia 14 | h2 15 | margin: 0 16 | font-size: 24px 17 | line-height: 32px 18 | color: #cecece 19 | 20 | em 21 | color: #4D9EEF 22 | font-family: Inconsolata, Monaco, monospace 23 | font-style: normal 24 | 25 | #wrapper 26 | position: relative 27 | width: 600px 28 | margin: 0px auto 29 | 30 | #tooltip 31 | position: absolute 32 | top: 80px 33 | left: 380px 34 | color: #4D9EEF 35 | font-weight: bold 36 | z-index: 20 37 | cursor: pointer 38 | 39 | #header, #help 40 | position: absolute 41 | left: 300px 42 | width: 300px 43 | margin-left: -150px 44 | 45 | #header 46 | width: 350px 47 | height: 600px 48 | margin-left: -175px 49 | background: #fff 50 | z-index: 10 51 | top: 60px 52 | cursor: pointer 53 | 54 | h2 55 | width: 300px 56 | img 57 | margin-left: -25px 58 | h1 59 | margin-left: -3px 60 | 61 | #help 62 | top: 150px 63 | border-top: 1px #7a7a7a solid 64 | 65 | li 66 | font-weight: bold 67 | color: #ddd 68 | p 69 | font-weight: normal 70 | color: #222 71 | 72 | 73 | -------------------------------------------------------------------------------- /lib/template/dynamic/index.haml: -------------------------------------------------------------------------------- 1 | #tooltip 2 | Click for help. 3 | 4 | #header 5 | %img{:src=>'/images/frank-med.png'} 6 | %h1 Frank 7 | %h2 Relax, fella. Frank's got it under control. 8 | 9 | %ol#help 10 | %li 11 | %p= "Edit setup.rb to set your server options, folder names for static/dynamic content, and layouts for your views." 12 | %li 13 | %p= "Stash your static files (images, flash movies, etc) in the folder that you set as [static_folder]." 14 | %li 15 | %p= "Frank will look for files in the [dynamic_folder] first and try to compile them." 16 | %p= "Example: a request for /css/blah.css will look for a file in [dynamic_folder]/css/ named blah. Frank uses the file extension to determine the correct compiler." -------------------------------------------------------------------------------- /lib/template/helpers.rb: -------------------------------------------------------------------------------- 1 | module FrankHelpers 2 | # helpers go here 3 | end -------------------------------------------------------------------------------- /lib/template/layouts/default.haml: -------------------------------------------------------------------------------- 1 | !!! Strict 2 | %html 3 | %head 4 | %title Frank's on the scene. 5 | %link{:rel=>'stylesheet', :type=>'text/css', :href=>'/css/frank.css'} 6 | %script{:type=>'text/javascript', :src=>'/js/frank.js'} 7 | %body 8 | #wrapper 9 | = yield -------------------------------------------------------------------------------- /lib/template/setup.rb: -------------------------------------------------------------------------------- 1 | # ---------------------- 2 | # Server settings: 3 | # 4 | # Change the server host/port to bind rack to. 5 | # 'server' can be any Rack-supported server, e.g. 6 | # Mongrel, Thin, WEBrick 7 | # 8 | Frank.server.handler = "mongrel" 9 | Frank.server.hostname = "0.0.0.0" 10 | Frank.server.port = "3601" 11 | 12 | # ---------------------- 13 | # Static folder: 14 | # 15 | # All files in this folder will be served up 16 | # directly, without interpretation 17 | # 18 | Frank.static_folder = "static" 19 | 20 | # ---------------------- 21 | # Dynamic folder: 22 | # 23 | # Frank will try to interpret any of the files 24 | # in this folder based on their extension 25 | # 26 | Frank.dynamic_folder = "dynamic" 27 | 28 | # ---------------------- 29 | # Layouts folder: 30 | # 31 | # Frank will look for layouts in this folder 32 | # the default layout is `default' 33 | # it respects nested layouts that correspond to nested 34 | # folders in the `dynamic_folder' 35 | # for example: a template: `dynamic_folder/blog/a-blog-post.haml' 36 | # would look for a layout: `layouts/blog/default.haml' 37 | # and if not found use the default: `layouts/default.haml' 38 | # 39 | # Frank also supports defining layouts on an 40 | # individual template basis using meta data 41 | # you can do this by defining a meta field `layout: my_layout.haml' 42 | # 43 | Frank.layouts_folder = "layouts" 44 | 45 | # ---------------------- 46 | # Export settings: 47 | # 48 | # Projects will be exported to default to the following directory 49 | # 50 | Frank.export.path = "exported" 51 | 52 | # ---------------------- 53 | # Publish settings: 54 | # 55 | # Frank can publish your exported project to 56 | # a remote server via scp (default) or ftp. All you have to do is tell Frank what host, path, and username. 57 | # Depending on the chosen mode, you may need to install net-scp or net-sftp. 58 | # If you have ssh keys setup there is no need for a password. 59 | # Just uncomment the Publish settings below and 60 | # make the appropriate changes. 61 | # 62 | # Frank.publish.host = "example.com" 63 | # Frank.publish.path = "/www" 64 | # Frank.publish.username = 'me' 65 | # Frank.publish.password = 'secret' 66 | # Frank.publish.port = 22 67 | # Frank.publish.mode = :scp (or :ftp, :ftptls, :sftp) #no ftptls in ruby 1.9 68 | # 69 | # 70 | 71 | # ---------------------- 72 | # Sass Options: 73 | # Frank.sass_options = { :load_paths => [ File.join(File.dirname(__FILE__), 'dynamic/css') ] } 74 | 75 | 76 | # ---------------------- 77 | # Initializers: 78 | # 79 | # Add any other project setup code, or requires here 80 | # .... 81 | -------------------------------------------------------------------------------- /lib/template/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/template/static/favicon.ico -------------------------------------------------------------------------------- /lib/template/static/images/frank-med.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trvsdnn/frank/0414c9f1a3b9158aecc4a47e9194a79706f76041/lib/template/static/images/frank-med.png -------------------------------------------------------------------------------- /lib/template/static/js/frank.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var collapse, easeInOutQuad, expand, expanded, expanding, init, slide, toggle; 3 | // ----------------------------------------------- 4 | // 5 | // This is pretty gross; Gotta love what those 6 | // JS libraries do for you 7 | // 8 | // - 9 | // Easing function borrowed from jQuery.easing 10 | easeInOutQuad = function easeInOutQuad(t, b, c, d) { 11 | return ((t /= d / 2) < 1) && (c / 2 * t * t + b) || (-c / 2 * ((--t) * (t - 2) - 1) + b); 12 | }; 13 | // Expand / Collapse help info 14 | expanded = false; 15 | expanding = false; 16 | slide = function slide(el, end, duration, start, now) { 17 | now = now || 0.001; 18 | start = start || parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue('left')); 19 | el.style.left = Math.round(start + (end - start) * easeInOutQuad(now, 0, 1, duration)) + 'px'; 20 | now += 30; 21 | return now < duration ? setTimeout(function() { 22 | return slide(el, end, duration, start, now); 23 | }, 30) : expanding = false; 24 | }; 25 | expand = function expand() { 26 | document.getElementById('tooltip').style.display = 'none'; 27 | slide(document.getElementById('header'), 75, 600); 28 | return slide(document.getElementById('help'), 525, 600); 29 | }; 30 | collapse = function collapse() { 31 | document.getElementById('tooltip').style.display = 'block'; 32 | slide(document.getElementById('header'), 300, 600); 33 | return slide(document.getElementById('help'), 300, 600); 34 | }; 35 | toggle = function toggle(e) { 36 | if (expanding) { 37 | return false; 38 | } 39 | expanded = !expanded; 40 | expanding = true; 41 | expanded ? expand() : collapse(); 42 | return false; 43 | }; 44 | init = function init() { 45 | return document.addEventListener('click', toggle, false); 46 | }; 47 | window.onload = init; 48 | })(); -------------------------------------------------------------------------------- /spec/base_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Frank::Base do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Frank.bootstrap(File.join(File.dirname(__FILE__), 'template')) 8 | Frank.new do 9 | # this is just used for a test 10 | @blowup_sometimes = true 11 | end 12 | end 13 | 14 | it 'has all of the required settings set' do 15 | app 16 | Frank.root.should_not be_nil 17 | Frank.server.handler.should_not be_nil 18 | Frank.server.hostname.should_not be_nil 19 | Frank.server.port.should_not be_nil 20 | Frank.static_folder.should_not be_nil 21 | Frank.dynamic_folder.should_not be_nil 22 | Frank.layouts_folder.should_not be_nil 23 | end 24 | 25 | it 'renders a dynamic template given a request' do 26 | get '/' 27 | 28 | last_response.should be_ok 29 | last_response.body.strip.should == "
/
\n
\n

hello worlds

\n

/

\n
" 30 | end 31 | 32 | it 'renders a page and uses a helper' do 33 | get '/helper_test' 34 | 35 | last_response.should be_ok 36 | last_response.body.strip.should == "
/helper_test
\n
\n

hello from helper

\n
" 37 | end 38 | 39 | it 'renders a nested template given a request' do 40 | get '/nested/child' 41 | 42 | last_response.should be_ok 43 | last_response.body.should == "
\n

hello from child

\n
\n" 44 | end 45 | 46 | it 'renders dynamic css without a layout' do 47 | get '/stylesheets/sass.css' 48 | 49 | last_response.should be_ok 50 | last_response.body.should include("#hello-worlds {\n background: red;\n}\n") 51 | end 52 | 53 | it 'renders a 404 page if template not found' do 54 | capture_stdout { get '/not_here.css' } 55 | 56 | last_response.should_not be_ok 57 | last_response.content_type.should == 'text/html' 58 | last_response.body.should =~ /Not Found/ 59 | end 60 | 61 | it 'renders a 500 page for error' do 62 | capture_stdout { get '/500' } 63 | last_response.should_not be_ok 64 | last_response.content_type.should == 'text/html' 65 | last_response.body.should =~ /undefined local variable or method `non_method'/ 66 | end 67 | 68 | it 'stubs out a project' do 69 | out = capture_stdout { Frank.stub('stubbed') } 70 | Dir.entries('stubbed').should == Dir.entries(File.join(LIBDIR, 'template')) 71 | response = "\nFrank is...\n - \e[32mCreating\e[0m your project 'stubbed'\n - \e[32mCopying\e[0m default Frank template\n\n \e[32mCongratulations, 'stubbed' is ready to go!\e[0m\n" 72 | out.string.should == response 73 | end 74 | 75 | after(:all) do 76 | FileUtils.rm_r File.join(Dir.pwd, 'stubbed') 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /spec/compile_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Frank::Compile do 4 | include Rack::Test::Methods 5 | include Frank::Spec::Helpers 6 | 7 | let(:proj_dir) { File.join(File.dirname(__FILE__), 'template') } 8 | 9 | context 'default output' do 10 | 11 | context 'without specifying an export dir' do 12 | after do 13 | FileUtils.rm_r File.join(proj_dir, Frank.export.path) 14 | end 15 | 16 | it 'creates the default export dir' do 17 | Frank.bootstrap(proj_dir) 18 | 19 | Dir.chdir proj_dir do 20 | frank 'export' 21 | 22 | File.directory?(Frank.export.path).should be_true 23 | end 24 | end 25 | end 26 | 27 | context 'specifying an export dir' do 28 | let(:output_dir) { File.join(proj_dir, 'output') } 29 | 30 | before(:all) do 31 | Dir.chdir proj_dir do 32 | frank 'export', output_dir 33 | end 34 | end 35 | 36 | after(:all) do 37 | FileUtils.rm_r output_dir 38 | end 39 | 40 | it 'creates the output folder' do 41 | File.exist?(File.join(File.dirname(__FILE__), 'template/output')).should be_true 42 | end 43 | 44 | it 'creates index.html' do 45 | output = File.join(File.dirname(__FILE__), 'template/output/index.html') 46 | File.read(output).should == "\n
/
\n
\n

hello worlds

\n

/

\n
\n" 47 | end 48 | 49 | it 'creates partial_test.html' do 50 | output = File.join(File.dirname(__FILE__), 'template/output/partial_test.html') 51 | File.read(output).should == "\n
/partial_test
\n
\n

hello worlds

\n

/partial_test

\n

hello from partial

\n
\n" 52 | end 53 | 54 | it 'creates partial_locals_test.html' do 55 | output = File.join(File.dirname(__FILE__), 'template/output/partial_locals_test.html') 56 | File.read(output).should == "\n
/partial_locals_test
\n
\n

hello worlds

\n

/partial_locals_test

\n

hello from local

\n
\n" 57 | end 58 | 59 | it 'creates child.html' do 60 | output = File.join(File.dirname(__FILE__), 'template/output/nested/child.html') 61 | File.read(output).should == "
\n

hello from child

\n
\n" 62 | end 63 | 64 | it 'creates deep.html' do 65 | output = File.join(File.dirname(__FILE__), 'template/output/nested/deeper/deep.html') 66 | File.read(output).should == "
\n

really deep

\n
\n" 67 | end 68 | 69 | it 'creates no_layout.html' do 70 | output = File.join(File.dirname(__FILE__), 'template/output/no_layout.html') 71 | File.read(output).should == "

i have no layout

\n" 72 | end 73 | 74 | it 'creates coffee.js' do 75 | output = File.join(File.dirname(__FILE__), 'template/output/coffee.js') 76 | File.read(output).should == "(function() {\n ({\n greeting: \"Hello CoffeeScript\"\n });\n}).call(this);\n" 77 | end 78 | 79 | it 'creates erb.html' do 80 | output = File.join(File.dirname(__FILE__), 'template/output/erb.html') 81 | File.read(output).should == "\n
/erb
\n
\n

hello worlds

\n
\n" 82 | end 83 | 84 | it 'creates redcloth.html' do 85 | output = File.join(File.dirname(__FILE__), 'template/output/redcloth.html') 86 | File.read(output).should == "\n
/redcloth
\n
\n

hello worlds

\n
\n" 87 | end 88 | 89 | it 'creates markdown.html' do 90 | output = File.join(File.dirname(__FILE__), 'template/output/markdown.html') 91 | File.read(output).should == "\n
/markdown
\n
\n

hello worlds

\n
\n" 92 | end 93 | 94 | it 'creates liquid.html' do 95 | output = File.join(File.dirname(__FILE__), 'template/output/liquid.html') 96 | File.read(output).should == "\n
/liquid
\n
\n

hello worlds

\n
\n" 97 | end 98 | 99 | it 'creates builder.html' do 100 | output = File.join(File.dirname(__FILE__), 'template/output/builder.html') 101 | File.read(output).should == "\n
/builder
\n
\n

hello worlds

\n
\n" 102 | end 103 | 104 | it 'copies static.html' do 105 | output = File.join(File.dirname(__FILE__), 'template/output/files/static.html') 106 | File.read(output).should == "hello from static" 107 | end 108 | 109 | it "doesn't create partials" do 110 | File.exist?(File.join(File.dirname(__FILE__), 'template/output/_partial.html')).should be_false 111 | end 112 | 113 | it 'handles lorem replacement fields' do 114 | output = File.join(File.dirname(__FILE__), 'template/output/lorem_test.html') 115 | File.read(output).should include("

replace-this

") 116 | File.read(output).should include("

replace-this

") 117 | File.read(output).should include("

replace-this

") 118 | File.read(output).should include("

replace-this

") 119 | File.read(output).should include("

replace-this

") 120 | File.read(output).should include("") 121 | File.read(output).should include("") 122 | end 123 | 124 | it 'should not render the refresh js' do 125 | output = File.join(File.dirname(__FILE__), 'template/output/refresh.html') 126 | File.read(output).should == "\n
/refresh
\n
\n \n
\n" 127 | end 128 | end 129 | 130 | end 131 | 132 | context 'productions output' do 133 | 134 | let(:output_dir) { File.join(proj_dir, 'output') } 135 | 136 | before :all do 137 | Dir.chdir proj_dir do 138 | frank 'export', output_dir, '--production' 139 | end 140 | end 141 | 142 | after(:all) do 143 | FileUtils.rm_r output_dir 144 | end 145 | 146 | it 'creates the output folder' do 147 | File.exist?(File.join(File.dirname(__FILE__), 'template/output')).should be_true 148 | end 149 | 150 | it 'creates index.html' do 151 | output = File.join(File.dirname(__FILE__), 'template/output/index.html') 152 | File.read(output).should == "\n
/
\n
\n

hello worlds

\n

/

\n
\n" 153 | end 154 | 155 | it 'creates partial_test.html' do 156 | output = File.join(File.dirname(__FILE__), 'template/output/partial_test/index.html') 157 | File.read(output).should == "\n
/partial_test
\n
\n

hello worlds

\n

/partial_test

\n

hello from partial

\n
\n" 158 | end 159 | 160 | it 'creates partial_locals_test.html' do 161 | output = File.join(File.dirname(__FILE__), 'template/output/partial_locals_test/index.html') 162 | File.read(output).should == "\n
/partial_locals_test
\n
\n

hello worlds

\n

/partial_locals_test

\n

hello from local

\n
\n" 163 | end 164 | 165 | it 'creates child.html' do 166 | output = File.join(File.dirname(__FILE__), 'template/output/nested/child/index.html') 167 | File.read(output).should == "
\n

hello from child

\n
\n" 168 | end 169 | 170 | it 'creates deep.html' do 171 | output = File.join(File.dirname(__FILE__), 'template/output/nested/deeper/deep/index.html') 172 | File.read(output).should == "
\n

really deep

\n
\n" 173 | end 174 | 175 | it 'creates no_layout.html' do 176 | output = File.join(File.dirname(__FILE__), 'template/output/no_layout/index.html') 177 | File.read(output).should == "

i have no layout

\n" 178 | end 179 | 180 | it 'creates coffee.js' do 181 | output = File.join(File.dirname(__FILE__), 'template/output/coffee.js') 182 | File.read(output).should == "(function() {\n ({\n greeting: \"Hello CoffeeScript\"\n });\n}).call(this);\n" 183 | end 184 | 185 | it 'creates erb.html' do 186 | output = File.join(File.dirname(__FILE__), 'template/output/erb/index.html') 187 | File.read(output).should == "\n
/erb
\n
\n

hello worlds

\n
\n" 188 | end 189 | 190 | it 'creates redcloth.html' do 191 | output = File.join(File.dirname(__FILE__), 'template/output/redcloth/index.html') 192 | File.read(output).should == "\n
/redcloth
\n
\n

hello worlds

\n
\n" 193 | end 194 | 195 | it 'creates markdown.html' do 196 | output = File.join(File.dirname(__FILE__), 'template/output/markdown/index.html') 197 | File.read(output).should == "\n
/markdown
\n
\n

hello worlds

\n
\n" 198 | end 199 | 200 | it 'creates liquid.html' do 201 | output = File.join(File.dirname(__FILE__), 'template/output/liquid/index.html') 202 | File.read(output).should == "\n
/liquid
\n
\n

hello worlds

\n
\n" 203 | end 204 | 205 | it 'creates builder.html' do 206 | output = File.join(File.dirname(__FILE__), 'template/output/builder/index.html') 207 | File.read(output).should == "\n
/builder
\n
\n

hello worlds

\n
\n" 208 | end 209 | 210 | it 'copies static.html' do 211 | output = File.join(File.dirname(__FILE__), 'template/output/files/static.html') 212 | File.read(output).should == "hello from static" 213 | end 214 | 215 | it "doesn't create partials" do 216 | File.exist?(File.join(File.dirname(__FILE__), 'template/output/_partial/index.html')).should be_false 217 | end 218 | 219 | it 'handles lorem replacement fields' do 220 | output = File.join(File.dirname(__FILE__), 'template/output/lorem_test/index.html') 221 | File.read(output).should include("

replace-this

") 222 | File.read(output).should include("

replace-this

") 223 | File.read(output).should include("

replace-this

") 224 | File.read(output).should include("

replace-this

") 225 | File.read(output).should include("

replace-this

") 226 | File.read(output).should include("") 227 | File.read(output).should include("") 228 | end 229 | 230 | it 'should not render the refresh js' do 231 | output = File.join(File.dirname(__FILE__), 'template/output/refresh/index.html') 232 | File.read(output).should == "\n
/refresh
\n
\n \n
\n" 233 | end 234 | 235 | end 236 | 237 | end 238 | -------------------------------------------------------------------------------- /spec/publish/base_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require File.expand_path('../../spec_helper.rb', __FILE__) 4 | require 'frank/publish/base' 5 | 6 | 7 | describe Frank::Publish::Base do 8 | 9 | let(:publisher) do 10 | Frank::Publish::Base.new(Frank.publish) 11 | end 12 | 13 | before(:all) do 14 | Frank.bootstrap(File.join(File.dirname(__FILE__), '..', 'template')) 15 | end 16 | 17 | describe '#initialize' do 18 | 19 | it 'should set the correct values' do 20 | publisher.username.should == 'test' 21 | publisher.password.should == 'secret' 22 | publisher.hostname.should == 'example.com' 23 | publisher.remote_path.should == '/remote/path' 24 | end 25 | 26 | it 'should set the local export path' do 27 | publisher.local_path =~ /\/tmp\/frank-publish-template-\d+\// 28 | end 29 | 30 | end 31 | 32 | describe '#permform!' do 33 | 34 | before do 35 | publisher.stubs(:export!) 36 | publisher.stubs(:transfer!) 37 | publisher.stubs(:cleanup!) 38 | end 39 | 40 | it 'call export, transfer, cleanup ins that order' do 41 | publisher.expects(:export!) 42 | publisher.expects(:transfer!) 43 | publisher.expects(:cleanup!) 44 | 45 | publisher.perform! 46 | end 47 | 48 | end 49 | 50 | describe '#files_to_transfer' do 51 | 52 | it 'should list empty directories if export does not exists' do 53 | publisher.send(:files_to_transfer).should be_empty 54 | end 55 | 56 | it 'should list all files' do 57 | publisher.send(:export!) 58 | publisher.send(:files_to_transfer).should have(26).elements 59 | publisher.send(:files_to_transfer).should include("500.html") 60 | publisher.send(:files_to_transfer).should_not include("files") 61 | end 62 | 63 | it 'should not list hidden files beginning with .' do 64 | publisher.send(:export!) 65 | publisher.send(:files_to_transfer).each do |elem| 66 | elem.should_not =~ /^\./ 67 | end 68 | end 69 | 70 | after do 71 | publisher.send(:cleanup!) 72 | end 73 | 74 | end 75 | 76 | describe '#directories' do 77 | 78 | it 'should list empty directories if export does not exists' do 79 | publisher.send(:directories).should be_empty 80 | end 81 | 82 | it 'should list all directories' do 83 | publisher.send(:export!) 84 | publisher.send(:directories).should include("files", "nested", "nested/deeper", "stylesheets") 85 | end 86 | 87 | it 'should not list . or ..' do 88 | publisher.send(:export!) 89 | publisher.send(:directories).should_not include('.', '..') 90 | end 91 | 92 | after do 93 | publisher.send(:cleanup!) 94 | end 95 | 96 | end 97 | 98 | describe '#export!' do 99 | 100 | it 'should export the project to the tmp folder' do 101 | publisher.send(:export!) 102 | 103 | publisher.local_path.should =~ /\/tmp\/frank-publish-template-\d+/ 104 | File.exist?(publisher.local_path).should be_true 105 | end 106 | 107 | after do 108 | publisher.send(:cleanup!) 109 | end 110 | 111 | end 112 | 113 | describe '#cleanup!' do 114 | 115 | it 'should not leave the export folder in the tmp folder' do 116 | publisher.send(:export!) 117 | publisher.send(:cleanup!) 118 | 119 | publisher.local_path.should =~ /\/tmp\/frank-publish-template-\d+/ 120 | File.exist?(publisher.local_path).should_not be_true 121 | end 122 | 123 | end 124 | 125 | end 126 | -------------------------------------------------------------------------------- /spec/publish/ftp_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require File.expand_path('../../spec_helper.rb', __FILE__) 4 | require 'frank/publish/ftp' 5 | 6 | describe Frank::Publish::FTP do 7 | 8 | let(:publisher) do 9 | Frank::Publish::FTP.new(Frank.publish) do |ftp| 10 | ftp.username = 'my_username' 11 | ftp.password = 'my_password' 12 | ftp.hostname = 'ftp.example.com' 13 | ftp.local_path = '/local/path' 14 | ftp.remote_path = '/remote/path' 15 | end 16 | end 17 | 18 | before(:all) do 19 | Frank.bootstrap(File.join(File.dirname(__FILE__), 'template')) 20 | end 21 | 22 | describe '#initialize' do 23 | it 'should set the correct values' do 24 | publisher.username.should == 'my_username' 25 | publisher.password.should == 'my_password' 26 | publisher.hostname.should == 'ftp.example.com' 27 | publisher.port.should == 21 28 | publisher.local_path.should == '/local/path' 29 | publisher.remote_path.should == 'remote/path' 30 | 31 | end 32 | 33 | it 'should remove any preceeding tilde and slash from the path' do 34 | publisher = Frank::Publish::FTP.new(Frank.publish) do |ftp| 35 | ftp.remote_path = '~/my_backups/path' 36 | end 37 | publisher.remote_path.should == 'my_backups/path' 38 | end 39 | 40 | context 'when setting configuration defaults' do 41 | 42 | 43 | end # context 'when setting configuration defaults' 44 | 45 | end # describe '#initialize' 46 | 47 | describe '#connection' do 48 | let(:connection) { mock } 49 | 50 | it 'should yield a connection to the remote server' do 51 | Net::FTP.expects(:open).with( 52 | 'ftp.example.com', 'my_username', 'my_password' 53 | ).yields(connection) 54 | 55 | connection.expects(:passive=).with(true) 56 | 57 | publisher.send(:connection) do |ftp| 58 | ftp.should be(connection) 59 | end 60 | end 61 | 62 | it 'should set the Net::FTP_PORT constant' do 63 | publisher.port = 40 64 | Net::FTP.expects(:const_defined?).with(:FTP_PORT).returns(true) 65 | Net::FTP.expects(:send).with(:remove_const, :FTP_PORT) 66 | Net::FTP.expects(:send).with(:const_set, :FTP_PORT, 40) 67 | 68 | Net::FTP.expects(:open) 69 | publisher.send(:connection) 70 | end 71 | 72 | end # describe '#connection' 73 | 74 | describe '#transfer!' do 75 | let(:connection) { mock } 76 | let(:package) { mock } 77 | let(:files) { ["file1", "file2", "subdir1/file3", "subdir2/file4"] } 78 | let(:dirs) { ["subdir1", "subdir2"] } 79 | let(:s) { sequence '' } 80 | 81 | before do 82 | publisher.stubs(:connection).yields(connection) 83 | publisher.stubs(:files_to_transfer).returns(files) 84 | publisher.stubs(:directories).returns(dirs) 85 | end 86 | 87 | it 'should transfer the files' do 88 | 89 | 90 | # connection.expects(:chdir).in_sequence(s).with('remote/path') 91 | 92 | #publisher.expects(:directories).in_sequence(s) 93 | 94 | publisher.expects(:create_remote_path).in_sequence(s).with('remote/path/subdir1', connection) 95 | publisher.expects(:create_remote_path).in_sequence(s).with('remote/path/subdir2', connection) 96 | 97 | connection.expects(:put).in_sequence(s).with( 98 | File.join('/local/path', 'file1'), 99 | File.join('remote/path', 'file1') 100 | ) 101 | 102 | connection.expects(:put).in_sequence(s).with( 103 | File.join('/local/path', 'file2'), 104 | File.join('remote/path', 'file2') 105 | ) 106 | 107 | connection.expects(:put).in_sequence(s).with( 108 | File.join('/local/path', 'subdir1/file3'), 109 | File.join('remote/path', 'subdir1/file3') 110 | ) 111 | 112 | connection.expects(:put).in_sequence(s).with( 113 | File.join('/local/path', 'subdir2/file4'), 114 | File.join('remote/path', 'subdir2/file4') 115 | ) 116 | 117 | publisher.send(:transfer!) 118 | end 119 | end # describe '#transfer!' 120 | 121 | 122 | describe '#create_remote_path' do 123 | let(:connection) { mock } 124 | let(:remote_path) { 'remote/folder/another_folder' } 125 | let(:s) { sequence '' } 126 | 127 | context 'while properly creating remote directories one by one' do 128 | it 'should rescue any FTPPermErrors and continue' do 129 | connection.expects(:mkdir).in_sequence(s). 130 | with("remote").raises(Net::FTPPermError) 131 | connection.expects(:mkdir).in_sequence(s). 132 | with("remote/folder") 133 | connection.expects(:mkdir).in_sequence(s). 134 | with("remote/folder/another_folder") 135 | 136 | expect do 137 | publisher.send(:create_remote_path, remote_path, connection) 138 | end.not_to raise_error 139 | end 140 | end 141 | end 142 | 143 | end 144 | -------------------------------------------------------------------------------- /spec/publish/ftptls_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require File.expand_path('../../spec_helper.rb', __FILE__) 4 | require 'frank/publish/ftptls' 5 | 6 | describe Frank::Publish::FTPTLS do 7 | 8 | let(:publisher) do 9 | Frank::Publish::FTPTLS.new(Frank.publish) do |ftp| 10 | ftp.username = 'my_username' 11 | ftp.password = 'my_password' 12 | ftp.hostname = 'ftp.example.com' 13 | ftp.local_path = '/local/path' 14 | ftp.remote_path = '/remote/path' 15 | end 16 | end 17 | 18 | before(:all) do 19 | Frank.bootstrap(File.join(File.dirname(__FILE__), 'template')) 20 | end 21 | 22 | describe '#initialize' do 23 | it 'should set the correct values' do 24 | publisher.username.should == 'my_username' 25 | publisher.password.should == 'my_password' 26 | publisher.hostname.should == 'ftp.example.com' 27 | publisher.port.should == 21 28 | publisher.local_path.should == '/local/path' 29 | publisher.remote_path.should == 'remote/path' 30 | 31 | end 32 | 33 | it 'should remove any preceeding tilde and slash from the path' do 34 | publisher = Frank::Publish::FTPTLS.new(Frank.publish) do |ftp| 35 | ftp.remote_path = '~/my_backups/path' 36 | end 37 | publisher.remote_path.should == 'my_backups/path' 38 | end 39 | 40 | context 'when setting configuration defaults' do 41 | 42 | 43 | end # context 'when setting configuration defaults' 44 | 45 | end # describe '#initialize' 46 | 47 | describe '#connection' do 48 | let(:connection) { mock } 49 | 50 | it 'should yield a connection to the remote server' do 51 | Net::FTPTLS.expects(:open).with( 52 | 'ftp.example.com', 'my_username', 'my_password' 53 | ).yields(connection) 54 | 55 | connection.expects(:passive=).with(true) 56 | 57 | publisher.send(:connection) do |ftp| 58 | ftp.should be(connection) 59 | end 60 | end 61 | 62 | it 'should set the Net::FTP_PORT constant' do 63 | publisher.port = 40 64 | Net::FTPTLS.expects(:const_defined?).with(:FTP_PORT).returns(true) 65 | Net::FTPTLS.expects(:send).with(:remove_const, :FTP_PORT) 66 | Net::FTPTLS.expects(:send).with(:const_set, :FTP_PORT, 40) 67 | 68 | Net::FTPTLS.expects(:open) 69 | publisher.send(:connection) 70 | end 71 | 72 | end # describe '#connection' 73 | 74 | describe '#transfer!' do 75 | let(:connection) { mock } 76 | let(:package) { mock } 77 | let(:files) { ["file1", "file2", "subdir1/file3", "subdir2/file4"] } 78 | let(:dirs) { ["subdir1", "subdir2"] } 79 | let(:s) { sequence '' } 80 | 81 | before do 82 | publisher.stubs(:connection).yields(connection) 83 | publisher.stubs(:files_to_transfer).returns(files) 84 | publisher.stubs(:directories).returns(dirs) 85 | end 86 | 87 | it 'should transfer the files' do 88 | 89 | 90 | # connection.expects(:chdir).in_sequence(s).with('remote/path') 91 | 92 | #publisher.expects(:directories).in_sequence(s) 93 | 94 | publisher.expects(:create_remote_path).in_sequence(s).with('remote/path/subdir1', connection) 95 | publisher.expects(:create_remote_path).in_sequence(s).with('remote/path/subdir2', connection) 96 | 97 | connection.expects(:put).in_sequence(s).with( 98 | File.join('/local/path', 'file1'), 99 | File.join('remote/path', 'file1') 100 | ) 101 | 102 | connection.expects(:put).in_sequence(s).with( 103 | File.join('/local/path', 'file2'), 104 | File.join('remote/path', 'file2') 105 | ) 106 | 107 | connection.expects(:put).in_sequence(s).with( 108 | File.join('/local/path', 'subdir1/file3'), 109 | File.join('remote/path', 'subdir1/file3') 110 | ) 111 | 112 | connection.expects(:put).in_sequence(s).with( 113 | File.join('/local/path', 'subdir2/file4'), 114 | File.join('remote/path', 'subdir2/file4') 115 | ) 116 | 117 | publisher.send(:transfer!) 118 | end 119 | end # describe '#transfer!' 120 | 121 | 122 | describe '#create_remote_path' do 123 | let(:connection) { mock } 124 | let(:remote_path) { 'remote/folder/another_folder' } 125 | let(:s) { sequence '' } 126 | 127 | context 'while properly creating remote directories one by one' do 128 | it 'should rescue any FTPPermErrors and continue' do 129 | connection.expects(:mkdir).in_sequence(s). 130 | with("remote").raises(Net::FTPPermError) 131 | connection.expects(:mkdir).in_sequence(s). 132 | with("remote/folder") 133 | connection.expects(:mkdir).in_sequence(s). 134 | with("remote/folder/another_folder") 135 | 136 | expect do 137 | publisher.send(:create_remote_path, remote_path, connection) 138 | end.not_to raise_error 139 | end 140 | end 141 | end 142 | 143 | end 144 | -------------------------------------------------------------------------------- /spec/publish/scp_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require File.expand_path('../../spec_helper.rb', __FILE__) 4 | require 'frank/publish/scp' 5 | 6 | describe Frank::Publish::SCP do 7 | 8 | let(:publisher) do 9 | Frank::Publish::SCP.new(Frank.publish) do |scp| 10 | scp.username = 'my_username' 11 | scp.password = 'my_password' 12 | scp.hostname = 'scp.example.com' 13 | scp.local_path = '/local/path' 14 | scp.remote_path = '/remote/path' 15 | end 16 | end 17 | 18 | before(:all) do 19 | Frank.bootstrap(File.join(File.dirname(__FILE__), 'template')) 20 | end 21 | 22 | describe '#initialize' do 23 | it 'should set the correct values' do 24 | publisher.username.should == 'my_username' 25 | publisher.password.should == 'my_password' 26 | publisher.hostname.should == 'scp.example.com' 27 | publisher.port.should == 22 28 | publisher.local_path.should == '/local/path' 29 | publisher.remote_path.should == '/remote/path' 30 | 31 | end 32 | 33 | end # describe '#initialize' 34 | 35 | describe '#connection' do 36 | let(:connection) { mock } 37 | 38 | it 'should yield a connection to the remote server' do 39 | Net::SCP.expects(:start).with('scp.example.com', 'my_username', :password => 'my_password').yields(connection) 40 | 41 | publisher.send(:connection) do |scp| 42 | scp.should be(connection) 43 | end 44 | end 45 | 46 | end # describe '#connection' 47 | 48 | describe '#transfer!' do 49 | let(:connection) { mock } 50 | 51 | before do 52 | publisher.stubs(:connection).yields(connection) 53 | end 54 | 55 | it 'should transfer the local_path to remote_path using upload!' do 56 | connection.expects(:upload!).with('/local/path', '/remote/path') 57 | 58 | publisher.send(:transfer!) 59 | end 60 | end # describe '#transfer!' 61 | 62 | end 63 | -------------------------------------------------------------------------------- /spec/publish/sftp_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require File.expand_path('../../spec_helper.rb', __FILE__) 4 | require 'frank/publish/sftp' 5 | 6 | describe Frank::Publish::SFTP do 7 | 8 | let(:publisher) do 9 | Frank::Publish::SFTP.new(Frank.publish) do |scp| 10 | scp.username = 'my_username' 11 | scp.password = 'my_password' 12 | scp.hostname = 'scp.example.com' 13 | scp.local_path = '/local/path' 14 | scp.remote_path = '/remote/path' 15 | end 16 | end 17 | 18 | before(:all) do 19 | Frank.bootstrap(File.join(File.dirname(__FILE__), 'template')) 20 | end 21 | 22 | describe '#initialize' do 23 | it 'should set the correct values' do 24 | publisher.username.should == 'my_username' 25 | publisher.password.should == 'my_password' 26 | publisher.hostname.should == 'scp.example.com' 27 | publisher.port.should == 22 28 | publisher.local_path.should == '/local/path' 29 | publisher.remote_path.should == '/remote/path' 30 | 31 | end 32 | 33 | end # describe '#initialize' 34 | 35 | describe '#connection' do 36 | let(:connection) { mock } 37 | 38 | it 'should yield a connection to the remote server' do 39 | Net::SFTP.expects(:start).with('scp.example.com', 'my_username', :password => 'my_password').yields(connection) 40 | 41 | publisher.send(:connection) do |scp| 42 | scp.should be(connection) 43 | end 44 | end 45 | 46 | end # describe '#connection' 47 | 48 | describe '#transfer!' do 49 | let(:connection) { mock } 50 | 51 | before do 52 | publisher.stubs(:connection).yields(connection) 53 | end 54 | 55 | it 'should transfer the local_path to remote_path using upload!' do 56 | connection.expects(:upload!).with('/local/path', '/remote/path') 57 | 58 | publisher.send(:transfer!) 59 | end 60 | end # describe '#transfer!' 61 | 62 | end 63 | -------------------------------------------------------------------------------- /spec/publish_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | 4 | describe Frank::Publish do 5 | include Rack::Test::Methods 6 | include Frank::Spec::Helpers 7 | 8 | let(:proj_dir) { File.join(File.dirname(__FILE__), 'template') } 9 | let(:protocols) { [:ftp, :ftptls, :sftp, :scp] } 10 | 11 | before(:all) do 12 | Frank.bootstrap(proj_dir) 13 | end 14 | 15 | describe '#exit_unless_configured' do 16 | 17 | before do 18 | Frank.publish.host = 'example.com' 19 | Frank.publish.username = 'test' 20 | end 21 | 22 | # How do I prohibit SystemExit from really exiting? 23 | # 24 | #it 'should exit if mandatory username param is missing' do 25 | # Frank.publish.username= nil 26 | # 27 | # lambda { 28 | # Frank::Publish.exit_unless_configured 29 | # }.should raise_error(SystemExit) 30 | #end 31 | # 32 | #it 'should exit if mandatory host param is missing' do 33 | # Frank.publish.host= nil 34 | # 35 | # lambda { 36 | # Frank::Publish.exit_unless_configured 37 | # }.should raise_error(SystemExit) 38 | #end 39 | 40 | it 'should return the publish protocol to use' do 41 | protocols.each do |p| 42 | Frank.publish.mode = p 43 | 44 | Frank::Publish.exit_unless_configured.should == p 45 | end 46 | 47 | end 48 | 49 | end 50 | 51 | describe '#execute!' do 52 | 53 | let(:publisher) { mock } 54 | 55 | it 'should instatiate and call perform! on the appropriate class' do 56 | protocols.each do |proto| 57 | Frank.publish.mode = proto 58 | 59 | require "frank/publish/#{proto}" 60 | 61 | clazz = Frank::Publish.const_get(proto.to_s.upcase) 62 | clazz.stubs(:new).with(Frank.publish).returns(publisher) 63 | publisher.expects(:perform!) 64 | 65 | Frank::Publish.execute! 66 | 67 | end 68 | 69 | end 70 | end 71 | 72 | 73 | end 74 | -------------------------------------------------------------------------------- /spec/render_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Frank::Render do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Frank.bootstrap(File.join(File.dirname(__FILE__), 'template')) 8 | Frank.new 9 | end 10 | 11 | before(:all) do 12 | @app = app 13 | end 14 | 15 | it 'renders a template using layout' do 16 | template = @app.render('index.haml') 17 | template.should == "\n
/
\n
\n

hello worlds

\n

/

\n
\n" 18 | end 19 | 20 | it 'renders a template using layout2' do 21 | template = @app.render('layout2_test.haml') 22 | template.should == "
\n

hi inside layout2

\n
\n" 23 | end 24 | 25 | it 'renders a markdown template inside haml layout' do 26 | template = @app.render('markdown_in_haml.md') 27 | template.should == "\n
/markdown_in_haml
\n
\n

hi inside layout

\n
\n" 28 | end 29 | 30 | it 'renders a nested template with a nested layout' do 31 | template = @app.render('/nested/child.haml') 32 | template.should == "
\n

hello from child

\n
\n" 33 | end 34 | 35 | it 'renders a deeply nested template with a nested layout' do 36 | template = @app.render('/nested/deeper/deep.haml') 37 | template.should == "
\n

really deep

\n
\n" 38 | end 39 | 40 | it 'renders a haml template with no layout' do 41 | template = @app.render('no_layout.haml') 42 | template.should == "

i have no layout

\n" 43 | end 44 | 45 | it 'renders haml template' do 46 | template = @app.render('index.haml') 47 | template.should == "\n
/
\n
\n

hello worlds

\n

/

\n
\n" 48 | end 49 | 50 | it 'renders haml template with a haml partial' do 51 | template = @app.render('partial_test.haml') 52 | template.should == "\n
/partial_test
\n
\n

hello worlds

\n

/partial_test

\n

hello from partial

\n
\n" 53 | end 54 | 55 | it 'renders a partial with locals' do 56 | template = @app.render('partial_locals_test.haml') 57 | template.should == "\n
/partial_locals_test
\n
\n

hello worlds

\n

/partial_locals_test

\n

hello from local

\n
\n" 58 | end 59 | 60 | it 'renders less template' do 61 | template = @app.render('stylesheets/less.less') 62 | template.should include("#hello-worlds { background: red; }") 63 | end 64 | 65 | it 'renders sass template' do 66 | template = @app.render('stylesheets/sass.sass') 67 | template.should include("#hello-worlds {\n background: red;\n}\n") 68 | end 69 | 70 | it 'renders sass with compass reset' do 71 | template = @app.render('stylesheets/sass_with_compass.sass') 72 | template.should include("h1, h2, h3, h4, h5, h6, p, blockquote, pre,\n") 73 | end 74 | 75 | it 'renders scss with compass mixin' do 76 | template = @app.render('stylesheets/scss_with_compass.scss') 77 | template.should include("-moz-border-radius: 5px;\n") 78 | template.should include("-webkit-border-radius: 5px;\n") 79 | template.should include("border-radius: 5px;\n") 80 | end 81 | 82 | it 'renders coffee template' do 83 | template = @app.render('coffee.coffee') 84 | template.should == "(function() {\n ({\n greeting: \"Hello CoffeeScript\"\n });\n}).call(this);\n" 85 | end 86 | 87 | it 'renders erb template' do 88 | template = @app.render('erb.erb') 89 | template.should == "\n
/erb
\n
\n

hello worlds

\n
\n" 90 | end 91 | 92 | it 'renders redcloth template' do 93 | template = @app.render('redcloth.textile') 94 | template.should == "\n
/redcloth
\n
\n

hello worlds

\n
\n" 95 | end 96 | 97 | it 'renders rdiscount template' do 98 | template = @app.render('markdown.md') 99 | template.should == "\n
/markdown
\n
\n

hello worlds

\n
\n" 100 | end 101 | 102 | it 'renders liquid template' do 103 | template = @app.render('liquid.liquid') 104 | template.should == "\n
/liquid
\n
\n

hello worlds

\n
\n" 105 | end 106 | 107 | it 'renders builder template' do 108 | template = @app.render('builder.builder') 109 | template.should == "\n
/builder
\n
\n

hello worlds

\n
\n" 110 | end 111 | 112 | it 'raise template error' do 113 | lambda { @app.render('not_a.template') }.should raise_error(Frank::TemplateError) 114 | end 115 | 116 | end 117 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | testdir = File.dirname(__FILE__) 2 | $:.unshift testdir unless $LOAD_PATH.include?(testdir) 3 | 4 | require 'rubygems' if RUBY_VERSION < '1.9' 5 | require 'bundler/setup' 6 | 7 | require 'stringio' 8 | require 'rack/test' 9 | require 'template/helpers' 10 | require 'frank' 11 | require 'frank/publish/base' 12 | 13 | module Kernel 14 | def capture_stdout 15 | out = StringIO.new 16 | $stdout = out 17 | yield 18 | return out 19 | ensure 20 | $stdout = STDOUT 21 | end 22 | end 23 | 24 | module Frank 25 | module Spec 26 | module Helpers 27 | BIN_DIR = File.join(File.dirname(File.dirname(__FILE__)), 'bin') 28 | 29 | def frank(command, *args) 30 | result = system "#{BIN_DIR}/frank #{command} #{args * ' '}" 31 | 32 | if $?.success? 33 | result 34 | else 35 | exit 1 36 | end 37 | end 38 | end 39 | end 40 | end 41 | 42 | module Frank 43 | module Publish 44 | def self.ok_message str, prefix = ""; 45 | end 46 | 47 | def self.err_message str, prefix = ""; 48 | end 49 | end 50 | end 51 | 52 | RSpec.configure do |config| 53 | ## 54 | # Use Mocha to mock with RSpec 55 | config.mock_with :mocha 56 | 57 | end 58 | -------------------------------------------------------------------------------- /spec/template/dynamic/500.haml: -------------------------------------------------------------------------------- 1 | = non_method if @blowup_sometimes -------------------------------------------------------------------------------- /spec/template/dynamic/_partial.haml: -------------------------------------------------------------------------------- 1 | %p hello from partial -------------------------------------------------------------------------------- /spec/template/dynamic/_partial_with_locals.haml: -------------------------------------------------------------------------------- 1 | %p="hello from #{local}" 2 | -------------------------------------------------------------------------------- /spec/template/dynamic/builder.builder: -------------------------------------------------------------------------------- 1 | xml.h1('hello' + ' worlds') -------------------------------------------------------------------------------- /spec/template/dynamic/coffee.coffee: -------------------------------------------------------------------------------- 1 | greeting: "Hello CoffeeScript" -------------------------------------------------------------------------------- /spec/template/dynamic/content_for_erb.erb: -------------------------------------------------------------------------------- 1 | <% content_for :head do %> 2 | 3 | <% end %> -------------------------------------------------------------------------------- /spec/template/dynamic/content_for_haml.haml: -------------------------------------------------------------------------------- 1 | - content_for :head do 2 | %meta{:foo => 'content'} -------------------------------------------------------------------------------- /spec/template/dynamic/erb.erb: -------------------------------------------------------------------------------- 1 |

<%= 'hello worlds' %>

2 | -------------------------------------------------------------------------------- /spec/template/dynamic/helper_test.haml: -------------------------------------------------------------------------------- 1 | %h1= hello_helper -------------------------------------------------------------------------------- /spec/template/dynamic/index.haml: -------------------------------------------------------------------------------- 1 | title: hello worlds 2 | META--------------------------------- 3 | 4 | %h1= title 5 | %h2= current_path 6 | -------------------------------------------------------------------------------- /spec/template/dynamic/layout2_test.haml: -------------------------------------------------------------------------------- 1 | layout: explicit/layout2.haml 2 | META-------------------- 3 | 4 | %h1 hi inside layout2 -------------------------------------------------------------------------------- /spec/template/dynamic/liquid.liquid: -------------------------------------------------------------------------------- 1 |

hello worlds

-------------------------------------------------------------------------------- /spec/template/dynamic/lorem_test.haml: -------------------------------------------------------------------------------- 1 | %p.words= lorem.words 3, 'replace-this' 2 | %p.sentences= lorem.sentences 2, 'replace-this' 3 | %p.paragraphs= lorem.paragraphs 1, 'replace-this' 4 | %p.date= lorem.date '%Y-%m-%d', 1910..1919, 'replace-this' 5 | %p.name= lorem.name 'replace-this' 6 | %p.email= lorem.email 'replace-this' 7 | %img{:src => lorem.image('400x300')} 8 | %img{:src => lorem.image('400x300', :random_color => true)} 9 | %img{:src => lorem.image('400x300', :background_color => '444', :color => 'eee', :replacement => 'replace-this')} 10 | %img{:src => lorem.image('400x300', :color => 'aaa')} 11 | %img{:src => lorem.image('400x300', :background_color => '444', :text => 'blah')} 12 | 13 | -------------------------------------------------------------------------------- /spec/template/dynamic/markdown.md: -------------------------------------------------------------------------------- 1 | # hello worlds -------------------------------------------------------------------------------- /spec/template/dynamic/markdown_in_haml.md: -------------------------------------------------------------------------------- 1 | layout: default.haml 2 | META-------------------- 3 | 4 | # hi inside layout -------------------------------------------------------------------------------- /spec/template/dynamic/nested/child.haml: -------------------------------------------------------------------------------- 1 | %h1 hello from child -------------------------------------------------------------------------------- /spec/template/dynamic/nested/deeper/deep.haml: -------------------------------------------------------------------------------- 1 | %h1 really deep -------------------------------------------------------------------------------- /spec/template/dynamic/no_layout.haml: -------------------------------------------------------------------------------- 1 | layout: nil 2 | META------------------- 3 | 4 | %h1 i have no layout -------------------------------------------------------------------------------- /spec/template/dynamic/partial_locals_test.haml: -------------------------------------------------------------------------------- 1 | %h1 hello worlds 2 | %h2= current_path 3 | = render_partial('partial_with_locals', :local => 'local') 4 | -------------------------------------------------------------------------------- /spec/template/dynamic/partial_test.haml: -------------------------------------------------------------------------------- 1 | %h1 hello worlds 2 | %h2= current_path 3 | = render_partial('partial') -------------------------------------------------------------------------------- /spec/template/dynamic/redcloth.textile: -------------------------------------------------------------------------------- 1 | h1. hello worlds -------------------------------------------------------------------------------- /spec/template/dynamic/refresh.haml: -------------------------------------------------------------------------------- 1 | =refresh -------------------------------------------------------------------------------- /spec/template/dynamic/setting_in_layout.haml: -------------------------------------------------------------------------------- 1 | - title "BLAH!" 2 | %h1 hello -------------------------------------------------------------------------------- /spec/template/dynamic/stylesheets/less.less: -------------------------------------------------------------------------------- 1 | @color: red; 2 | 3 | #hello-worlds { 4 | background: @color; 5 | } -------------------------------------------------------------------------------- /spec/template/dynamic/stylesheets/sass.sass: -------------------------------------------------------------------------------- 1 | #hello-worlds 2 | background: red -------------------------------------------------------------------------------- /spec/template/dynamic/stylesheets/sass_with_compass.sass: -------------------------------------------------------------------------------- 1 | @import compass/reset -------------------------------------------------------------------------------- /spec/template/dynamic/stylesheets/scss_with_compass.scss: -------------------------------------------------------------------------------- 1 | @import "compass/css3"; 2 | 3 | .panel { 4 | @include border-radius(5px); 5 | } -------------------------------------------------------------------------------- /spec/template/helpers.rb: -------------------------------------------------------------------------------- 1 | module FrankHelpers 2 | def hello_helper 3 | 'hello from helper' 4 | end 5 | 6 | def title(val = nil) 7 | @title = val if val 8 | @title || "" 9 | end 10 | end -------------------------------------------------------------------------------- /spec/template/layouts/default.haml: -------------------------------------------------------------------------------- 1 | - if @title 2 | #title= @title 3 | = content_for :head 4 | #p= current_path 5 | #layout 6 | = yield -------------------------------------------------------------------------------- /spec/template/layouts/explicit/layout2.haml: -------------------------------------------------------------------------------- 1 | #layout2 2 | = yield -------------------------------------------------------------------------------- /spec/template/layouts/nested/default.haml: -------------------------------------------------------------------------------- 1 | #nested_layout 2 | = yield -------------------------------------------------------------------------------- /spec/template/setup.rb: -------------------------------------------------------------------------------- 1 | Frank.environment = :test 2 | Frank.publish.path = '/remote/path' 3 | Frank.publish.host = 'example.com' 4 | Frank.publish.user = 'test' 5 | Frank.publish.password = 'secret' 6 | -------------------------------------------------------------------------------- /spec/template/static/files/static.html: -------------------------------------------------------------------------------- 1 | hello from static -------------------------------------------------------------------------------- /spec/template_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper' 2 | 3 | describe Frank::TemplateHelpers do 4 | include Rack::Test::Methods 5 | 6 | def app 7 | Frank.bootstrap(File.join(File.dirname(__FILE__), 'template')) 8 | require File.join(Frank.root, 'helpers') 9 | Frank.new 10 | end 11 | 12 | before(:all) do 13 | @app = app 14 | end 15 | 16 | it 'render haml and use hello_helper' do 17 | template = @app.render('helper_test.haml') 18 | template.should == "\n
/helper_test
\n
\n

hello from helper

\n
\n" 19 | end 20 | 21 | it 'sets an instance variable, which the layout should render correctly' do 22 | template = @app.render('setting_in_layout.haml') 23 | template.should == "
BLAH!
\n\n
/setting_in_layout
\n
\n

hello

\n
\n" 24 | end 25 | 26 | it 'should render the refresh javascript' do 27 | template = @app.render('refresh.haml') 28 | template.should include("