lewt 0.5.12
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.
- checksums.yaml +7 -0
- data/LICENSE.md +22 -0
- data/README.md +238 -0
- data/bin/lewt +10 -0
- data/lib/config/customers.yml +33 -0
- data/lib/config/enterprise.yml +54 -0
- data/lib/config/settings.yml +10 -0
- data/lib/config/templates/invoice.html.liquid +63 -0
- data/lib/config/templates/invoice.text.liquid +40 -0
- data/lib/config/templates/meta.html.liquid +0 -0
- data/lib/config/templates/meta.text.liquid +1 -0
- data/lib/config/templates/metastat.html.liquid +2 -0
- data/lib/config/templates/metastat.text.liquid +8 -0
- data/lib/config/templates/report.html.liquid +29 -0
- data/lib/config/templates/report.text.liquid +15 -0
- data/lib/config/templates/style.css +461 -0
- data/lib/extension.rb +158 -0
- data/lib/extensions/calendar-timekeeping/apple_extractor.rb +63 -0
- data/lib/extensions/calendar-timekeeping/calendar-timekeeping.rb +65 -0
- data/lib/extensions/calendar-timekeeping/extractor.rb +62 -0
- data/lib/extensions/calendar-timekeeping/gcal_extractor.rb +61 -0
- data/lib/extensions/calendar-timekeeping/ical_extractor.rb +52 -0
- data/lib/extensions/liquid-renderer.rb +106 -0
- data/lib/extensions/metastat/metamath.rb +108 -0
- data/lib/extensions/metastat/metastat.rb +161 -0
- data/lib/extensions/simple-expenses.rb +112 -0
- data/lib/extensions/simple-invoices.rb +93 -0
- data/lib/extensions/simple-milestones.rb +102 -0
- data/lib/extensions/simple-reports.rb +81 -0
- data/lib/extensions/store.rb +81 -0
- data/lib/lewt.rb +233 -0
- data/lib/lewt_book.rb +29 -0
- data/lib/lewt_ledger.rb +149 -0
- data/lib/lewtopts.rb +170 -0
- data/tests/LEWT Schedule.ics +614 -0
- data/tests/expenses.csv +1 -0
- data/tests/milestones.csv +1 -0
- data/tests/run_tests.rb +14 -0
- data/tests/tc_Billing.rb +29 -0
- data/tests/tc_CalExt.rb +44 -0
- data/tests/tc_Lewt.rb +37 -0
- data/tests/tc_LewtExtension.rb +31 -0
- data/tests/tc_LewtLedger.rb +38 -0
- data/tests/tc_LewtOpts.rb +26 -0
- metadata +158 -0
@@ -0,0 +1,461 @@
|
|
1
|
+
body {
|
2
|
+
font-family: Helvetica, arial, sans-serif;
|
3
|
+
font-size: 14px;
|
4
|
+
line-height: 1.6;
|
5
|
+
padding-top: 10px;
|
6
|
+
padding-bottom: 10px;
|
7
|
+
background-color: white;
|
8
|
+
padding: 30px;
|
9
|
+
color: #333;
|
10
|
+
}
|
11
|
+
|
12
|
+
body > *:first-child {
|
13
|
+
margin-top: 0 !important;
|
14
|
+
}
|
15
|
+
|
16
|
+
body > *:last-child {
|
17
|
+
margin-bottom: 0 !important;
|
18
|
+
}
|
19
|
+
|
20
|
+
a {
|
21
|
+
color: #4183C4;
|
22
|
+
text-decoration: none;
|
23
|
+
}
|
24
|
+
|
25
|
+
a.absent {
|
26
|
+
color: #cc0000;
|
27
|
+
}
|
28
|
+
|
29
|
+
a.anchor {
|
30
|
+
display: block;
|
31
|
+
padding-left: 30px;
|
32
|
+
margin-left: -30px;
|
33
|
+
cursor: pointer;
|
34
|
+
position: absolute;
|
35
|
+
top: 0;
|
36
|
+
left: 0;
|
37
|
+
bottom: 0;
|
38
|
+
}
|
39
|
+
|
40
|
+
h1, h2, h3, h4, h5, h6 {
|
41
|
+
margin: 20px 0 10px;
|
42
|
+
padding: 0;
|
43
|
+
font-weight: bold;
|
44
|
+
-webkit-font-smoothing: antialiased;
|
45
|
+
cursor: text;
|
46
|
+
position: relative;
|
47
|
+
}
|
48
|
+
|
49
|
+
h2:first-child, h1:first-child, h1:first-child + h2, h3:first-child, h4:first-child, h5:first-child, h6:first-child {
|
50
|
+
margin-top: 0;
|
51
|
+
padding-top: 0;
|
52
|
+
}
|
53
|
+
|
54
|
+
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
|
55
|
+
text-decoration: none;
|
56
|
+
}
|
57
|
+
|
58
|
+
h1 tt, h1 code {
|
59
|
+
font-size: inherit;
|
60
|
+
}
|
61
|
+
|
62
|
+
h2 tt, h2 code {
|
63
|
+
font-size: inherit;
|
64
|
+
}
|
65
|
+
|
66
|
+
h3 tt, h3 code {
|
67
|
+
font-size: inherit;
|
68
|
+
}
|
69
|
+
|
70
|
+
h4 tt, h4 code {
|
71
|
+
font-size: inherit;
|
72
|
+
}
|
73
|
+
|
74
|
+
h5 tt, h5 code {
|
75
|
+
font-size: inherit;
|
76
|
+
}
|
77
|
+
|
78
|
+
h6 tt, h6 code {
|
79
|
+
font-size: inherit;
|
80
|
+
}
|
81
|
+
|
82
|
+
h1 {
|
83
|
+
font-size: 28px;
|
84
|
+
color: black;
|
85
|
+
}
|
86
|
+
|
87
|
+
h2 {
|
88
|
+
font-size: 24px;
|
89
|
+
border-bottom: 1px solid #cccccc;
|
90
|
+
color: black;
|
91
|
+
}
|
92
|
+
|
93
|
+
h3 {
|
94
|
+
font-size: 18px;
|
95
|
+
}
|
96
|
+
|
97
|
+
h4 {
|
98
|
+
font-size: 16px;
|
99
|
+
}
|
100
|
+
|
101
|
+
h5 {
|
102
|
+
font-size: 14px;
|
103
|
+
}
|
104
|
+
|
105
|
+
h6 {
|
106
|
+
color: #777777;
|
107
|
+
font-size: 14px;
|
108
|
+
}
|
109
|
+
|
110
|
+
p, blockquote, ul, ol, dl, li, table, pre {
|
111
|
+
margin: 15px 0;
|
112
|
+
}
|
113
|
+
|
114
|
+
hr {
|
115
|
+
/* background: transparent url("http://tinyurl.com/bq5kskr") repeat-x 0 0; */
|
116
|
+
border: 0 none;
|
117
|
+
color: #cccccc;
|
118
|
+
height: 4px;
|
119
|
+
padding: 0;
|
120
|
+
}
|
121
|
+
|
122
|
+
body > h2:first-child {
|
123
|
+
margin-top: 0;
|
124
|
+
padding-top: 0;
|
125
|
+
}
|
126
|
+
|
127
|
+
body > h1:first-child {
|
128
|
+
margin-top: 0;
|
129
|
+
padding-top: 0;
|
130
|
+
}
|
131
|
+
|
132
|
+
body > h1:first-child + h2 {
|
133
|
+
margin-top: 0;
|
134
|
+
padding-top: 0;
|
135
|
+
}
|
136
|
+
|
137
|
+
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
|
138
|
+
margin-top: 0;
|
139
|
+
padding-top: 0;
|
140
|
+
}
|
141
|
+
|
142
|
+
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
|
143
|
+
margin-top: 0;
|
144
|
+
padding-top: 0;
|
145
|
+
}
|
146
|
+
|
147
|
+
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
|
148
|
+
margin-top: 0;
|
149
|
+
}
|
150
|
+
|
151
|
+
li p.first {
|
152
|
+
display: inline-block;
|
153
|
+
}
|
154
|
+
|
155
|
+
ul, ol {
|
156
|
+
padding-left: 30px;
|
157
|
+
}
|
158
|
+
|
159
|
+
ul :first-child, ol :first-child {
|
160
|
+
margin-top: 0;
|
161
|
+
}
|
162
|
+
|
163
|
+
ul :last-child, ol :last-child {
|
164
|
+
margin-bottom: 0;
|
165
|
+
}
|
166
|
+
|
167
|
+
dl {
|
168
|
+
padding: 0;
|
169
|
+
}
|
170
|
+
|
171
|
+
dl dt {
|
172
|
+
font-size: 14px;
|
173
|
+
font-weight: bold;
|
174
|
+
font-style: italic;
|
175
|
+
padding: 0;
|
176
|
+
margin: 15px 0 5px;
|
177
|
+
}
|
178
|
+
|
179
|
+
dl dt:first-child {
|
180
|
+
padding: 0;
|
181
|
+
}
|
182
|
+
|
183
|
+
dl dt > :first-child {
|
184
|
+
margin-top: 0;
|
185
|
+
}
|
186
|
+
|
187
|
+
dl dt > :last-child {
|
188
|
+
margin-bottom: 0;
|
189
|
+
}
|
190
|
+
|
191
|
+
dl dd {
|
192
|
+
margin: 0 0 15px;
|
193
|
+
padding: 0 15px;
|
194
|
+
}
|
195
|
+
|
196
|
+
dl dd > :first-child {
|
197
|
+
margin-top: 0;
|
198
|
+
}
|
199
|
+
|
200
|
+
dl dd > :last-child {
|
201
|
+
margin-bottom: 0;
|
202
|
+
}
|
203
|
+
|
204
|
+
blockquote {
|
205
|
+
border-left: 4px solid #dddddd;
|
206
|
+
padding: 0 15px;
|
207
|
+
color: #777777;
|
208
|
+
}
|
209
|
+
|
210
|
+
blockquote > :first-child {
|
211
|
+
margin-top: 0;
|
212
|
+
}
|
213
|
+
|
214
|
+
blockquote > :last-child {
|
215
|
+
margin-bottom: 0;
|
216
|
+
}
|
217
|
+
|
218
|
+
table {
|
219
|
+
padding: 0;
|
220
|
+
}
|
221
|
+
table tr {
|
222
|
+
border-top: 1px solid #cccccc;
|
223
|
+
background-color: white;
|
224
|
+
margin: 0;
|
225
|
+
padding: 0;
|
226
|
+
}
|
227
|
+
|
228
|
+
table tr:nth-child(2n) {
|
229
|
+
background-color: #f8f8f8;
|
230
|
+
}
|
231
|
+
|
232
|
+
table tr th {
|
233
|
+
font-weight: bold;
|
234
|
+
border: 1px solid #cccccc;
|
235
|
+
text-align: left;
|
236
|
+
margin: 0;
|
237
|
+
padding: 6px 13px;
|
238
|
+
}
|
239
|
+
|
240
|
+
table tr td {
|
241
|
+
border: 1px solid #cccccc;
|
242
|
+
text-align: left;
|
243
|
+
margin: 0;
|
244
|
+
padding: 6px 13px;
|
245
|
+
}
|
246
|
+
|
247
|
+
table tr th :first-child, table tr td :first-child {
|
248
|
+
margin-top: 0;
|
249
|
+
}
|
250
|
+
|
251
|
+
table tr th :last-child, table tr td :last-child {
|
252
|
+
margin-bottom: 0;
|
253
|
+
}
|
254
|
+
|
255
|
+
img {
|
256
|
+
max-width: 100%;
|
257
|
+
}
|
258
|
+
|
259
|
+
span.frame {
|
260
|
+
display: block;
|
261
|
+
overflow: hidden;
|
262
|
+
}
|
263
|
+
|
264
|
+
span.frame > span {
|
265
|
+
border: 1px solid #dddddd;
|
266
|
+
display: block;
|
267
|
+
float: left;
|
268
|
+
overflow: hidden;
|
269
|
+
margin: 13px 0 0;
|
270
|
+
padding: 7px;
|
271
|
+
width: auto;
|
272
|
+
}
|
273
|
+
|
274
|
+
span.frame span img {
|
275
|
+
display: block;
|
276
|
+
float: left;
|
277
|
+
}
|
278
|
+
|
279
|
+
span.frame span span {
|
280
|
+
clear: both;
|
281
|
+
color: #333333;
|
282
|
+
display: block;
|
283
|
+
padding: 5px 0 0;
|
284
|
+
}
|
285
|
+
|
286
|
+
span.align-center {
|
287
|
+
display: block;
|
288
|
+
overflow: hidden;
|
289
|
+
clear: both;
|
290
|
+
}
|
291
|
+
|
292
|
+
span.align-center > span {
|
293
|
+
display: block;
|
294
|
+
overflow: hidden;
|
295
|
+
margin: 13px auto 0;
|
296
|
+
text-align: center;
|
297
|
+
}
|
298
|
+
|
299
|
+
span.align-center span img {
|
300
|
+
margin: 0 auto;
|
301
|
+
text-align: center;
|
302
|
+
}
|
303
|
+
|
304
|
+
span.align-right {
|
305
|
+
display: block;
|
306
|
+
overflow: hidden;
|
307
|
+
clear: both;
|
308
|
+
}
|
309
|
+
|
310
|
+
span.align-right > span {
|
311
|
+
display: block;
|
312
|
+
overflow: hidden;
|
313
|
+
margin: 13px 0 0;
|
314
|
+
text-align: right;
|
315
|
+
}
|
316
|
+
|
317
|
+
span.align-right span img {
|
318
|
+
margin: 0;
|
319
|
+
text-align: right;
|
320
|
+
}
|
321
|
+
|
322
|
+
span.float-left {
|
323
|
+
display: block;
|
324
|
+
margin-right: 13px;
|
325
|
+
overflow: hidden;
|
326
|
+
float: left;
|
327
|
+
}
|
328
|
+
|
329
|
+
span.float-left span {
|
330
|
+
margin: 13px 0 0;
|
331
|
+
}
|
332
|
+
|
333
|
+
span.float-right {
|
334
|
+
display: block;
|
335
|
+
margin-left: 13px;
|
336
|
+
overflow: hidden;
|
337
|
+
float: right;
|
338
|
+
}
|
339
|
+
|
340
|
+
span.float-right > span {
|
341
|
+
display: block;
|
342
|
+
overflow: hidden;
|
343
|
+
margin: 13px auto 0;
|
344
|
+
text-align: right;
|
345
|
+
}
|
346
|
+
|
347
|
+
code, tt {
|
348
|
+
margin: 0 2px;
|
349
|
+
padding: 0 5px;
|
350
|
+
white-space: nowrap;
|
351
|
+
border: 1px solid #eaeaea;
|
352
|
+
background-color: #f8f8f8;
|
353
|
+
border-radius: 3px;
|
354
|
+
}
|
355
|
+
|
356
|
+
pre code {
|
357
|
+
margin: 0;
|
358
|
+
padding: 0;
|
359
|
+
white-space: pre;
|
360
|
+
border: none;
|
361
|
+
background: transparent;
|
362
|
+
}
|
363
|
+
|
364
|
+
.highlight pre {
|
365
|
+
background-color: #f8f8f8;
|
366
|
+
border: 1px solid #cccccc;
|
367
|
+
font-size: 13px;
|
368
|
+
line-height: 19px;
|
369
|
+
overflow: auto;
|
370
|
+
padding: 6px 10px;
|
371
|
+
border-radius: 3px;
|
372
|
+
}
|
373
|
+
|
374
|
+
pre {
|
375
|
+
background-color: #f8f8f8;
|
376
|
+
border: 1px solid #cccccc;
|
377
|
+
font-size: 13px;
|
378
|
+
line-height: 19px;
|
379
|
+
overflow: auto;
|
380
|
+
padding: 6px 10px;
|
381
|
+
border-radius: 3px;
|
382
|
+
}
|
383
|
+
|
384
|
+
pre code, pre tt {
|
385
|
+
background-color: transparent;
|
386
|
+
border: none;
|
387
|
+
}
|
388
|
+
|
389
|
+
|
390
|
+
/* generic helper syles */
|
391
|
+
ul.unstyled {
|
392
|
+
list-style-type: none;
|
393
|
+
margin: 0;
|
394
|
+
padding: 0;
|
395
|
+
}
|
396
|
+
|
397
|
+
|
398
|
+
/* invoice styles */
|
399
|
+
ul.invoice-items {
|
400
|
+
|
401
|
+
}
|
402
|
+
|
403
|
+
ul.invoice-items li {
|
404
|
+
margin: 10px 0;
|
405
|
+
padding: 5px 0;
|
406
|
+
/* border-bottom: 1px solid #CCC;*/
|
407
|
+
page-break-inside : avoid;
|
408
|
+
}
|
409
|
+
|
410
|
+
ul.invoice-items li table {
|
411
|
+
width: 100%;
|
412
|
+
font-size: 14px;
|
413
|
+
}
|
414
|
+
|
415
|
+
ul.invoice-items td {
|
416
|
+
border : none;
|
417
|
+
outline : none;
|
418
|
+
}
|
419
|
+
|
420
|
+
ul.invoice-items .invoice-item-description {
|
421
|
+
padding-bottom: 15px;
|
422
|
+
border: 1px dashed #ccc;
|
423
|
+
}
|
424
|
+
|
425
|
+
.invoice-items .full-row {
|
426
|
+
width: 100%;
|
427
|
+
}
|
428
|
+
|
429
|
+
body, html {
|
430
|
+
margin: 0;
|
431
|
+
padding: 0;
|
432
|
+
font-size: 14px;
|
433
|
+
}
|
434
|
+
|
435
|
+
h1, h2, h3, h4 {
|
436
|
+
page-break-after: avoid;
|
437
|
+
}
|
438
|
+
|
439
|
+
h1 {
|
440
|
+
font-size: 22px;
|
441
|
+
}
|
442
|
+
|
443
|
+
h2 {
|
444
|
+
font-size: 18px;
|
445
|
+
}
|
446
|
+
|
447
|
+
h3 {
|
448
|
+
font-size: 16px;
|
449
|
+
}
|
450
|
+
|
451
|
+
h4 {
|
452
|
+
font-size: 14px;
|
453
|
+
}
|
454
|
+
|
455
|
+
#logo {
|
456
|
+
width : 100px;
|
457
|
+
height : auto;
|
458
|
+
position : absolute;
|
459
|
+
top: 0;
|
460
|
+
right: 0;
|
461
|
+
}
|
data/lib/extension.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# Author:: Jason Wijegooneratne (mailto:code@jwije.com)
|
2
|
+
# Copyright:: Copyright (c) 2014 Jason Wijegooneratne
|
3
|
+
# License:: MIT. See LICENSE.md distributed with the source code for more information.
|
4
|
+
|
5
|
+
# =THE LEWT EXTENSION BASE CLASS
|
6
|
+
#
|
7
|
+
# This class provides some basic structuring for LEWT extensions
|
8
|
+
# and additionally adds some meta features to extensions which
|
9
|
+
# implement it in order to streamline development of LEWT
|
10
|
+
# extensions.
|
11
|
+
|
12
|
+
require "yaml"
|
13
|
+
|
14
|
+
# This module acts as a container for the LEWT namespace
|
15
|
+
module LEWT
|
16
|
+
|
17
|
+
class Extension
|
18
|
+
|
19
|
+
# LEWT Stash is the user configured stash path where all extensions, config file, templates etc are stored.
|
20
|
+
attr_reader :lewt_stash, :lewt_settings, :customers, :enterprise, :options, :command_name
|
21
|
+
|
22
|
+
# @@extensions is a registry shared between all extensions that impliment this class
|
23
|
+
# containing there class names for invocation by the core system.
|
24
|
+
@@lewt_extensions = Array.new
|
25
|
+
@options = nil
|
26
|
+
|
27
|
+
# This method is inoked by subclasses to initialise themselves within Lewt's extension registry.
|
28
|
+
# ext_init [Hash]:: contains the keys <tt>:cmd</tt>, <tt>:options</tt> - which are the command name (String) and options (Hash) for the extension respectively.
|
29
|
+
def initialize ( ext_init = { :cmd => "lewt_base_extension" } )
|
30
|
+
core_settings = YAML.load_file( File.expand_path( '../config/settings.yml', __FILE__) )
|
31
|
+
if File.exists? File.expand_path( '~/.lewt_settings', __FILE__)
|
32
|
+
core_settings.merge! YAML.load_file( File.expand_path( '~/.lewt_settings', __FILE__) )
|
33
|
+
end
|
34
|
+
|
35
|
+
@lewt_stash = core_settings['lewt_stash'] || File.expand_path('../', __FILE__) + "/config/"
|
36
|
+
load_lewt_settings()
|
37
|
+
@command_name = ext_init[:cmd]
|
38
|
+
@options = ext_init[:options] || nil
|
39
|
+
register_extension
|
40
|
+
end
|
41
|
+
|
42
|
+
# returns all registered extensions as an array of class names
|
43
|
+
def lewt_extensions
|
44
|
+
@@lewt_extensions
|
45
|
+
end
|
46
|
+
|
47
|
+
# This method mathes customers wth query strings provided by users in the CLI
|
48
|
+
# query [String]:: A search string to query against.
|
49
|
+
# suppress [String]:: A list of clients to exclude. Defaults to nil ie: none.
|
50
|
+
def get_matched_customers( query, suppress = nil )
|
51
|
+
requestedClients = Array.new
|
52
|
+
if query == nil
|
53
|
+
@customers.each do |client|
|
54
|
+
client_match = [ client["alias"], client["name"] ].join("|")
|
55
|
+
if suppress == nil or client_match.match(suppress.gsub(Lewt::OPTION_DELIMITER_REGEX,"|")) == nil
|
56
|
+
requestedClients.push(client)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
else
|
60
|
+
@customers.each do |client|
|
61
|
+
client_match = [ client["alias"], client["name"] ].join("|")
|
62
|
+
if client_match.match( query.gsub(Lewt::OPTION_DELIMITER_REGEX,"|") ) != nil
|
63
|
+
if suppress == nil or client_match.match(suppress.gsub(Lewt::OPTION_DELIMITER_REGEX,"|")) == nil
|
64
|
+
requestedClients.push(client)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
return requestedClients
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
# This method loads the core LEWT settings files
|
75
|
+
def load_lewt_settings
|
76
|
+
@lewt_settings = load_settings( "settings.yml")
|
77
|
+
# Start by loading the local config files
|
78
|
+
@customers = load_settings("customers.yml")
|
79
|
+
@enterprise = load_settings("enterprise.yml")
|
80
|
+
end
|
81
|
+
|
82
|
+
# Writes a key/value pair to the settings file. This can then be accessed with lewt_settings[key] and is persisted
|
83
|
+
# on the user's file system in a YAML settings file.
|
84
|
+
# key [String]:: The key to write to the settings file
|
85
|
+
# value:: The value to assign to this key.
|
86
|
+
def write_settings ( file, key, value )
|
87
|
+
settings = load_settings(file)
|
88
|
+
settings[key] = value
|
89
|
+
File.open( @lewt_stash + file, 'w') {|f| f.write settings.to_yaml } #Store
|
90
|
+
load_lewt_settings() # reload settings vars
|
91
|
+
return settings
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
# Formats the --save-name parameter if specified as a template into a string for usage with a File.write method.
|
97
|
+
# options [Hash]:: The options hash passed to this function by the Lewt program.
|
98
|
+
# i [Integer]:: The current data array index to match the :targets option against
|
99
|
+
def format_save_name(o, i)
|
100
|
+
match_client = /\#alias/
|
101
|
+
match_date = /\#date/
|
102
|
+
t = o[:save_file].dup
|
103
|
+
c = t.match match_client
|
104
|
+
d = t.match match_date
|
105
|
+
|
106
|
+
if c != nil
|
107
|
+
clients = get_matched_customers(o[:target])
|
108
|
+
client_alias = clients[i]["alias"]
|
109
|
+
t.gsub! match_client, client_alias
|
110
|
+
end
|
111
|
+
|
112
|
+
if d != nil
|
113
|
+
t.gsub! match_date, Date.today.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
return t
|
117
|
+
end
|
118
|
+
|
119
|
+
# Loads the specified settings file
|
120
|
+
# property:: The variable you would like to assign the loaded YAML to
|
121
|
+
# file [String]:: The settings file to load from lewt stash.
|
122
|
+
def load_settings ( file )
|
123
|
+
return YAML.load_file( @lewt_stash + file )
|
124
|
+
end
|
125
|
+
|
126
|
+
# register the given extensions' class name with the system for later invocation
|
127
|
+
def register_extension
|
128
|
+
# only register subclass of this basclass
|
129
|
+
if self.class != LEWT::Extension
|
130
|
+
@@lewt_extensions << self.clone
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# This method is used by extensions to hook into the command line
|
135
|
+
# call and set some user customisable at runtime.
|
136
|
+
# It will be passed the options object from the ruby OptParse class.
|
137
|
+
# def register_options opts end
|
138
|
+
|
139
|
+
# The extract method can be implemented to extract data from custom sources.
|
140
|
+
def extract
|
141
|
+
raise Exception "An extraction method is not defined by the #{self.class.name} class"
|
142
|
+
end
|
143
|
+
|
144
|
+
# The process method can be implemented to process extracted data.
|
145
|
+
# It is passed the extracted data from the previous stage.
|
146
|
+
def process extraced_data
|
147
|
+
raise Exception "A processing method is not defined by the #{self.class.name} class"
|
148
|
+
end
|
149
|
+
|
150
|
+
# The render method can be implemented to create views for data.
|
151
|
+
# It is passed the processed data from the previous stage.
|
152
|
+
def render
|
153
|
+
raise Exception "A rendering method is not defined by the #{self.class.name} class"
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'icalendar'
|
2
|
+
|
3
|
+
# Author:: Jason Wijegooneratne (mailto:code@jwije.com)
|
4
|
+
# Copyright:: Copyright (c) 2014 Jason Wijegooneratne
|
5
|
+
# License:: MIT. See LICENSE.md distributed with the source code for more information.
|
6
|
+
|
7
|
+
|
8
|
+
module CalendarExtractors
|
9
|
+
|
10
|
+
# This class handles extraction from iCal sources.
|
11
|
+
#
|
12
|
+
# ===Usage:
|
13
|
+
# - Add the key <tt>osxcal_path</tt> to your settings file and set its value to the location of your calender directory. This must
|
14
|
+
# be the full path.
|
15
|
+
|
16
|
+
class AppleExtractor < CalExtractor
|
17
|
+
|
18
|
+
# Initialises the object and calls the parent class' super() method.
|
19
|
+
def initialize( dateStart, dateEnd, targets, lewt_settings, suppressTargets )
|
20
|
+
@calendarPath = lewt_settings["osxcal_path"]
|
21
|
+
@suppressTargets = suppressTargets
|
22
|
+
super( dateStart, dateEnd, targets )
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Scans the apple calender storage directory and compiles a mashup of ical data it finds
|
27
|
+
def aggregateAppleCalenders
|
28
|
+
calenders = Array.new
|
29
|
+
Dir.glob("#{@calendarPath}**/*.ics").each do |path|
|
30
|
+
calenders += Icalendar.parse( File.open(path) )
|
31
|
+
end
|
32
|
+
return calenders
|
33
|
+
end
|
34
|
+
|
35
|
+
# Open iCalender file, parses it, then check events with the regular CalExtractor methods.
|
36
|
+
# Sets the data property of this object if match data is found.
|
37
|
+
def extractCalendarData
|
38
|
+
calendars = aggregateAppleCalenders()
|
39
|
+
calendars.each do |calendar|
|
40
|
+
calendar.events.each do |e|
|
41
|
+
target = self.isTargetCustomer?( e.summary )
|
42
|
+
dstart = Time.parse( e.dtstart.to_s )
|
43
|
+
dend = Time.parse( e.dtend.to_s )
|
44
|
+
if self.isTargetDate?(dstart) == true && target != false
|
45
|
+
timeDiff = (dend - dstart) /60/60
|
46
|
+
row = LEWT::LEWTLedger.new({
|
47
|
+
:date_start => e.dtstart,
|
48
|
+
:date_end => e.dtend,
|
49
|
+
:category => @category,
|
50
|
+
:entity => target["name"],
|
51
|
+
:description => e.description.to_s,
|
52
|
+
:quantity => timeDiff,
|
53
|
+
:unit_cost => target["rate"]
|
54
|
+
})
|
55
|
+
@data.push(row)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|