clay 1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/clay +29 -0
- data/src/clay.rb +196 -0
- data/src/rack/clay.rb +114 -0
- metadata +170 -0
data/bin/clay
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. src]) # add ../src dir to the path of required modules
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'clay'
|
6
|
+
|
7
|
+
usage = <<HELP
|
8
|
+
USAGE:
|
9
|
+
$ clay <command>
|
10
|
+
|
11
|
+
COMMANDS:
|
12
|
+
init - creates a directory structure for the project
|
13
|
+
form - forms a set of static pages from the project
|
14
|
+
run - starts a local server
|
15
|
+
HELP
|
16
|
+
|
17
|
+
command = ARGV[0]
|
18
|
+
case command
|
19
|
+
when "init" then
|
20
|
+
if ARGV[1].nil?
|
21
|
+
puts "USAGE:\n $ clay init <site_name> "
|
22
|
+
else
|
23
|
+
Clay.init ARGV[1]
|
24
|
+
end
|
25
|
+
when "form" then Clay.form
|
26
|
+
when "run" then Clay.run
|
27
|
+
when "version" then puts "clay ver.: " + Goo::VERSION
|
28
|
+
else puts usage
|
29
|
+
end
|
data/src/clay.rb
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'mustache'
|
4
|
+
require 'rdiscount'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module Clay
|
9
|
+
VERSION = "1.1"
|
10
|
+
|
11
|
+
def self.init project_name
|
12
|
+
print "Creating the folder structure... "
|
13
|
+
project.init project_name
|
14
|
+
puts "complete"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.form
|
18
|
+
print "Forming... "
|
19
|
+
project.check_consistency
|
20
|
+
project.build
|
21
|
+
puts "complete."
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.run
|
25
|
+
puts "Starting server on http://localhost:9292/"
|
26
|
+
project.prepare_rack_config
|
27
|
+
`rackup config.ru`
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def self.project
|
33
|
+
Project.new project_root
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.project_root
|
37
|
+
`pwd`.strip
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class Project
|
43
|
+
def initialize project_root
|
44
|
+
@project_root = project_root
|
45
|
+
end
|
46
|
+
|
47
|
+
def init name
|
48
|
+
create_directory name
|
49
|
+
Dir.chdir name do
|
50
|
+
@project_root = File.join(@project_root, name)
|
51
|
+
check_consistency
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_consistency
|
56
|
+
init_clay_project? and layouts_exist? and pages_exist? and public_exist?
|
57
|
+
end
|
58
|
+
|
59
|
+
def build
|
60
|
+
unless File.directory?(path("build"))
|
61
|
+
create_directory path("build")
|
62
|
+
end
|
63
|
+
publish_public
|
64
|
+
publish_pages
|
65
|
+
end
|
66
|
+
|
67
|
+
def prepare_rack_config
|
68
|
+
unless File.exists? path("config.ru")
|
69
|
+
file = File.open(path("config.ru"), "w")
|
70
|
+
file.write "require 'rack/clay'\nrun Rack::Clay.new"
|
71
|
+
file.close
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def init_clay_project?
|
78
|
+
`touch #{path(".clay")}` unless File.exists? path(".clay")
|
79
|
+
end
|
80
|
+
|
81
|
+
def layouts_exist?
|
82
|
+
create_directory path("layouts")
|
83
|
+
end
|
84
|
+
|
85
|
+
def pages_exist?
|
86
|
+
create_directory path("pages")
|
87
|
+
end
|
88
|
+
|
89
|
+
def public_exist?
|
90
|
+
create_directory path("public")
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_directory dirname
|
94
|
+
return if dirname.nil? || dirname.empty?
|
95
|
+
unless File.directory?(dirname)
|
96
|
+
puts "#{dirname} must be a directory"
|
97
|
+
FileUtils.rm_rf dirname
|
98
|
+
end
|
99
|
+
unless File.exists?(dirname)
|
100
|
+
puts "Creating directory: #{dirname}"
|
101
|
+
FileUtils.mkdir dirname
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def publish_pages
|
106
|
+
Dir.glob("pages/*.*").each { |page_path|
|
107
|
+
page = Page.new(page_path)
|
108
|
+
File.open(page.target, "w") {|f| f.write page.content }
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def publish_public
|
113
|
+
FileUtils.cp_r(Dir.glob("public/*"), "build")
|
114
|
+
end
|
115
|
+
|
116
|
+
def path filename
|
117
|
+
File.expand_path(File.join("#{@project_root}/", filename))
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
class Page
|
123
|
+
attr_reader :content
|
124
|
+
def initialize filename
|
125
|
+
case filename.split(".").last
|
126
|
+
when "md", "markdown" then @page_type = "markdown"
|
127
|
+
when "html" then @page_type = "html"
|
128
|
+
else raise "File type of #{filename} unknown.\nMaybe it belongs to the public directory?"
|
129
|
+
end
|
130
|
+
@filename = filename_within_pages filename
|
131
|
+
file_content = File.read(filename)
|
132
|
+
layout, raw_content, data = interpret file_content
|
133
|
+
@content = render raw_content, layout, @page_type, data
|
134
|
+
end
|
135
|
+
|
136
|
+
def target
|
137
|
+
file_base = @filename.split(".")[0..-2].join('.')
|
138
|
+
"build/#{file_base}.html"
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
def filename_within_pages filename
|
143
|
+
filename.split("/", 2)[1]
|
144
|
+
end
|
145
|
+
|
146
|
+
def render content, layout, page_type, data
|
147
|
+
data['content'] = parsed_content content, data
|
148
|
+
begin
|
149
|
+
layout_content = File.read(layout)
|
150
|
+
rescue NameError
|
151
|
+
raise "Layout #{layout} is missing.\nPlease create one in layouts directory"
|
152
|
+
end
|
153
|
+
rendered_content = Mustache.render(layout_content, data)
|
154
|
+
end
|
155
|
+
|
156
|
+
def parsed_content content, data
|
157
|
+
case @page_type
|
158
|
+
when "markdown" then return Mustache.render(RDiscount.new(content).to_html.strip, data)
|
159
|
+
when "html" then return Mustache.render(content, data)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def interpret content
|
164
|
+
layout_name = "default"
|
165
|
+
data = {}
|
166
|
+
if content.match(/^(\s*---(.+)---\s*)/m)
|
167
|
+
data = YAML.load($2.strip)
|
168
|
+
content = content.gsub($1, "")
|
169
|
+
layout_name = data.delete "layout" if data["layout"]
|
170
|
+
end
|
171
|
+
layout = "layouts/#{layout_name}.html"
|
172
|
+
texts = Dir.glob("texts/*")
|
173
|
+
texts.each{|filename| data.merge! Text.new(filename).to_h }
|
174
|
+
return layout, content, data
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class Text
|
179
|
+
attr_reader :content
|
180
|
+
def initialize filename
|
181
|
+
extension = File.extname(filename)
|
182
|
+
@name = File.basename(filename, extension)
|
183
|
+
raise "Text must be a markdown file" unless extension == ".md" or extension == ".markdown"
|
184
|
+
@content = parse File.read(filename)
|
185
|
+
end
|
186
|
+
|
187
|
+
def to_h
|
188
|
+
{"text-#{@name}" => content}
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def parse content
|
194
|
+
RDiscount.new(content).to_html.strip
|
195
|
+
end
|
196
|
+
end
|
data/src/rack/clay.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "rack/request"
|
3
|
+
require "rack/response"
|
4
|
+
require "fileutils"
|
5
|
+
require "yaml"
|
6
|
+
|
7
|
+
module Rack
|
8
|
+
class Clay
|
9
|
+
def initialize(opts = {})
|
10
|
+
@configfile = ::File.join(::Dir.pwd,"config.yaml")
|
11
|
+
@config = {}
|
12
|
+
if ::File.exist?(@configfile)
|
13
|
+
puts "Reading configs... "
|
14
|
+
@config = ::YAML.load(::File.read(@configfile))
|
15
|
+
@config = (@config.class == FalseClass ? {} : @config)
|
16
|
+
if @config[:destination].nil?
|
17
|
+
@path = opts[:destination].nil? ? "build" : opts[:destination]
|
18
|
+
else
|
19
|
+
opts.merge!(@config)
|
20
|
+
@path = @config[:destination].nil? ? "build" : @config[:destination]
|
21
|
+
end
|
22
|
+
puts @config.inspect
|
23
|
+
puts "Ready."
|
24
|
+
end
|
25
|
+
|
26
|
+
@path = "build"
|
27
|
+
@path = @config["build_path"] if @config["build_path"]
|
28
|
+
@mimes = Rack::Mime::MIME_TYPES.map{|k,v| /#{k.gsub('.','\.')}$/i }
|
29
|
+
end
|
30
|
+
def call(env)
|
31
|
+
rebuild
|
32
|
+
|
33
|
+
request = Request.new(env)
|
34
|
+
path_info = request.path_info
|
35
|
+
@files = ::Dir[@path + "/**/*"].inspect
|
36
|
+
if @files.include?(path_info)
|
37
|
+
if path_info =~ /(\/?)$/
|
38
|
+
if @mimes.collect {|regex| path_info =~ regex }.compact.empty?
|
39
|
+
path_info += $1.nil? ? "/index.html" : "index.html"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
mime = mime(path_info)
|
43
|
+
|
44
|
+
file = file_info(@path + path_info)
|
45
|
+
body = file[:body]
|
46
|
+
time = file[:time]
|
47
|
+
|
48
|
+
if time == request.env['HTTP_IF_MODIFIED_SINCE']
|
49
|
+
[304, {'Last-Modified' => time}, []]
|
50
|
+
else
|
51
|
+
[200, {"Content-Type" => mime, "Content-length" => body.length.to_s, 'Last-Modified' => time}, [body]]
|
52
|
+
end
|
53
|
+
|
54
|
+
else
|
55
|
+
status, body, path_info = ::File.exist?(@path+"/404.html") ? [404,file_info(@path+"/404.html")[:body],"404.html"] : [404,"Not found","404.html"]
|
56
|
+
mime = mime(path_info)
|
57
|
+
if !@compiling
|
58
|
+
[status, {"Content-Type" => mime, "Content-length" => body.length.to_s}, [body]]
|
59
|
+
else
|
60
|
+
[200, {"Content-Type" => "text/plain"}, ["This site is currently generating pages. Please reload this page after 10 secs."]]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def file_info(path)
|
66
|
+
expand_path = ::File.expand_path(path)
|
67
|
+
::File.open(expand_path, 'r') do |f|
|
68
|
+
{:body => f.read, :time => f.mtime.httpdate, :expand_path => expand_path}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def mime(path_info)
|
73
|
+
if path_info !~ /html$/i
|
74
|
+
ext = $1 if path_info =~ /(\.\S+)$/
|
75
|
+
Mime.mime_type((ext.nil? ? ".html" : ext))
|
76
|
+
else
|
77
|
+
Mime.mime_type(".html")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def rebuild
|
82
|
+
if @config["autoreload"]
|
83
|
+
require 'json'
|
84
|
+
clayfile = ::File.join(::Dir.pwd, ".clay")
|
85
|
+
old_file_hashes = ::File.read(clayfile)
|
86
|
+
new_file_hashes = get_file_hashes
|
87
|
+
changes = compare_file_hashes old_file_hashes, new_file_hashes.to_json
|
88
|
+
unless changes
|
89
|
+
begin
|
90
|
+
require 'clay'
|
91
|
+
::Clay.form
|
92
|
+
rescue LoadError
|
93
|
+
raise "Could not load clay"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
::File.open(clayfile, "w") {|f| f.write(new_file_hashes.to_json)}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_file_hashes
|
101
|
+
files = ::Dir.glob("**/*") - [@path] - ::Dir.glob(@path + "/**/*")
|
102
|
+
file_hashes_array = files.map {|filename| [filename, file_hash(filename)]}
|
103
|
+
file_hashes = Hash[*file_hashes_array.flatten]
|
104
|
+
end
|
105
|
+
|
106
|
+
def file_hash filename
|
107
|
+
::File.new(filename).mtime.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
def compare_file_hashes old, new
|
111
|
+
old == new
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
metadata
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: clay
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 13
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: "1.1"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Pavlo Kerestey
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-27 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
hash: 29
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 11
|
30
|
+
version: "0.11"
|
31
|
+
name: mustache
|
32
|
+
requirement: *id001
|
33
|
+
prerelease: false
|
34
|
+
type: :runtime
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
hash: 3
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
version: "0"
|
45
|
+
name: rdiscount
|
46
|
+
requirement: *id002
|
47
|
+
prerelease: false
|
48
|
+
type: :runtime
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
name: rack
|
60
|
+
requirement: *id003
|
61
|
+
prerelease: false
|
62
|
+
type: :runtime
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
name: passenger
|
74
|
+
requirement: *id004
|
75
|
+
prerelease: false
|
76
|
+
type: :runtime
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ~>
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
hash: 15
|
84
|
+
segments:
|
85
|
+
- 1
|
86
|
+
- 0
|
87
|
+
version: "1.0"
|
88
|
+
name: bundler
|
89
|
+
requirement: *id005
|
90
|
+
prerelease: false
|
91
|
+
type: :development
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
name: rspec
|
103
|
+
requirement: *id006
|
104
|
+
prerelease: false
|
105
|
+
type: :development
|
106
|
+
- !ruby/object:Gem::Dependency
|
107
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
name: rake
|
117
|
+
requirement: *id007
|
118
|
+
prerelease: false
|
119
|
+
type: :development
|
120
|
+
description: A sticky clay to automatically form a static website using Mustache templates and markdown files.
|
121
|
+
email:
|
122
|
+
- pavlo@kerestey.net
|
123
|
+
executables:
|
124
|
+
- clay
|
125
|
+
extensions: []
|
126
|
+
|
127
|
+
extra_rdoc_files: []
|
128
|
+
|
129
|
+
files:
|
130
|
+
- src/clay.rb
|
131
|
+
- src/rack/clay.rb
|
132
|
+
- bin/clay
|
133
|
+
has_rdoc: true
|
134
|
+
homepage: http://kerestey.net/clay
|
135
|
+
licenses: []
|
136
|
+
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
|
140
|
+
require_paths:
|
141
|
+
- - src
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
hash: 3
|
148
|
+
segments:
|
149
|
+
- 0
|
150
|
+
version: "0"
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
hash: 23
|
157
|
+
segments:
|
158
|
+
- 1
|
159
|
+
- 3
|
160
|
+
- 6
|
161
|
+
version: 1.3.6
|
162
|
+
requirements: []
|
163
|
+
|
164
|
+
rubyforge_project: clay
|
165
|
+
rubygems_version: 1.3.7
|
166
|
+
signing_key:
|
167
|
+
specification_version: 3
|
168
|
+
summary: A sticky clay to automatically form a static website.
|
169
|
+
test_files: []
|
170
|
+
|