marley 0.1.0
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/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
|
+
}
|