ciridiri 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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