punchcard 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+ .bundle
4
+ .sass-cache
5
+
6
+ ## TEXTMATE
7
+ *.tmproj
8
+ tmtags
9
+
10
+ ## EMACS
11
+ *~
12
+ \#*
13
+ .\#*
14
+
15
+ ## VIM
16
+ *.swp
17
+
18
+ ## PROJECT::GENERAL
19
+ coverage
20
+ rdoc
21
+ pkg
22
+
23
+ ## PROJECT::SPECIFIC
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+ gem 'sinatra-mongoid', :require => 'sinatra/mongoid'
data/Gemfile.lock ADDED
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ punchcard (0.0.0)
5
+ bson_ext
6
+ gravtastic
7
+ haml (>= 3.0.24)
8
+ mongoid
9
+ sass (>= 3.0.0)
10
+ sinatra (>= 1.0.0)
11
+ sinatra-mongoid
12
+
13
+ GEM
14
+ remote: http://rubygems.org/
15
+ specs:
16
+ activesupport (2.3.10)
17
+ bson (1.0.9)
18
+ bson_ext (1.1.4)
19
+ durran-validatable (2.0.1)
20
+ gravtastic (3.1.0)
21
+ haml (3.0.24)
22
+ mongo (1.0.9)
23
+ bson (>= 1.0.5)
24
+ mongo_ext (0.19.3)
25
+ mongoid (1.9.2)
26
+ activesupport (< 3.0.0)
27
+ bson (~> 1.0.1)
28
+ durran-validatable (>= 2.0.1)
29
+ mongo (~> 1.0.1)
30
+ will_paginate (< 2.9)
31
+ rack (1.2.1)
32
+ rack-test (0.5.6)
33
+ rack (>= 1.0)
34
+ sass (3.1.0.alpha.200)
35
+ shoulda (2.10.3)
36
+ sinatra (1.1.0)
37
+ rack (~> 1.1)
38
+ tilt (~> 1.1)
39
+ sinatra-mongoid (0.0.2)
40
+ mongo_ext
41
+ mongoid
42
+ sinatra
43
+ tilt (1.1)
44
+ will_paginate (2.3.15)
45
+
46
+ PLATFORMS
47
+ ruby
48
+
49
+ DEPENDENCIES
50
+ bson_ext
51
+ gravtastic
52
+ haml (>= 3.0.24)
53
+ mongoid
54
+ punchcard!
55
+ rack-test (>= 0.5.6)
56
+ sass (>= 3.0.0)
57
+ shoulda (= 2.10.3)
58
+ sinatra (>= 1.0.0)
59
+ sinatra-mongoid
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Christoph Olszowka
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,62 @@
1
+ = Punchcard
2
+
3
+ A simple Sinatra app that lets you track who is and has been in the office at what times.
4
+
5
+ == Setup (theory)
6
+
7
+ Create a folder for your app, add a Gemfile:
8
+
9
+ source :rubygems
10
+ gem 'punchcard'
11
+
12
+ Run bundle install. You'll also need a config.ru that looks like this:
13
+
14
+ require 'bundler'
15
+ Bundler.require
16
+ Punchcard.set :mongo_db, 'punchcard'
17
+ run Punchcard
18
+
19
+ Now running rackup should launch punchcard.
20
+
21
+ == Setup (for real on Heroku)
22
+
23
+ Same as above, but you can ditch the mongo_db setup line. Initialize a git repo in your folder
24
+
25
+ $ git init
26
+ $ git commit -a -m "Initial commit"
27
+ $ heroku create --stack bamboo-mri-1.9.2
28
+ $ heroku addons:add mongohq:free
29
+ $ git push heroku
30
+
31
+ Now head over to your app's url! Please also check out the next two sections for auth and adding people.
32
+
33
+ == Authentication
34
+
35
+ You'll surely want to secure your punch card from general web access. The simplest way to do so is by
36
+ using the rack auth middleware by defining it somewhere in your config.ru:
37
+
38
+ Punchcard.use Rack::Auth::Basic do |username, password|
39
+ [username, password] == ['admin', 'admin']
40
+ end
41
+
42
+ == Adding people
43
+
44
+ Currently, you'll have to add users directly from the console. After creating your app on heroku,
45
+ you can do so by running
46
+
47
+ $ heroku console
48
+ > Person.create!(:name => 'Forrest Gump', :email => 'forrest@shrimpco.org')
49
+
50
+ == Note on Patches/Pull Requests
51
+
52
+ * Fork the project.
53
+ * Make your feature addition or bug fix.
54
+ * Add tests for it. This is important so I don't break it in a
55
+ future version unintentionally.
56
+ * Commit, do not mess with rakefile, version, or history.
57
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
58
+ * Send me a pull request. Bonus points for topic branches.
59
+
60
+ == Copyright
61
+
62
+ Copyright (c) 2010 Christoph Olszowka. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,61 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "punchcard"
8
+ gem.summary = %Q{Simple sinatra/mongodb app for tracking time when people have been in the office}
9
+ gem.description = %Q{Simple sinatra/mongodb app for tracking time when people have been in the office}
10
+ gem.email = "christoph at olszowka de"
11
+ gem.homepage = "http://github.com/colszowka/punchcard"
12
+ gem.authors = ["Christoph Olszowka"]
13
+ gem.add_dependency 'sinatra', ">= 1.0.0"
14
+ gem.add_dependency 'sinatra-mongoid', '>= 0'
15
+ gem.add_dependency 'bson_ext', '>= 0'
16
+ gem.add_dependency 'haml', '>= 3.0.24'
17
+ gem.add_dependency 'sass', '>= 3.0.0'
18
+ gem.add_dependency 'mongoid', '>= 0'
19
+ gem.add_dependency 'gravtastic', '>= 0'
20
+ gem.add_development_dependency "shoulda", "2.10.3"
21
+ gem.add_development_dependency "rack-test", ">= 0.5.6"
22
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
23
+ end
24
+ Jeweler::GemcutterTasks.new
25
+ rescue LoadError
26
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
27
+ end
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ begin
37
+ require 'rcov/rcovtask'
38
+ Rcov::RcovTask.new do |test|
39
+ test.libs << 'test'
40
+ test.pattern = 'test/**/test_*.rb'
41
+ test.verbose = true
42
+ end
43
+ rescue LoadError
44
+ task :rcov do
45
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
46
+ end
47
+ end
48
+
49
+ task :test => :check_dependencies
50
+
51
+ task :default => :test
52
+
53
+ require 'rake/rdoctask'
54
+ Rake::RDocTask.new do |rdoc|
55
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
56
+
57
+ rdoc.rdoc_dir = 'rdoc'
58
+ rdoc.title = "punchcard #{version}"
59
+ rdoc.rdoc_files.include('README*')
60
+ rdoc.rdoc_files.include('lib/**/*.rb')
61
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/config.ru ADDED
@@ -0,0 +1,11 @@
1
+ # Example config.ru for running from direct git clone
2
+ # See README to see how to launch from gem
3
+ require './lib/punchcard'
4
+
5
+ Punchcard.set :mongo_db, 'punchcard'
6
+
7
+ Punchcard.use Rack::Auth::Basic do |username, password|
8
+ [username, password] == ['admin', 'admin']
9
+ end
10
+
11
+ run Punchcard
Binary file
@@ -0,0 +1,265 @@
1
+ /* -----------------------------------------------------------------------
2
+
3
+
4
+ Blueprint CSS Framework 1.0
5
+ http://blueprintcss.org
6
+
7
+ * Copyright (c) 2007-Present. See LICENSE for more info.
8
+ * See README for instructions on how to use Blueprint.
9
+ * For credits and origins, see AUTHORS.
10
+ * This is a compressed file. See the sources in the 'src' directory.
11
+
12
+ ----------------------------------------------------------------------- */
13
+
14
+ /* reset.css */
15
+ html {margin:0;padding:0;border:0;}
16
+ body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, nav, section {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;}
17
+ article, aside, dialog, figure, footer, header, hgroup, nav, section {display:block;}
18
+ body {line-height:1.5;background:white;}
19
+ table {border-collapse:separate;border-spacing:0;}
20
+ caption, th, td {text-align:left;font-weight:normal;float:none !important;}
21
+ table, th, td {vertical-align:middle;}
22
+ blockquote:before, blockquote:after, q:before, q:after {content:'';}
23
+ blockquote, q {quotes:"" "";}
24
+ a img {border:none;}
25
+ :focus {outline:0;}
26
+
27
+ /* typography.css */
28
+ html {font-size:100.01%;}
29
+ body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;}
30
+ h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;}
31
+ h1 {font-size:3em;line-height:1;margin-bottom:0.5em;}
32
+ h2 {font-size:2em;margin-bottom:0.75em;}
33
+ h3 {font-size:1.5em;line-height:1;margin-bottom:1em;}
34
+ h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;}
35
+ h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;}
36
+ h6 {font-size:1em;font-weight:bold;}
37
+ h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
38
+ p {margin:0 0 1.5em;}
39
+ .left {float:left !important;}
40
+ p .left {margin:1.5em 1.5em 1.5em 0;padding:0;}
41
+ .right {float:right !important;}
42
+ p .right {margin:1.5em 0 1.5em 1.5em;padding:0;}
43
+ a:focus, a:hover {color:#09f;}
44
+ a {color:#06c;text-decoration:underline;}
45
+ blockquote {margin:1.5em;color:#666;font-style:italic;}
46
+ strong, dfn {font-weight:bold;}
47
+ em, dfn {font-style:italic;}
48
+ sup, sub {line-height:0;}
49
+ abbr, acronym {border-bottom:1px dotted #666;}
50
+ address {margin:0 0 1.5em;font-style:italic;}
51
+ del {color:#666;}
52
+ pre {margin:1.5em 0;white-space:pre;}
53
+ pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
54
+ li ul, li ol {margin:0;}
55
+ ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;}
56
+ ul {list-style-type:disc;}
57
+ ol {list-style-type:decimal;}
58
+ dl {margin:0 0 1.5em 0;}
59
+ dl dt {font-weight:bold;}
60
+ dd {margin-left:1.5em;}
61
+ table {margin-bottom:1.4em;width:100%;}
62
+ th {font-weight:bold;}
63
+ thead th {background:#c3d9ff;}
64
+ th, td, caption {padding:4px 10px 4px 5px;}
65
+ tbody tr:nth-child(even) td, tbody tr.even td {background:#e5ecf9;}
66
+ tfoot {font-style:italic;}
67
+ caption {background:#eee;}
68
+ .small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;}
69
+ .large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;}
70
+ .hide {display:none;}
71
+ .quiet {color:#666;}
72
+ .loud {color:#000;}
73
+ .highlight {background:#ff0;}
74
+ .added {background:#060;color:#fff;}
75
+ .removed {background:#900;color:#fff;}
76
+ .first {margin-left:0;padding-left:0;}
77
+ .last {margin-right:0;padding-right:0;}
78
+ .top {margin-top:0;padding-top:0;}
79
+ .bottom {margin-bottom:0;padding-bottom:0;}
80
+
81
+ /* forms.css */
82
+ label {font-weight:bold;}
83
+ fieldset {padding:0 1.4em 1.4em 1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;}
84
+ legend {font-weight:bold;font-size:1.2em;margin-top:-0.2em;margin-bottom:1em;}
85
+ fieldset, #IE8#HACK {padding-top:1.4em;}
86
+ legend, #IE8#HACK {margin-top:0;margin-bottom:0;}
87
+ input[type=text], input[type=password], input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;}
88
+ input[type=text]:focus, input[type=password]:focus, input.text:focus, input.title:focus, textarea:focus {border-color:#666;}
89
+ select {background-color:#fff;border-width:1px;border-style:solid;}
90
+ input[type=text], input[type=password], input.text, input.title, textarea, select {margin:0.5em 0;}
91
+ input.text, input.title {width:300px;padding:5px;}
92
+ input.title {font-size:1.5em;}
93
+ textarea {width:390px;height:250px;padding:5px;}
94
+ form.inline {line-height:3;}
95
+ form.inline p {margin-bottom:0;}
96
+ .error, .alert, .notice, .success, .info {padding:0.8em;margin-bottom:1em;border:2px solid #ddd;}
97
+ .error, .alert {background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4;}
98
+ .notice {background:#fff6bf;color:#514721;border-color:#ffd324;}
99
+ .success {background:#e6efc2;color:#264409;border-color:#c6d880;}
100
+ .info {background:#d5edf8;color:#205791;border-color:#92cae4;}
101
+ .error a, .alert a {color:#8a1f11;}
102
+ .notice a {color:#514721;}
103
+ .success a {color:#264409;}
104
+ .info a {color:#205791;}
105
+
106
+ /* grid.css */
107
+ .container {width:950px;margin:0 auto;}
108
+ .showgrid {background:url(src/grid.png);}
109
+ .column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {float:left;margin-right:10px;}
110
+ .last {margin-right:0;}
111
+ .span-1 {width:30px;}
112
+ .span-2 {width:70px;}
113
+ .span-3 {width:110px;}
114
+ .span-4 {width:150px;}
115
+ .span-5 {width:190px;}
116
+ .span-6 {width:230px;}
117
+ .span-7 {width:270px;}
118
+ .span-8 {width:310px;}
119
+ .span-9 {width:350px;}
120
+ .span-10 {width:390px;}
121
+ .span-11 {width:430px;}
122
+ .span-12 {width:470px;}
123
+ .span-13 {width:510px;}
124
+ .span-14 {width:550px;}
125
+ .span-15 {width:590px;}
126
+ .span-16 {width:630px;}
127
+ .span-17 {width:670px;}
128
+ .span-18 {width:710px;}
129
+ .span-19 {width:750px;}
130
+ .span-20 {width:790px;}
131
+ .span-21 {width:830px;}
132
+ .span-22 {width:870px;}
133
+ .span-23 {width:910px;}
134
+ .span-24 {width:950px;margin-right:0;}
135
+ input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px;border-right-width:1px;padding-left:5px;padding-right:5px;}
136
+ input.span-1, textarea.span-1 {width:18px;}
137
+ input.span-2, textarea.span-2 {width:58px;}
138
+ input.span-3, textarea.span-3 {width:98px;}
139
+ input.span-4, textarea.span-4 {width:138px;}
140
+ input.span-5, textarea.span-5 {width:178px;}
141
+ input.span-6, textarea.span-6 {width:218px;}
142
+ input.span-7, textarea.span-7 {width:258px;}
143
+ input.span-8, textarea.span-8 {width:298px;}
144
+ input.span-9, textarea.span-9 {width:338px;}
145
+ input.span-10, textarea.span-10 {width:378px;}
146
+ input.span-11, textarea.span-11 {width:418px;}
147
+ input.span-12, textarea.span-12 {width:458px;}
148
+ input.span-13, textarea.span-13 {width:498px;}
149
+ input.span-14, textarea.span-14 {width:538px;}
150
+ input.span-15, textarea.span-15 {width:578px;}
151
+ input.span-16, textarea.span-16 {width:618px;}
152
+ input.span-17, textarea.span-17 {width:658px;}
153
+ input.span-18, textarea.span-18 {width:698px;}
154
+ input.span-19, textarea.span-19 {width:738px;}
155
+ input.span-20, textarea.span-20 {width:778px;}
156
+ input.span-21, textarea.span-21 {width:818px;}
157
+ input.span-22, textarea.span-22 {width:858px;}
158
+ input.span-23, textarea.span-23 {width:898px;}
159
+ input.span-24, textarea.span-24 {width:938px;}
160
+ .append-1 {padding-right:40px;}
161
+ .append-2 {padding-right:80px;}
162
+ .append-3 {padding-right:120px;}
163
+ .append-4 {padding-right:160px;}
164
+ .append-5 {padding-right:200px;}
165
+ .append-6 {padding-right:240px;}
166
+ .append-7 {padding-right:280px;}
167
+ .append-8 {padding-right:320px;}
168
+ .append-9 {padding-right:360px;}
169
+ .append-10 {padding-right:400px;}
170
+ .append-11 {padding-right:440px;}
171
+ .append-12 {padding-right:480px;}
172
+ .append-13 {padding-right:520px;}
173
+ .append-14 {padding-right:560px;}
174
+ .append-15 {padding-right:600px;}
175
+ .append-16 {padding-right:640px;}
176
+ .append-17 {padding-right:680px;}
177
+ .append-18 {padding-right:720px;}
178
+ .append-19 {padding-right:760px;}
179
+ .append-20 {padding-right:800px;}
180
+ .append-21 {padding-right:840px;}
181
+ .append-22 {padding-right:880px;}
182
+ .append-23 {padding-right:920px;}
183
+ .prepend-1 {padding-left:40px;}
184
+ .prepend-2 {padding-left:80px;}
185
+ .prepend-3 {padding-left:120px;}
186
+ .prepend-4 {padding-left:160px;}
187
+ .prepend-5 {padding-left:200px;}
188
+ .prepend-6 {padding-left:240px;}
189
+ .prepend-7 {padding-left:280px;}
190
+ .prepend-8 {padding-left:320px;}
191
+ .prepend-9 {padding-left:360px;}
192
+ .prepend-10 {padding-left:400px;}
193
+ .prepend-11 {padding-left:440px;}
194
+ .prepend-12 {padding-left:480px;}
195
+ .prepend-13 {padding-left:520px;}
196
+ .prepend-14 {padding-left:560px;}
197
+ .prepend-15 {padding-left:600px;}
198
+ .prepend-16 {padding-left:640px;}
199
+ .prepend-17 {padding-left:680px;}
200
+ .prepend-18 {padding-left:720px;}
201
+ .prepend-19 {padding-left:760px;}
202
+ .prepend-20 {padding-left:800px;}
203
+ .prepend-21 {padding-left:840px;}
204
+ .prepend-22 {padding-left:880px;}
205
+ .prepend-23 {padding-left:920px;}
206
+ .border {padding-right:4px;margin-right:5px;border-right:1px solid #ddd;}
207
+ .colborder {padding-right:24px;margin-right:25px;border-right:1px solid #ddd;}
208
+ .pull-1 {margin-left:-40px;}
209
+ .pull-2 {margin-left:-80px;}
210
+ .pull-3 {margin-left:-120px;}
211
+ .pull-4 {margin-left:-160px;}
212
+ .pull-5 {margin-left:-200px;}
213
+ .pull-6 {margin-left:-240px;}
214
+ .pull-7 {margin-left:-280px;}
215
+ .pull-8 {margin-left:-320px;}
216
+ .pull-9 {margin-left:-360px;}
217
+ .pull-10 {margin-left:-400px;}
218
+ .pull-11 {margin-left:-440px;}
219
+ .pull-12 {margin-left:-480px;}
220
+ .pull-13 {margin-left:-520px;}
221
+ .pull-14 {margin-left:-560px;}
222
+ .pull-15 {margin-left:-600px;}
223
+ .pull-16 {margin-left:-640px;}
224
+ .pull-17 {margin-left:-680px;}
225
+ .pull-18 {margin-left:-720px;}
226
+ .pull-19 {margin-left:-760px;}
227
+ .pull-20 {margin-left:-800px;}
228
+ .pull-21 {margin-left:-840px;}
229
+ .pull-22 {margin-left:-880px;}
230
+ .pull-23 {margin-left:-920px;}
231
+ .pull-24 {margin-left:-960px;}
232
+ .pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;}
233
+ .push-1 {margin:0 -40px 1.5em 40px;}
234
+ .push-2 {margin:0 -80px 1.5em 80px;}
235
+ .push-3 {margin:0 -120px 1.5em 120px;}
236
+ .push-4 {margin:0 -160px 1.5em 160px;}
237
+ .push-5 {margin:0 -200px 1.5em 200px;}
238
+ .push-6 {margin:0 -240px 1.5em 240px;}
239
+ .push-7 {margin:0 -280px 1.5em 280px;}
240
+ .push-8 {margin:0 -320px 1.5em 320px;}
241
+ .push-9 {margin:0 -360px 1.5em 360px;}
242
+ .push-10 {margin:0 -400px 1.5em 400px;}
243
+ .push-11 {margin:0 -440px 1.5em 440px;}
244
+ .push-12 {margin:0 -480px 1.5em 480px;}
245
+ .push-13 {margin:0 -520px 1.5em 520px;}
246
+ .push-14 {margin:0 -560px 1.5em 560px;}
247
+ .push-15 {margin:0 -600px 1.5em 600px;}
248
+ .push-16 {margin:0 -640px 1.5em 640px;}
249
+ .push-17 {margin:0 -680px 1.5em 680px;}
250
+ .push-18 {margin:0 -720px 1.5em 720px;}
251
+ .push-19 {margin:0 -760px 1.5em 760px;}
252
+ .push-20 {margin:0 -800px 1.5em 800px;}
253
+ .push-21 {margin:0 -840px 1.5em 840px;}
254
+ .push-22 {margin:0 -880px 1.5em 880px;}
255
+ .push-23 {margin:0 -920px 1.5em 920px;}
256
+ .push-24 {margin:0 -960px 1.5em 960px;}
257
+ .push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:left;position:relative;}
258
+ div.prepend-top, .prepend-top {margin-top:1.5em;}
259
+ div.append-bottom, .append-bottom {margin-bottom:1.5em;}
260
+ .box {padding:1.5em;margin-bottom:1.5em;background:#e5eCf9;}
261
+ hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin:0 0 1.45em;border:none;}
262
+ hr.space {background:#fff;color:#fff;visibility:hidden;}
263
+ .clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
264
+ .clearfix, .container {display:block;}
265
+ .clear {clear:both;}
@@ -0,0 +1,35 @@
1
+ // Update status for all people
2
+ function update_status() {
3
+ $.getJSON('/status.json', function(json) {
4
+ $.each(json, function(index, person) {
5
+ update_person(person);
6
+ });
7
+ });
8
+ };
9
+
10
+ // Render the state of the person from given json
11
+ function update_person(json) {
12
+ var rendered_template = $.tmpl('personTemplate', json);
13
+
14
+ if ($('#people #' + json._id).size() == 0) {
15
+ rendered_template.appendTo('#people');
16
+ } else {
17
+ $('#people #' + json._id).replaceWith(rendered_template);
18
+ };
19
+ };
20
+
21
+ $(document).ready(function() {
22
+ $( "#personTemplate" ).template( "personTemplate" );
23
+
24
+ $('#people li').live('dblclick', function() {
25
+ user_id = $(this).attr('id');
26
+ $.post('/punch/' + user_id, function(data) {
27
+ //$('.result').html(data);
28
+ var json = jQuery.parseJSON(data);
29
+ update_person(json);
30
+ });
31
+ });
32
+
33
+ update_status();
34
+ var timer = setInterval( update_status, 5000);
35
+ });
data/lib/punchcard.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'bundler'
2
+ Bundler.require(:default)
3
+
4
+ require 'punchcard/person'
5
+ require 'punchcard/punch'
6
+ require 'punchcard/mongohq'
7
+
8
+ class Punchcard < Sinatra::Base
9
+ set :views, File.join(File.dirname(__FILE__), 'views')
10
+ set :public, File.join(File.dirname(__FILE__), 'public')
11
+
12
+ get '/' do
13
+ haml :index
14
+ end
15
+
16
+ get '/status.json' do
17
+ Person.order_by(:name).all.to_json
18
+ end
19
+
20
+ post '/punch/:id' do
21
+ @person = Person.find(params[:id])
22
+ @person.punch!
23
+ @person.to_json
24
+ end
25
+
26
+ get '/css/screen.css' do
27
+ sass :screen
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ # Automagic Mongo db autosetup for heroku & mongohq
2
+ if ENV['MONGOHQ_URL']
3
+ require 'uri'
4
+ mongo_uri = URI.parse(ENV['MONGOHQ_URL'])
5
+ ENV['MONGOID_HOST'] = mongo_uri.host
6
+ ENV['MONGOID_PORT'] = mongo_uri.port.to_s
7
+ ENV['MONGOID_USERNAME'] = mongo_uri.user
8
+ ENV['MONGOID_PASSWORD'] = mongo_uri.password
9
+ ENV['MONGOID_DATABASE'] = mongo_uri.path.gsub("/", "")
10
+ end
@@ -0,0 +1,50 @@
1
+ class Person
2
+ include Mongoid::Document
3
+
4
+ field :name
5
+ field :email
6
+
7
+ index :name, :unique => true
8
+
9
+ embeds_many :punches
10
+
11
+ validates_presence_of :name, :email
12
+
13
+ include Gravtastic
14
+ has_gravatar
15
+
16
+ # Returns the currently pending punch when present, nil otherwise
17
+ def pending?
18
+ punches.pending.first
19
+ end
20
+
21
+ # Punches in when no punches pending, punches out when a pending punch exists!
22
+ # When punching in, checks whether a recently finished punch exists and reopens if so instead
23
+ # of creating a new punch
24
+ def punch!
25
+ if punch = punches.pending.first
26
+ punch.punch_out!
27
+ else
28
+ if recently_finished = punches.recently_finished.first
29
+ recently_finished.reopen!
30
+ else
31
+ punches.create!
32
+ end
33
+ end
34
+ end
35
+
36
+ def payload
37
+ {
38
+ :_id => id,
39
+ :name => name,
40
+ :email => email,
41
+ :gravatar_url => gravatar_url(:size => 80),
42
+ :pending => !!pending?,
43
+ :checked_in_at => pending?.try(:checked_in_at)
44
+ }
45
+ end
46
+
47
+ def to_json(*args)
48
+ payload.to_json
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ class Punch
2
+ include Mongoid::Document
3
+ field :checked_in_at, :type => Time
4
+ field :checked_out_at, :type => Time
5
+
6
+ embedded_in :person, :inverse_of => :punches
7
+
8
+ scope :pending, where(:checked_out_at.exists => false)
9
+ scope :finished, where(:checked_out_at.exists => true, :checked_in_at.exists => true)
10
+ scope :recently_finished, where(:checked_out_at.gt => 30.minutes.ago)
11
+
12
+ validates_presence_of :checked_in_at
13
+
14
+ before_validation do |p|
15
+ p.checked_in_at ||= Time.now
16
+ end
17
+
18
+ validate do |p|
19
+ p.errors.add :person, "Person already has a pending punch!" if p.person.pending? and p.person.pending? != p
20
+ end
21
+
22
+ # Punches this punch out when pending
23
+ def punch_out!
24
+ return false if checked_out_at.present?
25
+ # Make sure no one stays the night...
26
+ if self.checked_in_at.to_date < Date.today
27
+ self.checked_out_at = self.checked_in_at.end_of_day
28
+ else
29
+ self.checked_out_at = Time.now
30
+ end
31
+ save!
32
+ self
33
+ end
34
+
35
+ def reopen!
36
+ self.checked_out_at = nil
37
+ save!
38
+ self
39
+ end
40
+ end
@@ -0,0 +1 @@
1
+ %ul#people
@@ -0,0 +1,28 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title Punchcard
5
+ %script{:src => "http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"}
6
+ %script{:src => 'http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js'}
7
+ %script{:src => "/js/app.js"}
8
+ :plain
9
+ <link rel="stylesheet" href="/css/blueprint.css" type="text/css" media="screen" />
10
+ <link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen" />
11
+
12
+ :plain
13
+ <script id="personTemplate" type="text/x-jquery-tmpl">
14
+ <li id="${_id}">
15
+ <img src="${gravatar_url}" title="${name}" class="gravatar {{if pending == true}}pending{{/if}} ${pending}"/>
16
+ <div class="person">${name}</div>
17
+ <div class="checked_in_at">{{if checked_in_at}}Checked in at ${checked_in_at}{{/if}}&nbsp;</div>
18
+ </li>
19
+ </script>
20
+
21
+ %body
22
+ #header
23
+
24
+ #main
25
+ = yield
26
+ .clear
27
+
28
+ #footer
@@ -0,0 +1,53 @@
1
+ body
2
+ font-family: "Lucida Grande", Helvetica, Arial, Verdana, sans-serif
3
+ background: #efefef
4
+
5
+ $green: #090
6
+ $yellow: #fc0
7
+
8
+ #main
9
+ width: 800px
10
+ margin-top: 30px
11
+ padding: 30px 20px
12
+ margin-left: auto
13
+ margin-right: auto
14
+ background: #eadab6 url('/cardbg.png') repeat-x
15
+ -moz-box-shadow: 3px 3px 4px #d2bd8e
16
+
17
+ .clear
18
+ clear: both
19
+
20
+ #people
21
+ list-style: none
22
+ margin: 0
23
+ padding: 0
24
+
25
+ li
26
+ clear: both
27
+ cursor: pointer
28
+ margin-bottom: 20px
29
+ font-weight: bold
30
+ font-size: 50px
31
+
32
+ &:last-child
33
+ margin-bottom: 0
34
+ .gravatar
35
+ margin-bottom: 0
36
+
37
+ .checked_in_at
38
+ color: #888
39
+ font-size: 16px
40
+ position: relative
41
+ top: -10px
42
+
43
+ .gravatar
44
+ float: left
45
+ width: 80px
46
+ margin-bottom: 20px
47
+ margin-right: 25px
48
+ border: 3px solid $yellow
49
+ -moz-box-shadow: 0px 0px 10px $yellow
50
+ &.pending
51
+ border-color: $green
52
+ -moz-box-shadow: 0px 0px 15px $green
53
+
data/punchcard.gemspec ADDED
@@ -0,0 +1,92 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{punchcard}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Christoph Olszowka"]
12
+ s.date = %q{2010-12-08}
13
+ s.description = %q{Simple sinatra/mongodb app for tracking time when people have been in the office}
14
+ s.email = %q{christoph at olszowka de}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "config.ru",
29
+ "lib/public/cardbg.png",
30
+ "lib/public/css/blueprint.css",
31
+ "lib/public/js/app.js",
32
+ "lib/punchcard.rb",
33
+ "lib/punchcard/mongohq.rb",
34
+ "lib/punchcard/person.rb",
35
+ "lib/punchcard/punch.rb",
36
+ "lib/views/index.haml",
37
+ "lib/views/layout.haml",
38
+ "lib/views/screen.sass",
39
+ "punchcard.gemspec",
40
+ "test/helper.rb",
41
+ "test/test_database.rb",
42
+ "test/test_punchcard.rb"
43
+ ]
44
+ s.homepage = %q{http://github.com/colszowka/punchcard}
45
+ s.rdoc_options = ["--charset=UTF-8"]
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.3.7}
48
+ s.summary = %q{Simple sinatra/mongodb app for tracking time when people have been in the office}
49
+ s.test_files = [
50
+ "test/helper.rb",
51
+ "test/test_database.rb",
52
+ "test/test_punchcard.rb"
53
+ ]
54
+
55
+ if s.respond_to? :specification_version then
56
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
60
+ s.add_runtime_dependency(%q<sinatra>, [">= 1.0.0"])
61
+ s.add_runtime_dependency(%q<sinatra-mongoid>, [">= 0"])
62
+ s.add_runtime_dependency(%q<bson_ext>, [">= 0"])
63
+ s.add_runtime_dependency(%q<haml>, [">= 3.0.24"])
64
+ s.add_runtime_dependency(%q<sass>, [">= 3.0.0"])
65
+ s.add_runtime_dependency(%q<mongoid>, [">= 0"])
66
+ s.add_runtime_dependency(%q<gravtastic>, [">= 0"])
67
+ s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
68
+ s.add_development_dependency(%q<rack-test>, [">= 0.5.6"])
69
+ else
70
+ s.add_dependency(%q<sinatra>, [">= 1.0.0"])
71
+ s.add_dependency(%q<sinatra-mongoid>, [">= 0"])
72
+ s.add_dependency(%q<bson_ext>, [">= 0"])
73
+ s.add_dependency(%q<haml>, [">= 3.0.24"])
74
+ s.add_dependency(%q<sass>, [">= 3.0.0"])
75
+ s.add_dependency(%q<mongoid>, [">= 0"])
76
+ s.add_dependency(%q<gravtastic>, [">= 0"])
77
+ s.add_dependency(%q<shoulda>, ["= 2.10.3"])
78
+ s.add_dependency(%q<rack-test>, [">= 0.5.6"])
79
+ end
80
+ else
81
+ s.add_dependency(%q<sinatra>, [">= 1.0.0"])
82
+ s.add_dependency(%q<sinatra-mongoid>, [">= 0"])
83
+ s.add_dependency(%q<bson_ext>, [">= 0"])
84
+ s.add_dependency(%q<haml>, [">= 3.0.24"])
85
+ s.add_dependency(%q<sass>, [">= 3.0.0"])
86
+ s.add_dependency(%q<mongoid>, [">= 0"])
87
+ s.add_dependency(%q<gravtastic>, [">= 0"])
88
+ s.add_dependency(%q<shoulda>, ["= 2.10.3"])
89
+ s.add_dependency(%q<rack-test>, [">= 0.5.6"])
90
+ end
91
+ end
92
+
data/test/helper.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.require
4
+ require 'test/unit'
5
+ require 'shoulda'
6
+
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ require 'punchcard'
10
+
11
+ Punchcard.set :mongo_db, 'punchcard_test'
12
+
13
+ class Test::Unit::TestCase
14
+ def setup
15
+ Person.delete_all
16
+ end
17
+ end
@@ -0,0 +1,120 @@
1
+ require 'helper'
2
+
3
+ class TestDatabase < Test::Unit::TestCase
4
+ context "A vanilla person" do
5
+ setup { @person = Person.new }
6
+ should "not be valid" do
7
+ assert !@person.valid?
8
+ end
9
+ end
10
+
11
+ context "A newly created person" do
12
+ setup do
13
+ @person = Person.create(:name => 'Christoph Olszowka', :email => 'foo@example.com')
14
+ end
15
+
16
+ should "have no punches" do
17
+ assert_equal 0, @person.punches.count
18
+ end
19
+
20
+ should "not be pending" do
21
+ assert_nil @person.pending?
22
+ end
23
+
24
+ context "that gets punched" do
25
+ setup do
26
+ assert @person.punch!.instance_of?(Punch), "Should have been able to get punched and have returned a Punch"
27
+ end
28
+
29
+ should "have one pending punch" do
30
+ assert_equal Punch, @person.pending?.class
31
+ assert_equal 1, @person.punches.pending.length
32
+ assert_equal 0, @person.punches.finished.length
33
+ end
34
+
35
+ should "not allow to create another pending punch" do
36
+ assert_raise Mongoid::Errors::Validations do
37
+ @person.punches.create!
38
+ end
39
+ end
40
+
41
+ context "and gets punched again" do
42
+ setup do
43
+ assert @person.punch!.instance_of?(Punch), "Should have been able to get punched and have returned a Punch"
44
+ end
45
+
46
+ should "have finished the pending punch" do
47
+ assert @person.punches.first.checked_out_at > 5.seconds.ago
48
+ end
49
+
50
+ should "not be pending" do
51
+ assert_nil @person.pending?
52
+ end
53
+
54
+ should "have one punch and no pending punches" do
55
+ assert_equal 1, @person.punches.count
56
+ assert_equal 0, @person.punches.pending.length
57
+ assert_equal 1, @person.punches.finished.length
58
+ end
59
+
60
+ context "and gets punched yet again" do
61
+ setup do
62
+ assert @person.punch!.instance_of?(Punch), "Should have been able to get punched and have returned a Punch"
63
+ end
64
+
65
+ should "be pending" do
66
+ assert @person.pending?
67
+ end
68
+
69
+ should "have 1 punch by having reopened the old one" do
70
+ assert_equal 1, @person.punches.count
71
+ assert_equal 1, @person.punches.pending.length
72
+ assert_equal 0, @person.punches.finished.length
73
+ end
74
+ end
75
+
76
+ context "and gets punched yet again 2 hours later" do
77
+ setup do
78
+ # Fake the timestamp so reopening does not get triggered
79
+ punch = @person.punches.first
80
+ punch.checked_out_at = 2.hours.ago
81
+ punch.save!
82
+ assert @person.punch!.instance_of?(Punch), "Should have been able to get punched and have returned a Punch"
83
+ end
84
+
85
+ should "be pending" do
86
+ assert @person.pending?
87
+ end
88
+
89
+ should "have 2 punches and one pending punch" do
90
+ assert_equal 2, @person.punches.count
91
+ assert_equal 1, @person.punches.pending.length
92
+ assert_equal 1, @person.punches.finished.length
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ context "that punched in yesterday" do
99
+ setup do
100
+ @person.punch!
101
+ punch = @person.pending?
102
+ punch.checked_in_at = 1.day.ago
103
+ punch.save!
104
+ end
105
+
106
+ context "and punches out today" do
107
+ setup do
108
+ @person.punch!
109
+ end
110
+
111
+ should "result in the punch out getting moved to the end of yesterday" do
112
+ checkout_time = @person.punches.finished.first.checked_out_at
113
+ assert_equal 1.day.ago.end_of_day.to_date, checkout_time.to_date
114
+ assert_equal 23, checkout_time.hour
115
+ assert_equal 59, checkout_time.min
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,4 @@
1
+ require 'helper'
2
+
3
+ class TestPunchcard < Test::Unit::TestCase
4
+ end
metadata ADDED
@@ -0,0 +1,215 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: punchcard
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Christoph Olszowka
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-08 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: sinatra
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 0
31
+ - 0
32
+ version: 1.0.0
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: sinatra-mongoid
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: bson_ext
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ type: :runtime
60
+ version_requirements: *id003
61
+ - !ruby/object:Gem::Dependency
62
+ name: haml
63
+ prerelease: false
64
+ requirement: &id004 !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 3
71
+ - 0
72
+ - 24
73
+ version: 3.0.24
74
+ type: :runtime
75
+ version_requirements: *id004
76
+ - !ruby/object:Gem::Dependency
77
+ name: sass
78
+ prerelease: false
79
+ requirement: &id005 !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ segments:
85
+ - 3
86
+ - 0
87
+ - 0
88
+ version: 3.0.0
89
+ type: :runtime
90
+ version_requirements: *id005
91
+ - !ruby/object:Gem::Dependency
92
+ name: mongoid
93
+ prerelease: false
94
+ requirement: &id006 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ type: :runtime
103
+ version_requirements: *id006
104
+ - !ruby/object:Gem::Dependency
105
+ name: gravtastic
106
+ prerelease: false
107
+ requirement: &id007 !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ segments:
113
+ - 0
114
+ version: "0"
115
+ type: :runtime
116
+ version_requirements: *id007
117
+ - !ruby/object:Gem::Dependency
118
+ name: shoulda
119
+ prerelease: false
120
+ requirement: &id008 !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - "="
124
+ - !ruby/object:Gem::Version
125
+ segments:
126
+ - 2
127
+ - 10
128
+ - 3
129
+ version: 2.10.3
130
+ type: :development
131
+ version_requirements: *id008
132
+ - !ruby/object:Gem::Dependency
133
+ name: rack-test
134
+ prerelease: false
135
+ requirement: &id009 !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ segments:
141
+ - 0
142
+ - 5
143
+ - 6
144
+ version: 0.5.6
145
+ type: :development
146
+ version_requirements: *id009
147
+ description: Simple sinatra/mongodb app for tracking time when people have been in the office
148
+ email: christoph at olszowka de
149
+ executables: []
150
+
151
+ extensions: []
152
+
153
+ extra_rdoc_files:
154
+ - LICENSE
155
+ - README.rdoc
156
+ files:
157
+ - .document
158
+ - .gitignore
159
+ - Gemfile
160
+ - Gemfile.lock
161
+ - LICENSE
162
+ - README.rdoc
163
+ - Rakefile
164
+ - VERSION
165
+ - config.ru
166
+ - lib/public/cardbg.png
167
+ - lib/public/css/blueprint.css
168
+ - lib/public/js/app.js
169
+ - lib/punchcard.rb
170
+ - lib/punchcard/mongohq.rb
171
+ - lib/punchcard/person.rb
172
+ - lib/punchcard/punch.rb
173
+ - lib/views/index.haml
174
+ - lib/views/layout.haml
175
+ - lib/views/screen.sass
176
+ - punchcard.gemspec
177
+ - test/helper.rb
178
+ - test/test_database.rb
179
+ - test/test_punchcard.rb
180
+ has_rdoc: true
181
+ homepage: http://github.com/colszowka/punchcard
182
+ licenses: []
183
+
184
+ post_install_message:
185
+ rdoc_options:
186
+ - --charset=UTF-8
187
+ require_paths:
188
+ - lib
189
+ required_ruby_version: !ruby/object:Gem::Requirement
190
+ none: false
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ segments:
195
+ - 0
196
+ version: "0"
197
+ required_rubygems_version: !ruby/object:Gem::Requirement
198
+ none: false
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ segments:
203
+ - 0
204
+ version: "0"
205
+ requirements: []
206
+
207
+ rubyforge_project:
208
+ rubygems_version: 1.3.7
209
+ signing_key:
210
+ specification_version: 3
211
+ summary: Simple sinatra/mongodb app for tracking time when people have been in the office
212
+ test_files:
213
+ - test/helper.rb
214
+ - test/test_database.rb
215
+ - test/test_punchcard.rb