punchcard 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/.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