ciridiri 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ #ciridiri.rb
2
+
3
+ __ciridiri.rb__ is a Ruby+Sinatra port of [ciridiri](http://vast.github.com/ciridiri/),
4
+ dead simple wiki engine.
5
+
6
+ ##Requirements
7
+
8
+ * [sinatra][]
9
+
10
+ ##Installation
11
+
12
+ git clone git://github.com/vast/ciridiri.rb.git
13
+ cd ciridiri.rb
14
+ rackup
15
+
16
+ And point your browser to `http://localhost:4567/`.
17
+
18
+ ##Usage
19
+ Create new pages through accessing `http://localhost:4567/path/to/new/page.html`.
20
+ Edit existent page through accessing `http://localhost:4567/existent/page.html.e` or just press `ctrl-shift-e`.
21
+
22
+ [sinatra]: http://sinatrarb.com/
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+ require 'rake/gempackagetask'
4
+
5
+ task :default => :test
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gemspec|
10
+ gemspec.name = "ciridiri"
11
+ gemspec.version = "0.8.1"
12
+ gemspec.summary = gemspec.description = "Dead simple wiki engine"
13
+ gemspec.email = "vasily@polovnyov.ru"
14
+ gemspec.homepage = "http://vast.github.com/ciridiri.rb"
15
+ gemspec.authors = ["Vasily Polovnyov"]
16
+
17
+ gemspec.add_dependency 'sinatra', '>=0.9.1'
18
+
19
+ gemspec.add_development_dependency 'rack-test', '>=0.3.0'
20
+ gemspec.add_development_dependency 'contest', '>=0.1.0'
21
+
22
+ gemspec.test_files = Dir.glob('test/*')
23
+ gemspec.files = ["LICENSE", "README.md", "Rakefile", "config.ru"] + Dir.glob('lib/**/*') + gemspec.test_files +
24
+ Dir.glob('public/**/*') + Dir.glob('views/*')
25
+
26
+ end
27
+ rescue LoadError
28
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
29
+ end
30
+
31
+ Rake::TestTask.new do |t|
32
+ t.libs << "test"
33
+ t.test_files = FileList['test/*_test.rb']
34
+ t.verbose = true
35
+ ENV['RACK_ENV'] = 'test'
36
+ end
data/config.ru ADDED
@@ -0,0 +1,13 @@
1
+ $:.unshift File.expand_path("#{File.dirname(__FILE__)}/lib")
2
+ require 'rubygems'
3
+ require 'sinatra'
4
+ require 'ciridiri'
5
+
6
+ # require 'rdiscount'
7
+ # Ciridiri::Page.formatter = lambda {|text| RDiscount.new(text).to_html}
8
+
9
+ set :raise_errors, true
10
+ set :show_exceptions, false
11
+ set :logging, false
12
+
13
+ run Ciridiri::Application
data/lib/ciridiri.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'sinatra/base'
3
+
4
+ $:.unshift(File.dirname(__FILE__))
5
+ require 'ciridiri/page'
6
+
7
+ class Ciridiri::Application < Sinatra::Base
8
+ include Ciridiri
9
+ configure do
10
+ set :app_file, __FILE__
11
+ set :root, File.expand_path('..', File.dirname(__FILE__))
12
+ enable :static
13
+ enable :logging if development?
14
+
15
+ Page.caching = false if development? || test?
16
+ Page.content_dir = File.join(self.root, "pages", self.environment.to_s)
17
+ end
18
+
19
+ helpers do
20
+ include Rack::Utils
21
+ alias_method :h, :escape_html
22
+ end
23
+
24
+ get '/' do
25
+ redirect '/index.html'
26
+ end
27
+
28
+ get '*.html' do
29
+ uri = params[:splat].first
30
+ if @page = Page.find_by_uri(uri)
31
+ erb :show
32
+ else
33
+ redirect "#{uri}.html.e"
34
+ end
35
+ end
36
+
37
+ get '*.html.e' do
38
+ @page = Page.find_by_uri_or_empty(params[:splat].first)
39
+ erb :edit
40
+ end
41
+
42
+ post '*.html' do
43
+ @page = Page.find_by_uri_or_empty(params[:splat].first)
44
+ @page.contents = params[:contents]
45
+ @page.save
46
+ redirect "#{@page.uri}.html"
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ # Extends the class object with class and instance accessors for class attributes,
2
+ # just like the native attr* accessors for instance attributes.
3
+ #
4
+ # class Person
5
+ # cattr_accessor :hair_colors
6
+ # end
7
+ #
8
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
9
+ #
10
+ class Class
11
+ def cattr_reader(*syms)
12
+ syms.flatten.each do |sym|
13
+ next if sym.is_a?(Hash)
14
+ class_eval(<<-EOS, __FILE__, __LINE__)
15
+ unless defined? @@#{sym} # unless defined? @@hair_colors
16
+ @@#{sym} = nil # @@hair_colors = nil
17
+ end # end
18
+ #
19
+ def self.#{sym} # def self.hair_colors
20
+ @@#{sym} # @@hair_colors
21
+ end # end
22
+ #
23
+ def self.#{sym}? # def self.hair_colors?
24
+ @@#{sym} # @@hair_colors
25
+ end # end
26
+ EOS
27
+ end
28
+ end
29
+
30
+ def cattr_writer(*syms)
31
+ syms.flatten.each do |sym|
32
+ class_eval(<<-EOS, __FILE__, __LINE__)
33
+ unless defined? @@#{sym} # unless defined? @@hair_colors
34
+ @@#{sym} = nil # @@hair_colors = nil
35
+ end # end
36
+ #
37
+ def self.#{sym}=(obj) # def self.hair_colors=(obj)
38
+ @@#{sym} = obj # @@hair_colors = obj
39
+ end # end
40
+ EOS
41
+ end
42
+ end
43
+
44
+ def cattr_accessor(*syms)
45
+ cattr_reader(*syms)
46
+ cattr_writer(*syms)
47
+ end
48
+ end
@@ -0,0 +1,24 @@
1
+ module Ciridiri
2
+ module Finders
3
+ # Convert `uri` to a path and return a new `Page` instance if the corresponding file exists.
4
+ # Return nil otherwise
5
+ def find_by_uri(uri)
6
+ content_path = path_from_uri(uri)
7
+ File.exists?(content_path) ? Page.new(uri, File.open(content_path).read) : nil
8
+ end
9
+
10
+ # Return a new empty `Page` instance if the corresponding file doesn't exists
11
+ def find_by_uri_or_empty(uri)
12
+ find_by_uri(uri) or Page.new(uri, '')
13
+ end
14
+
15
+ # Return an array of all `Page`s excluding backups
16
+ def all
17
+ Dir.chdir(content_dir) do
18
+ files = Dir.glob(File.join("**", "*#{SOURCE_FILE_EXT}")).delete_if {|p| p =~ Regexp.new("\\.[0-9]+\\#{SOURCE_FILE_EXT}$")}
19
+ files.collect {|f| Page.new(uri_from_path(f), File.open(f, 'r') {|b| b.read})}
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,149 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__)) unless $LOAD_PATH.include?(File.dirname(__FILE__))
2
+ %w[fileutils finders paths extensions].each {|r| require r}
3
+
4
+ module Ciridiri
5
+ class Page
6
+ extend Ciridiri::Finders, Ciridiri::Paths
7
+ ###Constants block
8
+
9
+ # A regular expression for markdown like headers (`#`, `##`, `###`, `=====`, `----`)
10
+ MD_TITLE = Regexp.new("(^\#{1,3}\\s*?([^#].*?)#*$)|(^ {0,3}(\\S.*?)\\n(?:=|-)+(?=\\n+|\\Z))", Regexp::MULTILINE)
11
+ # HTML headers (`<h1-3>`)
12
+ HTML_TITLE = Regexp.new("^<h[1-3](.*)?>(.*)+</h[1-3]>")
13
+
14
+ # File extensions
15
+ SOURCE_FILE_EXT = ".text".freeze
16
+ CACHED_FILE_EXT = ".html".freeze
17
+
18
+ ###Default values for all options
19
+
20
+ # Where pages should be stored on a file system
21
+ @@content_dir = '.'
22
+ # Should we create backups (`filename.1278278364.text`, where `1278278364` -- current timestamp) or not.
23
+ # Useful when you are not going to place `@@content_dir` under version control
24
+ @@backups = false
25
+ # Page fragments (formatted file `contents`) caching
26
+ @@caching = true
27
+
28
+ ####Formatter block
29
+
30
+ # You can use any formatter. For example:
31
+ #
32
+ # Bluecloth:
33
+ # require 'bluecloth'
34
+ # Page.formatter = lambda {|text| Bluecloth.new(text).to_html)}
35
+ #
36
+ # RDiscount:
37
+ # require 'rdiscount'
38
+ # Page.formatter = lambda {|text| RDiscount.new(text).to_html)}
39
+ #
40
+ # Rutils with RDiscount:
41
+ # require 'rutils'
42
+ # require 'rdiscount'
43
+ # Page.formatter = lambda {|text| RuTils::Gilenson::Formatter.new(RDiscount.new(text).to_html).to_html}
44
+ #
45
+ # HTML escaping:
46
+ # Page.formatter = {|text| "<pre>#{Rack::Utils.escape_html(text)}</pre>"}
47
+ #
48
+ @@formatter = lambda {|text| text}
49
+
50
+ # Define attr_reader/accessors
51
+ attr_accessor :title, :contents
52
+ attr_reader :path, :uri
53
+
54
+ # Class level attr_accessors. We use them for configuring: `Page.content_dir = '/tmp'`
55
+ cattr_accessor :content_dir, :backups, :caching, :formatter
56
+
57
+ ###Public methods
58
+
59
+ # Convert `uri` to `path`, find the `title` in `contents`
60
+ def initialize(uri, contents)
61
+ @path, @uri, @title, @contents = Page.path_from_uri(uri), uri, Page.find_title(contents), contents
62
+ end
63
+
64
+ # Create needed directory hierarchy and backup the file if needed.
65
+ # Write `@contents` to the file and return `true` or `false` if
66
+ # any error occured
67
+ def save
68
+ FileUtils.mkdir_p(File.dirname(@path)) unless File.exists?(@path)
69
+ backup if Page.backups? && File.exists?(@path)
70
+
71
+ begin
72
+ File.open(@path, "w") {|f| f.write(@contents)}
73
+ true
74
+ rescue StandardError
75
+ false
76
+ end
77
+ end
78
+
79
+ # Save `@contents` formatted with `Page.formatter` to the cache file
80
+ # `index.text` -> `index.text.html`
81
+ def cache!
82
+ File.open(@path + CACHED_FILE_EXT, 'w') {|f| f.write(@@formatter.call(@contents))}
83
+ end
84
+
85
+ # Delete the cache file
86
+ def sweep!
87
+ File.delete(@path + CACHED_FILE_EXT)
88
+ end
89
+
90
+ # Return an array of the page revisions
91
+ def revisions
92
+ @revisions ||= find_revisions
93
+ end
94
+
95
+ # Return `@contents` HTML representation.
96
+ # If a page fragments caching enabled (`Page.caching = true`) then
97
+ # regenerate the fragment cache (`index.text.html`) if needed (it's outdated or doesn't exist)
98
+ # and return the cached contents.
99
+ # Otherwise (`Page.caching = false`) return `@contents` formatted with `Page.formatter`
100
+ def to_html
101
+ if Page.caching?
102
+ cached = @path + CACHED_FILE_EXT
103
+ cache! if !File.exists?(cached) || File.mtime(@path) > File.mtime(cached)
104
+
105
+ File.open(cached).read
106
+ else
107
+ @@formatter.call(@contents)
108
+ end
109
+ end
110
+
111
+ # Tiny `attr_writer` for `@@content_dir` which creates the content directory if it doesn't exist
112
+ def self.content_dir=(dir)
113
+ @@content_dir = dir
114
+ FileUtils.mkdir_p(@@content_dir) if !File.exists?(@@content_dir)
115
+ end
116
+
117
+ ###Protected methods
118
+
119
+ protected
120
+ # Find the title in contents (html or markdown variant).
121
+ # Return `""` if nothing found.
122
+ def self.find_title(contents="")
123
+ if contents.detect {|s| s.match(MD_TITLE)}
124
+ $2.strip || $4.strip
125
+ elsif contents.detect {|s| s.match(HTML_TITLE)}
126
+ $2.strip
127
+ else
128
+ ""
129
+ end
130
+ end
131
+
132
+ # Collect only timestamps of revisions.
133
+ # `index.1273434670.text`, `index.1273434450.text` -> `["1273434670", "1273434450"]`
134
+ def find_revisions
135
+ Dir.chdir(File.dirname(@path)) do
136
+ basename = File.basename(@path, SOURCE_FILE_EXT)
137
+ Dir.glob(basename + ".*" + SOURCE_FILE_EXT).
138
+ collect {|f| File.basename(f, SOURCE_FILE_EXT).sub(basename, '')}
139
+ end
140
+ end
141
+
142
+ # Backup the file by copying it's current version to the same file but with the current timestamp.
143
+ # `index.text` -> `index.1273434670.text`
144
+ def backup
145
+ FileUtils.cp(@path, @path.sub(Regexp.new("#{SOURCE_FILE_EXT}$"), ".#{Time.now.to_i.to_s}#{SOURCE_FILE_EXT}"))
146
+ end
147
+
148
+ end
149
+ end
@@ -0,0 +1,12 @@
1
+ module Ciridiri
2
+ module Paths
3
+ # Convert `uri` to `path` in a file system including `content_dir` and a source file extension
4
+ # `/team/pro/chuck-norris` -> `content_dir/team/pro/chuck-norris.text`
5
+ def path_from_uri(uri)
6
+ path = uri.split("/")
7
+ filename = path.pop
8
+ File.join(content_dir, path, "#{filename}#{Page::SOURCE_FILE_EXT}")
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,240 @@
1
+ /*
2
+ based on:
3
+ +----------------------------------------------------------------------------------------------------+
4
+ | |
5
+ | TYPOGRIDPHY - TYPOGRAPHICAL AND GRID LAYOUT CSS FRAMEWORK FROM HARRY ROBERTS OF CSS WIZARDRY |
6
+ | |
7
+ +-------------------------------------------------+--------------------------------------------------+
8
+ | | |
9
+ | TYPOGRIDPHY IS © COPYRIGHT OF HARRY ROBERTS | v 0.1.1 |
10
+ | IT IS FREE TO BE USED AND MODIFIED PROVIDED | May 2008 |
11
+ | THIS TEXT REMAINS INTACT -- CSSWIZARDRY.COM | http://csswizardry.com |
12
+ | | |
13
+ +-------------------------------------------------+--------------------------------------------------+
14
+
15
+
16
+
17
+ COLOUR REFERENCES
18
+ BODY BG: #FFF
19
+ TOP STRIP: #000
20
+ BODY COLOUR: #444
21
+ LINKS: #000
22
+ -------------------------------------------------------- */
23
+
24
+
25
+ /* RESET */
26
+ body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td {
27
+ margin: 0;
28
+ padding: 0
29
+ }
30
+ table {
31
+ border-collapse: collapse;
32
+ border-spacing: 0
33
+ }
34
+ fieldset,img {
35
+ border: 0
36
+ }
37
+ address,caption,cite,code,dfn,em,strong,th,var {
38
+ font-style: normal;
39
+ font-weight: normal
40
+ }
41
+ ol,ul {
42
+ list-style: none
43
+ }
44
+ caption,th {
45
+ text-align: left
46
+ }
47
+ h1,h2,h3,h4,h5,h6 {
48
+ font-size: 100%;
49
+ font-weight: normal
50
+ }
51
+ q:before,q:after {
52
+ content:''
53
+ }
54
+ abbr,acronym {
55
+ border:0
56
+ }
57
+ /*---------- END RESET ----------*/
58
+
59
+
60
+ /*-------------------------------------------+
61
+ | |
62
+ | MAIN STRUCTURE STYLES |
63
+ | |
64
+ +-------------------------------------------*/
65
+ html {
66
+ font-size: 100%;
67
+ min-height: 101%
68
+ }
69
+ body {
70
+ font-family: Georgia, "Times New Roman", serif;
71
+ color: #444;
72
+ background: #fff;
73
+ border-top: .5em solid #444;
74
+ padding: 0 1em
75
+ }
76
+ #wrap {
77
+ width: 940px;
78
+ margin: 0 auto
79
+ }
80
+ #header {
81
+ padding-top: 1.5em;
82
+ margin: 0 0 2em
83
+ }
84
+ /*-------------------------------------------+
85
+ | |
86
+ | FONT STYLES |
87
+ | |
88
+ +-------------------------------------------*/
89
+ /*PARAGRAPHS
90
+ -------------------------------------------------------- */
91
+ p, pre {
92
+ line-height: 1.5em;
93
+ margin: 0 0 1.5em
94
+ }
95
+ /* Styles an introductory paragraph, similar to newspapers. Assign this class to the first paragraph in an article */
96
+ p.intro:first-line {
97
+ font-variant: small-caps
98
+ }
99
+ /* Styles a drop cap on each paragraph with this class */
100
+ p.drop:first-letter {
101
+ float: left;
102
+ font-size: 3em;
103
+ margin: -.05em .1em -.5em 0
104
+ }
105
+ /*HEADINGS
106
+ -------------------------------------------------------- */
107
+ h1, h2, h3, h4, h5, h6 {margin-bottom: .5em; color: #000}
108
+ h3, h4, h5 {font-variant: small-caps}
109
+ h1 {
110
+ font-size: 3em;
111
+ margin-top: .6em;
112
+ font-style: italic;
113
+ line-height: 1.2em
114
+ }
115
+ h2 {
116
+ font-size: 2em;
117
+ margin-top: .9em;
118
+ line-height: .9em
119
+ }
120
+ h3 {
121
+ font-size: 1.5em;
122
+ margin-top: 1.2em;
123
+ line-height: 1.2em
124
+ }
125
+ h4 {
126
+ font-size: 1.2em;
127
+ margin-top: 1.5em;
128
+ line-height: 1.5em
129
+ }
130
+ h5 {
131
+ font-size: 1em;
132
+ margin-top: 1.8em;
133
+ line-height: 1.8em
134
+ }
135
+ h6 {
136
+ font-size: 1em;
137
+ margin-top: 1.8em;
138
+ line-height: 1.8em
139
+ }
140
+ /*LINKS
141
+ -------------------------------------------------------- */
142
+ a {color: #000}
143
+ a:hover {text-decoration: none}
144
+ /*ALL THE TRIMMINGS
145
+ -------------------------------------------------------- */
146
+ blockquote p {
147
+ font-size: 1.2em!important;
148
+ line-height: 1.5em!important;
149
+ margin-bottom: 1.5em!important;
150
+ font-style: italic;
151
+ font-weight: bold
152
+ }
153
+ blockquote p cite {font-style: normal}
154
+ strong {font-variant:small-caps}
155
+ em {
156
+ font-style: italic;
157
+ font-weight: inherit
158
+ }
159
+ .amp { /* Give those ampersands a right sexy look */
160
+ font-family: Baskerville, "Goudy Old Style", "Palatino", "Book Antiqua", serif;
161
+ font-style: italic;
162
+ font-weight: normal;
163
+ line-height: inherit
164
+ }
165
+ abbr {
166
+ border-bottom: 1px dotted;
167
+ cursor: help
168
+ }
169
+ .clear {clear:both}
170
+ .right-float { /* Float any item to the right */
171
+ float: right;
172
+ margin: 0 0 0 2em
173
+ }
174
+ .left-float { /* Float any item to the left */
175
+ float:left;
176
+ margin: 0 2em 0 0
177
+ }
178
+ pre, code { /* Styling for and code type items */
179
+ font-family: consolas, lucida console, bitstream vera sans mono, courier new, monospace;
180
+ font-size: 87.5%;
181
+ color: #007A00
182
+ }
183
+ pre code {font-size: 1em}
184
+ /*-------------------------------------------+
185
+ | |
186
+ | IMAGE STYLES |
187
+ | |
188
+ +-------------------------------------------*/
189
+ img {font-size: 1em}
190
+ img.left-img { /* Float any image to the LEFT and give it some margin */
191
+ float: left;
192
+ padding: 4px;
193
+ border: 1px solid #ccc;
194
+ margin: .3em 2em 1.8em 0
195
+ }
196
+ img.right-img { /* Float any image to the RIGHT and give it some margin */
197
+ float: right;
198
+ padding: 4px;
199
+ border: 1px solid #ccc;
200
+ margin: .3em 0 1.8em 2em
201
+ }
202
+ /*-------------------------------------------+
203
+ | |
204
+ | LIST STYLES |
205
+ | |
206
+ +-------------------------------------------*/
207
+ ul {
208
+ margin-bottom: 1.8em;
209
+ list-style: square inside;
210
+ }
211
+ ul li {line-height:1.5em}
212
+ ul li.caption { /* Apply this class to the first list item in a list to give it a caption */
213
+ font-variant: small-caps;
214
+ list-style: none;
215
+ color: #000
216
+ }
217
+ li > ul, li > ol {
218
+ margin-bottom: 0;
219
+ margin-left: 5em
220
+ }
221
+ li > ul li, li > ol li {font-size: 1em}
222
+ ol {
223
+ margin-bottom: 1.8em;
224
+ list-style: decimal inside
225
+ }
226
+ ol li {line-height:1.5em}
227
+ /*-------------------------------------------+
228
+ | |
229
+ | MISC. STYLES |
230
+ | |
231
+ +-------------------------------------------*/
232
+ .submits {margin: 1em 0}
233
+ .submits input {font-size: 112%}
234
+
235
+ /*
236
+
237
+ "I could eat a knob at night"
238
+ - Karl Pilkington
239
+
240
+ */
@@ -0,0 +1,31 @@
1
+ body {
2
+ margin: 0;
3
+ padding: 0;
4
+ color: #252525;
5
+ font: normal 12pt/1.5 Cambria, Georgia, serif
6
+ }
7
+
8
+ a {text-decoration: none; color: #252525}
9
+ a img {border: 0}
10
+ h1, h2, h3, h4 {
11
+ margin: .8em 0 8px;
12
+ font-weight: normal;
13
+ color: #000;
14
+ line-height: 1.2
15
+ }
16
+ h1 {font-size: 250%}
17
+ h2 {font-size: 200%}
18
+ h3 {font-size: 150%}
19
+ h4 {font-size: 125%}
20
+ h5 {font-size: 110%}
21
+ p {margin: 0 0 .6em}
22
+ blockquote {font-style: italic}
23
+ pre, code {
24
+ font-family: monaco, lucida console, bitstream vera sans mono, monospace;
25
+ font-size: 10pt
26
+ }
27
+ h3+blockquote, h2+blockquote, p+ul, h2+ul, h3+ul {margin-top: .2em}
28
+
29
+ .noprint, form {
30
+ display: none
31
+ }
@@ -0,0 +1,22 @@
1
+ document.onkeydown = NavigateThrough;
2
+
3
+ function NavigateThrough (event) {
4
+ if (!document.getElementById) return;
5
+ if (window.event) event = window.event;
6
+ if (event.ctrlKey && event.shiftKey) {
7
+ var link = null;
8
+ var href = null;
9
+ switch (event.keyCode ? event.keyCode: event.which ? event.which: null) {
10
+ case 0x45:
11
+ link = document.getElementById ('edit-link');
12
+ break;
13
+ }
14
+
15
+ if (link && link.href) document.location = link.href;
16
+ if (href) document.location = href;
17
+ }
18
+ }
19
+
20
+ function ctrlEnterSubmit(e, form) {
21
+ if (((e.keyCode == 13) || (e.keyCode == 10)) && (e.ctrlKey == true)) form.submit();
22
+ }
@@ -0,0 +1,81 @@
1
+ require 'test_helper'
2
+ require 'rack/test'
3
+
4
+ class CiridiriTest < Test::Unit::TestCase
5
+ include Rack::Test::Methods
6
+ describe "Ciridiri web-app" do
7
+ def app
8
+ Ciridiri::Application
9
+ end
10
+
11
+ should "redirect from root to index" do
12
+ get '/'
13
+ assert_redirect('/index.html')
14
+ end
15
+
16
+ should "get existent page" do
17
+ page = page_stub
18
+ page.save
19
+
20
+ get "#{page.uri}.html"
21
+ assert last_response.ok?
22
+ assert last_response.body.include?(page.title)
23
+ end
24
+
25
+ should "redirect to edit form if page not found" do
26
+ get "/nonexistent.html"
27
+ assert_redirect("/nonexistent.html.e")
28
+ end
29
+
30
+ should "show an empty edit form" do
31
+ get "/nonexistent.html.e"
32
+ assert last_response.ok?
33
+ assert last_response.body.include?("form")
34
+ end
35
+
36
+ should "create a new page" do
37
+ post "/foo.html", :contents => 'fut-fut-fut, freeeeestylo'
38
+ assert_redirect("/foo.html")
39
+ follow_redirect!
40
+ assert last_response.ok?
41
+ assert last_response.body.include?('freeeeestylo')
42
+ end
43
+
44
+ should "edit an existent page" do
45
+ page = page_stub
46
+ page.save
47
+
48
+ get "#{page.uri}.html.e"
49
+ assert last_response.ok?
50
+ assert last_response.body.include?("textarea")
51
+ assert last_response.body.include?(page.contents)
52
+ end
53
+
54
+ should "update an existent page" do
55
+ page = page_stub
56
+ page.save
57
+
58
+ post "#{page.uri}.html", :contents => "new contents"
59
+ assert_redirect("#{page.uri}.html")
60
+ follow_redirect!
61
+ assert last_response.ok?
62
+ assert last_response.body.include?("new contents")
63
+ end
64
+
65
+ should "provide the edit-link" do
66
+ page = page_stub
67
+ page.save
68
+
69
+ get "#{page.uri}.html"
70
+ assert last_response.ok?
71
+ assert last_response.body.include?("edit-link")
72
+ assert last_response.body.include?("#{page.uri}.html.e")
73
+ end
74
+
75
+ protected
76
+ def assert_redirect(uri)
77
+ assert last_response.redirect?
78
+ assert_equal last_response.headers['Location'], uri
79
+ end
80
+ end
81
+ end
data/test/page_test.rb ADDED
@@ -0,0 +1,101 @@
1
+ require 'test_helper'
2
+
3
+ class PageTest < Test::Unit::TestCase
4
+ describe "Ciridiri::Page" do
5
+ should "create correct page" do
6
+ @page = page_stub
7
+ assert_not_nil @page.contents
8
+ assert_not_nil @page.title
9
+ assert_equal 'awesome title', @page.title
10
+ end
11
+
12
+ should "save page" do
13
+ @page = page_stub
14
+ assert @page.save
15
+ assert File.exists?(@page.path)
16
+ assert File.size(@page.path) > 0
17
+ end
18
+
19
+ should "parse html and md titles" do
20
+ @page = page_stub
21
+ @else_one_page = page_stub("index", "<h3 class=\"title\">awesome title</h3>\n hello, everyone!")
22
+ assert_equal @page.title, "awesome title"
23
+ assert_equal @page.title, @else_one_page.title
24
+ end
25
+
26
+ should "update page" do
27
+ @page = page_stub
28
+ @page.save
29
+ @page.contents = "#new title\nand new content"
30
+ assert @page.save
31
+ assert File.open(@page.path).read.include?('and new content')
32
+ end
33
+
34
+ should "find page by uri" do
35
+ @page = page_stub("hidden/blah")
36
+ @page.save
37
+
38
+ @p = Page.find_by_uri('hidden/blah')
39
+ assert_not_nil @p
40
+ assert_not_nil @p.title
41
+ assert_not_nil @p.contents
42
+ end
43
+
44
+ should "return nil if page is not found" do
45
+ assert_nil Page.find_by_uri('nonexistent-uri')
46
+ end
47
+
48
+ should "return empty page if needed" do
49
+ assert_not_nil Page.find_by_uri_or_empty('nonexistent-uri')
50
+ end
51
+
52
+ should "respect uri hierarchy" do
53
+ @page = page_stub('about/team/boris')
54
+ @page.save
55
+
56
+ target_path = File.expand_path(File.join(Page.content_dir, %w[about team], "boris#{Page::SOURCE_FILE_EXT}"))
57
+ assert File.exists?(target_path)
58
+ assert_equal target_path, File.expand_path(@page.path)
59
+ end
60
+
61
+ should "create backups if needed" do
62
+ begin
63
+ Page.backups = true
64
+ @page = page_stub
65
+ assert @page.save
66
+ @page.contents = "foo bar"
67
+ assert @page.save
68
+
69
+ assert_not_nil @page.revisions
70
+ assert_equal @page.revisions.length, 1
71
+ ensure
72
+ Page.backups = false
73
+ end
74
+ end
75
+
76
+ should "format contents" do
77
+ begin
78
+ @page = page_stub
79
+ @page.save
80
+ assert_equal @page.contents, @page.to_html
81
+ Page.formatter = lambda {|t| "<censored />"}
82
+ assert_equal @page.to_html, "<censored />"
83
+ ensure
84
+ Page.formatter = lambda {|t| t}
85
+ end
86
+ end
87
+
88
+ should "cache page" do
89
+ begin
90
+ Page.caching = true
91
+ @page = page_stub
92
+ @page.save
93
+ assert_not_nil @page.to_html
94
+ assert File.exists?(@page.path + Page::CACHED_FILE_EXT)
95
+ assert File.size(@page.path + Page::CACHED_FILE_EXT) > 0
96
+ ensure
97
+ Page.caching = false
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'contest'
4
+ require 'lib/ciridiri'
5
+ begin; require 'turn'; rescue LoadError; end
6
+
7
+ Ciridiri::Page.content_dir = File.join(File.dirname(__FILE__), 'pages')
8
+ Ciridiri::Page.caching = false
9
+
10
+ class Test::Unit::TestCase
11
+ include Ciridiri
12
+
13
+ class << self
14
+ alias_method :it, :test
15
+ end
16
+
17
+ def teardown
18
+ #recreate an empty content directory
19
+ FileUtils.rm_rf(Page.content_dir)
20
+ FileUtils.mkdir(Page.content_dir)
21
+ end
22
+
23
+ protected
24
+ def page_stub(uri = '/index', body = "##awesome title\n hello, everyone!")
25
+ Page.new(uri, body)
26
+ end
27
+
28
+ end
data/views/edit.erb ADDED
@@ -0,0 +1,16 @@
1
+ <h1>Edit page</h1>
2
+ <script type="text/javascript">
3
+ window.onload = function() {
4
+ document.getElementById('p-contents').focus();
5
+ }
6
+ </script>
7
+ <form action="<%= @page.uri %>.html" method="post" onkeypress="ctrlEnterSubmit(event, this)">
8
+ <fieldset>
9
+ <div>
10
+ <textarea rows="30" cols="20" id="p-contents" name="contents" style="width: 100%"><%= h @page.contents %></textarea>
11
+ </div>
12
+ </fieldset>
13
+ <fieldset class="submits">
14
+ <input type="submit" value="Save" /> or <a href="<%= @page.uri %>.html">cancel</a>
15
+ </fieldset>
16
+ </form>
data/views/layout.erb ADDED
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5
+ <link rel="stylesheet" media="screen, projection" href="/css/base.css" />
6
+ <link rel="stylesheet" media="print" href="/css/print.css" />
7
+ <% if @page && !(request.fullpath =~ /\.e$/) %>
8
+ <link rel="alternate" id="edit-link" type="application/x-wiki" title="Edit current page" href="<%= @page.uri %>.html.e" />
9
+ <% end %>
10
+ <% if request.fullpath =~ /\.e$/ %>
11
+ <meta name="robots" content="noindex" />
12
+ <% end %>
13
+ <script src="/js/application.js"></script>
14
+ <title>
15
+ <%= h @page.title + " / " if @page && @page.title%>
16
+ ciridiri.rb: dead simple wiki engine
17
+ </title>
18
+ </head>
19
+ <body>
20
+ <div id="wrap">
21
+ <div id="page">
22
+ <%= yield %>
23
+ </div>
24
+ </div>
25
+ </body>
26
+ </html>
data/views/show.erb ADDED
@@ -0,0 +1,3 @@
1
+ <div class="contents">
2
+ <%= @page.to_html %>
3
+ </div>
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ciridiri
3
+ version: !ruby/object:Gem::Version
4
+ hash: 61
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 8
9
+ - 1
10
+ version: 0.8.1
11
+ platform: ruby
12
+ authors:
13
+ - Vasily Polovnyov
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-01 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: sinatra
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 57
30
+ segments:
31
+ - 0
32
+ - 9
33
+ - 1
34
+ version: 0.9.1
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rack-test
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 19
46
+ segments:
47
+ - 0
48
+ - 3
49
+ - 0
50
+ version: 0.3.0
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: contest
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 27
62
+ segments:
63
+ - 0
64
+ - 1
65
+ - 0
66
+ version: 0.1.0
67
+ type: :development
68
+ version_requirements: *id003
69
+ description: Dead simple wiki engine
70
+ email: vasily@polovnyov.ru
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files:
76
+ - README.md
77
+ files:
78
+ - README.md
79
+ - Rakefile
80
+ - config.ru
81
+ - lib/ciridiri.rb
82
+ - lib/ciridiri/extensions.rb
83
+ - lib/ciridiri/finders.rb
84
+ - lib/ciridiri/page.rb
85
+ - lib/ciridiri/paths.rb
86
+ - public/css/base.css
87
+ - public/css/print.css
88
+ - public/js/application.js
89
+ - test/ciridiri_test.rb
90
+ - test/page_test.rb
91
+ - test/test_helper.rb
92
+ - views/edit.erb
93
+ - views/layout.erb
94
+ - views/show.erb
95
+ has_rdoc: true
96
+ homepage: http://vast.github.com/ciridiri.rb
97
+ licenses: []
98
+
99
+ post_install_message:
100
+ rdoc_options:
101
+ - --charset=UTF-8
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ hash: 3
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ requirements: []
123
+
124
+ rubyforge_project:
125
+ rubygems_version: 1.3.7
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Dead simple wiki engine
129
+ test_files:
130
+ - test/test_helper.rb
131
+ - test/ciridiri_test.rb
132
+ - test/page_test.rb