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.
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
+ }