marley 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -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
@@ -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;}
@@ -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
+ }
@@ -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'
@@ -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
@@ -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
+ }