marley 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Favicon.ico +0 -0
- data/README.rdoc +51 -0
- data/TODO +17 -0
- data/examples/empty.sqlite3 +0 -0
- data/examples/forum.css +3 -0
- data/examples/forum.js +23 -0
- data/examples/forum.rb +20 -0
- data/examples/forum.sql +42 -0
- data/examples/forum.sqlite3 +0 -0
- data/examples/forum_test.sqlite3 +0 -0
- data/examples/run.sh +14 -0
- data/lib/client/jamaica.css +270 -0
- data/lib/client/jamaica.js +353 -0
- data/lib/client/jamaica.rb +38 -0
- data/lib/client/jquery-1.6.2.js +8981 -0
- data/lib/client/jquery.form.js +814 -0
- data/lib/controllers.rb +69 -0
- data/lib/joint.rb +27 -0
- data/lib/joints/basic_menu_system.rb +54 -0
- data/lib/joints/basic_messaging.rb +88 -0
- data/lib/joints/basic_user.rb +51 -0
- data/lib/joints/tagged_messaging.rb +122 -0
- data/lib/joints/tagging.rb +60 -0
- data/lib/joints/user_based_navigation.rb +39 -0
- data/lib/marley.rb +148 -0
- data/lib/reggae.rb +110 -0
- data/lib/sequel_plugins.rb +141 -0
- data/lib/test_helpers.rb +52 -0
- data/marley-0.1.gem +0 -0
- data/marley.gemspec +16 -0
- data/reggae.ebnf +60 -0
- data/test/forum_tests.rb +356 -0
- metadata +116 -0
data/Favicon.ico
ADDED
File without changes
|
data/README.rdoc
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
=Marley
|
2
|
+
|
3
|
+
Marley is a project consisting of several parts:
|
4
|
+
|
5
|
+
* A server side micro-framework for returning data from ORM models (currently only Sequel) and other objects (Ruby, based on rack with thin as the default web server)
|
6
|
+
* A framework for adding reusable content to the framework ("Joints")
|
7
|
+
* A JSON data format ("Reggae")
|
8
|
+
* A simple JS client ("Jamaica") which renders Reggae as HTML.
|
9
|
+
|
10
|
+
The point of the whole thing is to create as many useful default behaviors as possible, while making it easy to override any or all of them.
|
11
|
+
|
12
|
+
Please see the examples/forum.rb to get a basic idea of what a Marley application looks like.
|
13
|
+
|
14
|
+
-----
|
15
|
+
|
16
|
+
More about each part:
|
17
|
+
|
18
|
+
==Marley
|
19
|
+
|
20
|
+
* Marley resources are constants in the Marley::Resources namespace.
|
21
|
+
* A resource must respond either to a #controller method or to one or more of #rest_get, #rest_post, #rest_put, or #rest_delete.
|
22
|
+
* Resources implementing a #controller method should return an object that responds to one or more REST verbs from that method
|
23
|
+
* REST verb methods should return an object which responds to #to_json by returning a string to be sent to the client
|
24
|
+
|
25
|
+
* Marley provides 2 plugins for the Sequel ORM.
|
26
|
+
* RestConvenience - Adds a default controller for standard rest routes to a model
|
27
|
+
* RestAuthorization - Adds default authorization to a model
|
28
|
+
|
29
|
+
I use Sequel exclusively, so I've only written these plugins for it. I imagine it would be pretty trivial to port them to other ORMs.
|
30
|
+
|
31
|
+
==Joints
|
32
|
+
|
33
|
+
"Joints" are pre-packaged resource sets that can be included in a Marley application. The joints API is very much a work in progress.
|
34
|
+
|
35
|
+
For now, there are 3 joints included in the Marley distribution:
|
36
|
+
|
37
|
+
* Basic User
|
38
|
+
* Basic Messaging
|
39
|
+
* Tagging
|
40
|
+
|
41
|
+
With a bit of configuration and a few menus, these comprise the example forum application, which is in turn the targt for the test suite.
|
42
|
+
|
43
|
+
==Reggae
|
44
|
+
|
45
|
+
The server and client use a JSON based data representation I developed for this project and tentatively named "Reggae." It is documented roughly in Reggae.ebnf. I am considering some structural changes, which are reflected in Reggae2.ebnf. I'd love some comments on this.
|
46
|
+
|
47
|
+
==Jamaica
|
48
|
+
|
49
|
+
The default Marley client is "Jamaica", which consists of JS/CSS for browsers. It sucks right now and I'm hoping somebody takes it over as a sub-project, but it does work - at least on FF.
|
50
|
+
|
51
|
+
|
data/TODO
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
- improve testing coverage
|
4
|
+
- improve documentation - esp API
|
5
|
+
|
6
|
+
- ditch Sequel dependency
|
7
|
+
|
8
|
+
- automate sequel plugin loading in options
|
9
|
+
|
10
|
+
- better logging
|
11
|
+
|
12
|
+
- hints for resources/columns
|
13
|
+
- better filtering options for posts
|
14
|
+
|
15
|
+
??? expose validation reflections to views
|
16
|
+
??? caching protocol for autocompleters/validation options
|
17
|
+
|
Binary file
|
data/examples/forum.css
ADDED
@@ -0,0 +1,3 @@
|
|
1
|
+
a.expandCollapse, a.expandCollapseAll{background-color:#398235;color:#fff;text-decoration:none;margin:.5em;font-weight:bold;}
|
2
|
+
.collapsed > .instance > .colContainer,.collapsed > .instance > .instanceActions{display:none;}
|
3
|
+
.collapsed > .instance > .post__titleColContainer,.collapsed > .instance > .private_message__titleColContainer{display:block;}
|
data/examples/forum.js
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$(function(){
|
2
|
+
$("body").ajaxComplete(function(){
|
3
|
+
$(".postInstance,.private_messageInstance").parent().not('.collapsible').prepend(["a",{"href":"#","class":"expandCollapse"},"collapse"].toDom()).prepend(["a",{"href":"#","class":"expandCollapseAll"},"collapse from here"].toDom());
|
4
|
+
$(".postInstance,.private_messageInstance").parent().not('.collapsible').addClass('collapsible');
|
5
|
+
});
|
6
|
+
$(".expandCollapse").live('click',collapseExpand);
|
7
|
+
$(".expandCollapseAll").live('click',collapseExpandAll);
|
8
|
+
});
|
9
|
+
|
10
|
+
function collapseExpand() {
|
11
|
+
$(this).closest('.collapsible').toggleClass('collapsed').toggleClass('expanded');
|
12
|
+
$(this).html($(this).html()=='collapse' ? 'expand' : 'collapse');
|
13
|
+
return false;
|
14
|
+
}
|
15
|
+
function collapseExpandAll() {
|
16
|
+
var affected=$(this).closest('.collapsible').find('.collapsible').andSelf();
|
17
|
+
if ($(this).html().match(/collapse/)){
|
18
|
+
affected.addClass('collapsed').removeClass('expanded').find('.expandCollapse,.expandCollapseAll').each(function(){$(this).html($(this).html().replace(/collapse/,'expand'))});
|
19
|
+
} else {
|
20
|
+
affected.removeClass('collapsed').addClass('expanded').find('.expandCollapse,.expandCollapseAll').each(function(){$(this).html($(this).html().replace(/expand/,'collapse'))});
|
21
|
+
}
|
22
|
+
return false;
|
23
|
+
}
|
data/examples/forum.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sequel'
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
5
|
+
$: << "#{File.dirname(__FILE__)}/../lib/"
|
6
|
+
|
7
|
+
APP_DIR=File.dirname(__FILE__)
|
8
|
+
require "marley"
|
9
|
+
require "client/jamaica"
|
10
|
+
#need to automate the following somehow but can't think of anything that isn't ugly ATM
|
11
|
+
DB=Sequel.sqlite("#{APP_DIR}/forum#{ENV["MARLEY_TESTING"] ? '_test' : ''}.sqlite3")#,:loggers => [Logger.new($stdout)])
|
12
|
+
|
13
|
+
RESERVED_PM_TAGS=['inbox','sent']
|
14
|
+
RESERVED_POST_TAGS=['announcement']
|
15
|
+
|
16
|
+
Marley.config({:app_name => 'The Forum',:client => Marley::Client.new({:app_name => 'The Forum'})})
|
17
|
+
|
18
|
+
|
19
|
+
Marley.joint 'tagged_messaging'
|
20
|
+
Marley.joint 'basic_menu_system'
|
data/examples/forum.sql
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
CREATE TABLE tags (id integer PRIMARY KEY,user_id integer, tag text);
|
2
|
+
CREATE INDEX tag_user_id on tags(user_id);
|
3
|
+
CREATE INDEX tag_tag on tags(tag);
|
4
|
+
CREATE UNIQUE INDEX tag_tag_user_id on tags(user_id,tag);
|
5
|
+
|
6
|
+
CREATE TABLE messages_tags ( id integer PRIMARY KEY, message_id integer, tag_id integer);
|
7
|
+
CREATE INDEX msg_tag_id on messages_tags(tag_id);
|
8
|
+
CREATE INDEX tag_msg_id on messages_tags(message_id);
|
9
|
+
|
10
|
+
CREATE TABLE messages (
|
11
|
+
id integer PRIMARY KEY,
|
12
|
+
message_type text,
|
13
|
+
author_id integer,
|
14
|
+
recipients text,
|
15
|
+
thread_id integer,
|
16
|
+
parent_id integer,
|
17
|
+
date_created datetime,
|
18
|
+
date_updated datetime,
|
19
|
+
title text,
|
20
|
+
message clob
|
21
|
+
);
|
22
|
+
CREATE INDEX message on messages(message);
|
23
|
+
CREATE INDEX message_author on messages(author_id);
|
24
|
+
CREATE INDEX message_parent on messages(parent_id);
|
25
|
+
CREATE INDEX message_thread on messages(thread_id);
|
26
|
+
CREATE INDEX message_title on messages(title);
|
27
|
+
CREATE INDEX message_type on messages(message_type);
|
28
|
+
CREATE INDEX thread on messages(thread_id);
|
29
|
+
|
30
|
+
CREATE TABLE users (
|
31
|
+
id INTEGER PRIMARY KEY,
|
32
|
+
user_type TEXT,
|
33
|
+
date_created datetime,
|
34
|
+
name TEXT,
|
35
|
+
email TEXT,
|
36
|
+
pw_hash TEXT,
|
37
|
+
active boolean default true,
|
38
|
+
description clob);
|
39
|
+
CREATE INDEX users_active on users(active);
|
40
|
+
CREATE UNIQUE INDEX users_email on users(email);
|
41
|
+
CREATE UNIQUE INDEX users_name on users(name);
|
42
|
+
CREATE INDEX users_user_type on users(user_type);
|
Binary file
|
Binary file
|
data/examples/run.sh
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# directory of the marley base directory
|
4
|
+
MARLEY_DIR=..
|
5
|
+
|
6
|
+
# directory of the marley libs
|
7
|
+
MARLEY_LIB=$MARLEY_DIR/lib
|
8
|
+
|
9
|
+
# ruby interpreter to use (1.8, 1.9)
|
10
|
+
RUBY=ruby
|
11
|
+
#RUBY=/opt/local/bin/ruby1.9
|
12
|
+
|
13
|
+
RUBYLIB=$MARLEY_LIB $RUBY ./simple_forum.rb run
|
14
|
+
|
@@ -0,0 +1,270 @@
|
|
1
|
+
html, body, div, span, h1, p, strong, ul, li, form, label {margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;}
|
2
|
+
input {vertical-align:middle;}
|
3
|
+
h1 {font-weight: bold;}
|
4
|
+
body {background: #C9DE96; color: #000; font:13px/1.231 sans-serif; *font-size:small;}
|
5
|
+
input, textarea {font:sans-serif;}
|
6
|
+
textarea {overflow: auto;}
|
7
|
+
input[type=submit] {cursor: pointer;}
|
8
|
+
input, textarea {margin: 0;}
|
9
|
+
body {height: 100%; min-height: 100%;}
|
10
|
+
.hidden {display: none;}
|
11
|
+
.editable {
|
12
|
+
border: 1px dotted #398235;
|
13
|
+
cursor: pointer;
|
14
|
+
padding: 2px 5px;
|
15
|
+
}
|
16
|
+
.editable:after {
|
17
|
+
content: "\270E";
|
18
|
+
font-size: 125%;
|
19
|
+
padding: 0 0 0 3px;
|
20
|
+
}
|
21
|
+
#signup__section,
|
22
|
+
#main__section {
|
23
|
+
background: #fff;
|
24
|
+
border: 1px solid #398235;
|
25
|
+
-moz-border-radius: 8px;
|
26
|
+
border-radius: 8px;
|
27
|
+
-moz-box-shadow: 0 0 10px rgba(51,51,51,0.5);
|
28
|
+
box-shadow: 0 0 10px rgba(51,51,51,0.5);
|
29
|
+
margin: 20px auto;
|
30
|
+
width: 960px;
|
31
|
+
}
|
32
|
+
#signup__section {
|
33
|
+
height: 350px;
|
34
|
+
left: 50%;
|
35
|
+
margin: -175px 0 0 -480px;
|
36
|
+
position: absolute;
|
37
|
+
top: 50%;
|
38
|
+
}
|
39
|
+
#signup__section h1,
|
40
|
+
#main__section > h1 {
|
41
|
+
background: #398235;
|
42
|
+
border-bottom: 1px solid #398235;
|
43
|
+
-moz-border-radius-topright: 8px;
|
44
|
+
-moz-border-radius-topleft: 8px;
|
45
|
+
border-top-right-radius: 8px;
|
46
|
+
border-top-left-radius: 8px;
|
47
|
+
color: #fff;
|
48
|
+
font: bold 167%/150% "Rockwell STD", Rockwell, serif;
|
49
|
+
margin: 0 0 1em;
|
50
|
+
text-align: center;
|
51
|
+
text-transform: capitalize;
|
52
|
+
}
|
53
|
+
#signup__section .sectionDescription {font-weight: bold;}
|
54
|
+
#signup__section .sectionDescription,
|
55
|
+
#main__section > .sectionDescription,
|
56
|
+
#signup_section {margin: 0 20px 20px;}
|
57
|
+
#signup_section li {
|
58
|
+
display: inline-block;
|
59
|
+
vertical-align: top;
|
60
|
+
width: 440px;
|
61
|
+
}
|
62
|
+
#signup_section li:first-child {margin: 0 40px 0 0;}
|
63
|
+
#signup_navigation li > div {
|
64
|
+
border: 1px solid #398235;
|
65
|
+
-moz-border-radius: 8px;
|
66
|
+
border-radius: 8px;
|
67
|
+
overflow: hidden;
|
68
|
+
}
|
69
|
+
#signup_navigation li .form_description {
|
70
|
+
background: #398235;
|
71
|
+
border-bottom: 1px solid #398235;
|
72
|
+
-moz-border-radius-topright: 8px;
|
73
|
+
-moz-border-radius-topleft: 8px;
|
74
|
+
border-top-right-radius: 8px;
|
75
|
+
border-top-left-radius: 8px;
|
76
|
+
color: #fff;
|
77
|
+
font-size: 85%;
|
78
|
+
font-weight: bold;
|
79
|
+
line-height: 200%;
|
80
|
+
margin: 0 0 1.5em;
|
81
|
+
text-align: center;
|
82
|
+
}
|
83
|
+
#signup_section .colContainer {margin: 0 20px 1em;}
|
84
|
+
#signup_section input {width: 240px;}
|
85
|
+
#signup_section input[type=submit] {
|
86
|
+
margin: 0 20px 20px 0;
|
87
|
+
width: auto;
|
88
|
+
}
|
89
|
+
#signup_section label {width: 150px;}
|
90
|
+
label {
|
91
|
+
color: #398235;
|
92
|
+
display: inline-block;
|
93
|
+
font: bold 85%/1.5em sans-serif;
|
94
|
+
width: 150px;
|
95
|
+
}
|
96
|
+
input,
|
97
|
+
textarea {
|
98
|
+
border: 1px solid #398235;
|
99
|
+
line-height: 1.5em;
|
100
|
+
}
|
101
|
+
textarea {
|
102
|
+
height: 150px;
|
103
|
+
line-height: 1.25em;
|
104
|
+
margin: 0 0 1em;
|
105
|
+
width: 100%;
|
106
|
+
}
|
107
|
+
input[type=submit],
|
108
|
+
#main__section .navigation li a,
|
109
|
+
.instanceActions a {
|
110
|
+
background: -moz-linear-gradient(top, #c9de96 0%, #398235 100%);
|
111
|
+
background: linear-gradient(top, #c9de96 0%,#398235 100%);
|
112
|
+
border: 1px solid #398235;
|
113
|
+
-moz-border-radius: 8px;
|
114
|
+
border-radius: 8px;
|
115
|
+
color: #fff;
|
116
|
+
font: bold 93% sans-serif;
|
117
|
+
margin: 0 0 0 150px;
|
118
|
+
padding: 5px 10px;
|
119
|
+
text-decoration: none;
|
120
|
+
text-transform: uppercase;
|
121
|
+
width: auto;
|
122
|
+
}
|
123
|
+
input[type=submit]:hover,
|
124
|
+
#main__section .navigation li a:hover,
|
125
|
+
.instanceActions a:hover {
|
126
|
+
background: -moz-linear-gradient(top, #398235 0%, #c9de96 100%);
|
127
|
+
background: linear-gradient(top, #398235 0%,#c9de96 100%);
|
128
|
+
color: #fff;
|
129
|
+
cursor: pointer;
|
130
|
+
font-weight: bold;
|
131
|
+
}
|
132
|
+
#main__section #main_navigation {
|
133
|
+
border-bottom: 1px solid #398235;
|
134
|
+
margin: 0 20px 20px;
|
135
|
+
padding: 0 0 20px;
|
136
|
+
text-align: center;
|
137
|
+
}
|
138
|
+
#main__section #main_navigation li {
|
139
|
+
display: inline-block;
|
140
|
+
margin: 0 10px;
|
141
|
+
}
|
142
|
+
#main__section > #main_navigation li a,
|
143
|
+
#main__section > .content > .section > .navigation li a {margin: 0;}
|
144
|
+
#main__section > .content {margin: 20px;}
|
145
|
+
#main__section > .content > .section {
|
146
|
+
overflow: hidden;
|
147
|
+
padding: 0 0 0 225px;
|
148
|
+
}
|
149
|
+
#main__section > .content h1,
|
150
|
+
#main__section > .content .sectionDescription,
|
151
|
+
#main__section > .content .navigation {
|
152
|
+
clear: left;
|
153
|
+
float: left;
|
154
|
+
margin: 0 0 0 -225px;
|
155
|
+
width: 200px;
|
156
|
+
}
|
157
|
+
#main__section > .content h1 {
|
158
|
+
color: #398235;
|
159
|
+
font: bold 167%/150% "Rockwell STD", Rockwell, serif;
|
160
|
+
text-transform: capitalize;
|
161
|
+
}
|
162
|
+
#main__section .navigation li {
|
163
|
+
display: block;
|
164
|
+
margin: 0 0 0.25em;
|
165
|
+
}
|
166
|
+
#main__section .content .navigation li a {
|
167
|
+
display: block;
|
168
|
+
font-size: 77%;
|
169
|
+
text-transform: none;
|
170
|
+
}
|
171
|
+
#main__section > .content > .section > .content {
|
172
|
+
float: left;
|
173
|
+
width: 695px;
|
174
|
+
}
|
175
|
+
#main__section > .content > .section > .content > .instanceContainer {
|
176
|
+
border: 1px solid #398235;
|
177
|
+
-moz-border-radius: 8px;
|
178
|
+
border-radius: 8px;
|
179
|
+
margin: 0 0 2em;
|
180
|
+
}
|
181
|
+
#main__section > .content > .section > .content .instanceContainer .instanceContainer {
|
182
|
+
border: 1px solid #398235;
|
183
|
+
border-width: 1px 0 0 1px;
|
184
|
+
margin: 0 0 0 5em;
|
185
|
+
}
|
186
|
+
#main__section > .content > .section > .content .instance {
|
187
|
+
overflow: hidden;
|
188
|
+
padding: 42px 10px 10px 145px;
|
189
|
+
position: relative;
|
190
|
+
}
|
191
|
+
#main__section > .content > .section > .content .private_messageInstance {padding: 10px;}
|
192
|
+
.post__titleColContainer {
|
193
|
+
background: #398235;
|
194
|
+
border-bottom: 1px solid #398235;
|
195
|
+
color: #fff;
|
196
|
+
height: 32px;
|
197
|
+
left: 0;
|
198
|
+
line-height: 32px;
|
199
|
+
margin: 0;
|
200
|
+
overflow: hidden;
|
201
|
+
position: absolute;
|
202
|
+
top: 0;
|
203
|
+
width: 100%;
|
204
|
+
}
|
205
|
+
.colContainer label {
|
206
|
+
font-size: 75%;
|
207
|
+
padding: 0 5px 0 0;
|
208
|
+
width: auto;
|
209
|
+
}
|
210
|
+
.colContainer div {
|
211
|
+
display: inline-block;
|
212
|
+
font-size: 75%;
|
213
|
+
}
|
214
|
+
#main__section .content > .instanceContainer > .instance > .post__titleColContainer {
|
215
|
+
-moz-border-radius-topright: 8px;
|
216
|
+
-moz-border-radius-topleft: 8px;
|
217
|
+
border-top-right-radius: 8px;
|
218
|
+
border-top-left-radius: 8px;
|
219
|
+
}
|
220
|
+
.instance .post__titleColContainer label {
|
221
|
+
color: #fff;
|
222
|
+
font: bold 85%/32px sans-serif;
|
223
|
+
padding: 0 10px;
|
224
|
+
}
|
225
|
+
.instance .post__titleColContainer div {font-size: 93%;}
|
226
|
+
.post__idColContainer,
|
227
|
+
.post__author_idColContainer,
|
228
|
+
.post__thread_idColContainer,
|
229
|
+
.post__date_createdColContainer,
|
230
|
+
.post__date_updatedColContainer {
|
231
|
+
clear: left;
|
232
|
+
float: left;
|
233
|
+
margin: 0 0 0.25em -135px;
|
234
|
+
width: 125px;
|
235
|
+
}
|
236
|
+
.post__messageColContainer {font-size: 125%;}
|
237
|
+
.post__messageColContainer label {display: none;}
|
238
|
+
.post__messageColContainer p {margin: 0 0 1.5em;}
|
239
|
+
.post__tagsColContainer {margin: 0 0 0.75em;}
|
240
|
+
.post__tagsColContainer .col {
|
241
|
+
overflow: hidden;
|
242
|
+
vertical-align: middle;
|
243
|
+
}
|
244
|
+
.post__tagsColContainer input[type=text] {width: 120px;}
|
245
|
+
.post__tagsColContainer input[type=submit] {
|
246
|
+
float: right;
|
247
|
+
margin: 0 0 0 10px;
|
248
|
+
padding: 0 5px;
|
249
|
+
}
|
250
|
+
.instanceActions {overflow: hidden;}
|
251
|
+
.instanceActions a,
|
252
|
+
.instance input[type=submit] {
|
253
|
+
float: right;
|
254
|
+
margin: 0;
|
255
|
+
width: auto;
|
256
|
+
}
|
257
|
+
.editing input[type=text] {
|
258
|
+
background: #DCFCBD;
|
259
|
+
padding: 1px 5px;
|
260
|
+
}
|
261
|
+
.editing input[type=submit] {
|
262
|
+
float: none;
|
263
|
+
margin: 0 0 0 5px;
|
264
|
+
}
|
265
|
+
#main__section > .content > .section > .content .private_messageInstance label {width: 100px;}
|
266
|
+
.errorCol {background: #febebe;}
|
267
|
+
.errorMsg {
|
268
|
+
color: #EA8989;
|
269
|
+
font: bold italic 77% sans-serif;
|
270
|
+
}
|