Pratt 1.5.6

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.
Files changed (283) hide show
  1. data/.exrc +61 -0
  2. data/.gitignore +4 -0
  3. data/History.txt +6 -0
  4. data/Manifest.txt +46 -0
  5. data/Pratt.gemspec +351 -0
  6. data/README.txt +66 -0
  7. data/Rakefile +85 -0
  8. data/TODO +54 -0
  9. data/VERSION +1 -0
  10. data/bin/pratt.rb +13 -0
  11. data/config.rb +34 -0
  12. data/lib/pratt.rb +527 -0
  13. data/lib/pratt/array.rb +11 -0
  14. data/lib/pratt/string.rb +18 -0
  15. data/models/app.rb +40 -0
  16. data/models/customer.rb +24 -0
  17. data/models/payment.rb +22 -0
  18. data/models/pratt.rb +19 -0
  19. data/models/project.rb +82 -0
  20. data/models/whence.rb +70 -0
  21. data/pkgs/tile-0.8.2.tar.gz +0 -0
  22. data/pkgs/tile-0.8.2/ANNOUNCE.txt +95 -0
  23. data/pkgs/tile-0.8.2/ChangeLog +4651 -0
  24. data/pkgs/tile-0.8.2/Makefile +250 -0
  25. data/pkgs/tile-0.8.2/Makefile.in +250 -0
  26. data/pkgs/tile-0.8.2/README.txt +86 -0
  27. data/pkgs/tile-0.8.2/aclocal.m4 +2 -0
  28. data/pkgs/tile-0.8.2/altTheme.o +0 -0
  29. data/pkgs/tile-0.8.2/blink.o +0 -0
  30. data/pkgs/tile-0.8.2/button.o +0 -0
  31. data/pkgs/tile-0.8.2/cache.o +0 -0
  32. data/pkgs/tile-0.8.2/clamTheme.o +0 -0
  33. data/pkgs/tile-0.8.2/classicTheme.o +0 -0
  34. data/pkgs/tile-0.8.2/config.log +1330 -0
  35. data/pkgs/tile-0.8.2/config.status +795 -0
  36. data/pkgs/tile-0.8.2/configure +15248 -0
  37. data/pkgs/tile-0.8.2/configure.in +89 -0
  38. data/pkgs/tile-0.8.2/demos/autocomplete.tcl +59 -0
  39. data/pkgs/tile-0.8.2/demos/demo.tcl +870 -0
  40. data/pkgs/tile-0.8.2/demos/dirbrowser.tcl +167 -0
  41. data/pkgs/tile-0.8.2/demos/dlgtest.tcl +97 -0
  42. data/pkgs/tile-0.8.2/demos/iconlib.tcl +110 -0
  43. data/pkgs/tile-0.8.2/demos/repeater.tcl +117 -0
  44. data/pkgs/tile-0.8.2/demos/toolbutton.tcl +101 -0
  45. data/pkgs/tile-0.8.2/doc/Geometry.3 +230 -0
  46. data/pkgs/tile-0.8.2/doc/INDEX.MAP +153 -0
  47. data/pkgs/tile-0.8.2/doc/Makefile +36 -0
  48. data/pkgs/tile-0.8.2/doc/TILE.XML +45 -0
  49. data/pkgs/tile-0.8.2/doc/Theme.3 +34 -0
  50. data/pkgs/tile-0.8.2/doc/button.n +75 -0
  51. data/pkgs/tile-0.8.2/doc/checkbutton.n +61 -0
  52. data/pkgs/tile-0.8.2/doc/combobox.n +98 -0
  53. data/pkgs/tile-0.8.2/doc/converting.txt +97 -0
  54. data/pkgs/tile-0.8.2/doc/dialog.n +122 -0
  55. data/pkgs/tile-0.8.2/doc/entry.n +438 -0
  56. data/pkgs/tile-0.8.2/doc/frame.n +43 -0
  57. data/pkgs/tile-0.8.2/doc/html/Geometry.html +304 -0
  58. data/pkgs/tile-0.8.2/doc/html/Theme.html +48 -0
  59. data/pkgs/tile-0.8.2/doc/html/button.html +120 -0
  60. data/pkgs/tile-0.8.2/doc/html/category-index.html +18 -0
  61. data/pkgs/tile-0.8.2/doc/html/checkbutton.html +94 -0
  62. data/pkgs/tile-0.8.2/doc/html/combobox.html +164 -0
  63. data/pkgs/tile-0.8.2/doc/html/converting.txt +97 -0
  64. data/pkgs/tile-0.8.2/doc/html/dialog.html +159 -0
  65. data/pkgs/tile-0.8.2/doc/html/entry.html +613 -0
  66. data/pkgs/tile-0.8.2/doc/html/frame.html +76 -0
  67. data/pkgs/tile-0.8.2/doc/html/image.html +100 -0
  68. data/pkgs/tile-0.8.2/doc/html/index.html +25 -0
  69. data/pkgs/tile-0.8.2/doc/html/keyword-index.html +228 -0
  70. data/pkgs/tile-0.8.2/doc/html/label.html +133 -0
  71. data/pkgs/tile-0.8.2/doc/html/labelframe.html +91 -0
  72. data/pkgs/tile-0.8.2/doc/html/manpage.css +212 -0
  73. data/pkgs/tile-0.8.2/doc/html/menubutton.html +63 -0
  74. data/pkgs/tile-0.8.2/doc/html/notebook.html +280 -0
  75. data/pkgs/tile-0.8.2/doc/html/paned.html +149 -0
  76. data/pkgs/tile-0.8.2/doc/html/progressbar.html +138 -0
  77. data/pkgs/tile-0.8.2/doc/html/radiobutton.html +89 -0
  78. data/pkgs/tile-0.8.2/doc/html/scrollbar.html +221 -0
  79. data/pkgs/tile-0.8.2/doc/html/separator.html +48 -0
  80. data/pkgs/tile-0.8.2/doc/html/sizegrip.html +62 -0
  81. data/pkgs/tile-0.8.2/doc/html/style.html +172 -0
  82. data/pkgs/tile-0.8.2/doc/html/tile-intro.html +164 -0
  83. data/pkgs/tile-0.8.2/doc/html/treeview.html +634 -0
  84. data/pkgs/tile-0.8.2/doc/html/widget.html +342 -0
  85. data/pkgs/tile-0.8.2/doc/image.n +81 -0
  86. data/pkgs/tile-0.8.2/doc/internals.txt +409 -0
  87. data/pkgs/tile-0.8.2/doc/label.n +75 -0
  88. data/pkgs/tile-0.8.2/doc/labelframe.n +64 -0
  89. data/pkgs/tile-0.8.2/doc/man.macros +239 -0
  90. data/pkgs/tile-0.8.2/doc/menubutton.n +41 -0
  91. data/pkgs/tile-0.8.2/doc/notebook.n +188 -0
  92. data/pkgs/tile-0.8.2/doc/paned.n +95 -0
  93. data/pkgs/tile-0.8.2/doc/progressbar.n +79 -0
  94. data/pkgs/tile-0.8.2/doc/radiobutton.n +57 -0
  95. data/pkgs/tile-0.8.2/doc/scrollbar.n +160 -0
  96. data/pkgs/tile-0.8.2/doc/separator.n +30 -0
  97. data/pkgs/tile-0.8.2/doc/sizegrip.n +53 -0
  98. data/pkgs/tile-0.8.2/doc/style.n +119 -0
  99. data/pkgs/tile-0.8.2/doc/tile-intro.n +165 -0
  100. data/pkgs/tile-0.8.2/doc/tmml.options +4 -0
  101. data/pkgs/tile-0.8.2/doc/treeview.n +415 -0
  102. data/pkgs/tile-0.8.2/doc/widget.n +227 -0
  103. data/pkgs/tile-0.8.2/doc/xml/Geometry.tmml +379 -0
  104. data/pkgs/tile-0.8.2/doc/xml/INDEX.MAP +153 -0
  105. data/pkgs/tile-0.8.2/doc/xml/Theme.tmml +63 -0
  106. data/pkgs/tile-0.8.2/doc/xml/button.tmml +134 -0
  107. data/pkgs/tile-0.8.2/doc/xml/checkbutton.tmml +119 -0
  108. data/pkgs/tile-0.8.2/doc/xml/combobox.tmml +184 -0
  109. data/pkgs/tile-0.8.2/doc/xml/dialog.tmml +195 -0
  110. data/pkgs/tile-0.8.2/doc/xml/entry.tmml +630 -0
  111. data/pkgs/tile-0.8.2/doc/xml/frame.tmml +98 -0
  112. data/pkgs/tile-0.8.2/doc/xml/image.tmml +101 -0
  113. data/pkgs/tile-0.8.2/doc/xml/label.tmml +154 -0
  114. data/pkgs/tile-0.8.2/doc/xml/labelframe.tmml +116 -0
  115. data/pkgs/tile-0.8.2/doc/xml/menubutton.tmml +80 -0
  116. data/pkgs/tile-0.8.2/doc/xml/notebook.tmml +306 -0
  117. data/pkgs/tile-0.8.2/doc/xml/paned.tmml +154 -0
  118. data/pkgs/tile-0.8.2/doc/xml/progressbar.tmml +151 -0
  119. data/pkgs/tile-0.8.2/doc/xml/radiobutton.tmml +109 -0
  120. data/pkgs/tile-0.8.2/doc/xml/scrollbar.tmml +233 -0
  121. data/pkgs/tile-0.8.2/doc/xml/separator.tmml +59 -0
  122. data/pkgs/tile-0.8.2/doc/xml/sizegrip.tmml +82 -0
  123. data/pkgs/tile-0.8.2/doc/xml/style.tmml +171 -0
  124. data/pkgs/tile-0.8.2/doc/xml/tile-intro.tmml +192 -0
  125. data/pkgs/tile-0.8.2/doc/xml/treeview.tmml +604 -0
  126. data/pkgs/tile-0.8.2/doc/xml/widget.tmml +372 -0
  127. data/pkgs/tile-0.8.2/entry.o +0 -0
  128. data/pkgs/tile-0.8.2/frame.o +0 -0
  129. data/pkgs/tile-0.8.2/generic/Makefile.in +221 -0
  130. data/pkgs/tile-0.8.2/generic/TODO +493 -0
  131. data/pkgs/tile-0.8.2/generic/altTheme.c +1172 -0
  132. data/pkgs/tile-0.8.2/generic/blink.c +168 -0
  133. data/pkgs/tile-0.8.2/generic/button.c +858 -0
  134. data/pkgs/tile-0.8.2/generic/cache.c +354 -0
  135. data/pkgs/tile-0.8.2/generic/clamTheme.c +974 -0
  136. data/pkgs/tile-0.8.2/generic/classicTheme.c +518 -0
  137. data/pkgs/tile-0.8.2/generic/configure +10334 -0
  138. data/pkgs/tile-0.8.2/generic/configure.in +100 -0
  139. data/pkgs/tile-0.8.2/generic/entry.c +1922 -0
  140. data/pkgs/tile-0.8.2/generic/frame.c +648 -0
  141. data/pkgs/tile-0.8.2/generic/gunk.h +44 -0
  142. data/pkgs/tile-0.8.2/generic/image.c +416 -0
  143. data/pkgs/tile-0.8.2/generic/label.c +663 -0
  144. data/pkgs/tile-0.8.2/generic/layout.c +1215 -0
  145. data/pkgs/tile-0.8.2/generic/manager.c +554 -0
  146. data/pkgs/tile-0.8.2/generic/manager.h +91 -0
  147. data/pkgs/tile-0.8.2/generic/notebook.c +1380 -0
  148. data/pkgs/tile-0.8.2/generic/paned.c +958 -0
  149. data/pkgs/tile-0.8.2/generic/pkgIndex.tcl.in +7 -0
  150. data/pkgs/tile-0.8.2/generic/progress.c +549 -0
  151. data/pkgs/tile-0.8.2/generic/scale.c +526 -0
  152. data/pkgs/tile-0.8.2/generic/scroll.c +253 -0
  153. data/pkgs/tile-0.8.2/generic/scrollbar.c +346 -0
  154. data/pkgs/tile-0.8.2/generic/separator.c +132 -0
  155. data/pkgs/tile-0.8.2/generic/square.c +306 -0
  156. data/pkgs/tile-0.8.2/generic/tagset.c +147 -0
  157. data/pkgs/tile-0.8.2/generic/tile.c +296 -0
  158. data/pkgs/tile-0.8.2/generic/tkElements.c +1280 -0
  159. data/pkgs/tile-0.8.2/generic/tkTheme.c +1708 -0
  160. data/pkgs/tile-0.8.2/generic/tkTheme.h +419 -0
  161. data/pkgs/tile-0.8.2/generic/tkThemeInt.h +45 -0
  162. data/pkgs/tile-0.8.2/generic/tkstate.c +268 -0
  163. data/pkgs/tile-0.8.2/generic/trace.c +145 -0
  164. data/pkgs/tile-0.8.2/generic/track.c +174 -0
  165. data/pkgs/tile-0.8.2/generic/treeview.c +3211 -0
  166. data/pkgs/tile-0.8.2/generic/ttk.decls +154 -0
  167. data/pkgs/tile-0.8.2/generic/ttkDecls.h +340 -0
  168. data/pkgs/tile-0.8.2/generic/ttkStubInit.c +61 -0
  169. data/pkgs/tile-0.8.2/generic/ttkStubLib.c +70 -0
  170. data/pkgs/tile-0.8.2/generic/widget.c +785 -0
  171. data/pkgs/tile-0.8.2/generic/widget.h +263 -0
  172. data/pkgs/tile-0.8.2/image.o +0 -0
  173. data/pkgs/tile-0.8.2/label.o +0 -0
  174. data/pkgs/tile-0.8.2/layout.o +0 -0
  175. data/pkgs/tile-0.8.2/library/altTheme.tcl +101 -0
  176. data/pkgs/tile-0.8.2/library/aquaTheme.tcl +62 -0
  177. data/pkgs/tile-0.8.2/library/button.tcl +85 -0
  178. data/pkgs/tile-0.8.2/library/clamTheme.tcl +139 -0
  179. data/pkgs/tile-0.8.2/library/classicTheme.tcl +108 -0
  180. data/pkgs/tile-0.8.2/library/combobox.tcl +439 -0
  181. data/pkgs/tile-0.8.2/library/cursors.tcl +36 -0
  182. data/pkgs/tile-0.8.2/library/defaults.tcl +118 -0
  183. data/pkgs/tile-0.8.2/library/dialog.tcl +274 -0
  184. data/pkgs/tile-0.8.2/library/entry.tcl +580 -0
  185. data/pkgs/tile-0.8.2/library/fonts.tcl +153 -0
  186. data/pkgs/tile-0.8.2/library/icons.tcl +105 -0
  187. data/pkgs/tile-0.8.2/library/keynav.tcl +192 -0
  188. data/pkgs/tile-0.8.2/library/menubutton.tcl +171 -0
  189. data/pkgs/tile-0.8.2/library/notebook.tcl +193 -0
  190. data/pkgs/tile-0.8.2/library/paned.tcl +87 -0
  191. data/pkgs/tile-0.8.2/library/progress.tcl +51 -0
  192. data/pkgs/tile-0.8.2/library/scale.tcl +54 -0
  193. data/pkgs/tile-0.8.2/library/scrollbar.tcl +125 -0
  194. data/pkgs/tile-0.8.2/library/sizegrip.tcl +77 -0
  195. data/pkgs/tile-0.8.2/library/tile.tcl +211 -0
  196. data/pkgs/tile-0.8.2/library/treeview.tcl +382 -0
  197. data/pkgs/tile-0.8.2/library/utils.tcl +254 -0
  198. data/pkgs/tile-0.8.2/library/winTheme.tcl +77 -0
  199. data/pkgs/tile-0.8.2/library/xpTheme.tcl +63 -0
  200. data/pkgs/tile-0.8.2/libtile0.8.2.so +0 -0
  201. data/pkgs/tile-0.8.2/libttkstub.a +0 -0
  202. data/pkgs/tile-0.8.2/license.terms +24 -0
  203. data/pkgs/tile-0.8.2/macosx/aquaTheme.c +1076 -0
  204. data/pkgs/tile-0.8.2/manager.o +0 -0
  205. data/pkgs/tile-0.8.2/notebook.o +0 -0
  206. data/pkgs/tile-0.8.2/paned.o +0 -0
  207. data/pkgs/tile-0.8.2/pkgIndex.tcl +3 -0
  208. data/pkgs/tile-0.8.2/progress.o +0 -0
  209. data/pkgs/tile-0.8.2/scale.o +0 -0
  210. data/pkgs/tile-0.8.2/scroll.o +0 -0
  211. data/pkgs/tile-0.8.2/scrollbar.o +0 -0
  212. data/pkgs/tile-0.8.2/separator.o +0 -0
  213. data/pkgs/tile-0.8.2/tagset.o +0 -0
  214. data/pkgs/tile-0.8.2/tclconfig/install-sh +119 -0
  215. data/pkgs/tile-0.8.2/tclconfig/tcl.m4 +4069 -0
  216. data/pkgs/tile-0.8.2/tclconfig/teax.m4 +109 -0
  217. data/pkgs/tile-0.8.2/tests/all.tcl +18 -0
  218. data/pkgs/tile-0.8.2/tests/bwidget.test +103 -0
  219. data/pkgs/tile-0.8.2/tests/cbtest.tcl +125 -0
  220. data/pkgs/tile-0.8.2/tests/combobox.test +51 -0
  221. data/pkgs/tile-0.8.2/tests/compound.tcl +92 -0
  222. data/pkgs/tile-0.8.2/tests/entry.test +285 -0
  223. data/pkgs/tile-0.8.2/tests/entrytest.tcl +78 -0
  224. data/pkgs/tile-0.8.2/tests/image.test +94 -0
  225. data/pkgs/tile-0.8.2/tests/labelframe.tcl +41 -0
  226. data/pkgs/tile-0.8.2/tests/labelframe.test +137 -0
  227. data/pkgs/tile-0.8.2/tests/layout.test +33 -0
  228. data/pkgs/tile-0.8.2/tests/misc.test +35 -0
  229. data/pkgs/tile-0.8.2/tests/nbtest.tcl +66 -0
  230. data/pkgs/tile-0.8.2/tests/notebook.test +500 -0
  231. data/pkgs/tile-0.8.2/tests/paned.test +298 -0
  232. data/pkgs/tile-0.8.2/tests/progress.test +92 -0
  233. data/pkgs/tile-0.8.2/tests/pwtest.tcl +90 -0
  234. data/pkgs/tile-0.8.2/tests/sbtest.tcl +79 -0
  235. data/pkgs/tile-0.8.2/tests/scrollbar.test +77 -0
  236. data/pkgs/tile-0.8.2/tests/sgtest.tcl +52 -0
  237. data/pkgs/tile-0.8.2/tests/testutils.tcl +20 -0
  238. data/pkgs/tile-0.8.2/tests/tile.test +674 -0
  239. data/pkgs/tile-0.8.2/tests/treetags.test +78 -0
  240. data/pkgs/tile-0.8.2/tests/treeview.test +563 -0
  241. data/pkgs/tile-0.8.2/tests/tvtest.tcl +332 -0
  242. data/pkgs/tile-0.8.2/tests/validate.test +278 -0
  243. data/pkgs/tile-0.8.2/tile.o +0 -0
  244. data/pkgs/tile-0.8.2/tkElements.o +0 -0
  245. data/pkgs/tile-0.8.2/tkTheme.o +0 -0
  246. data/pkgs/tile-0.8.2/tkstate.o +0 -0
  247. data/pkgs/tile-0.8.2/tools/genStubs.tcl +861 -0
  248. data/pkgs/tile-0.8.2/trace.o +0 -0
  249. data/pkgs/tile-0.8.2/track.o +0 -0
  250. data/pkgs/tile-0.8.2/treeview.o +0 -0
  251. data/pkgs/tile-0.8.2/ttkStubInit.o +0 -0
  252. data/pkgs/tile-0.8.2/ttkStubLib.o +0 -0
  253. data/pkgs/tile-0.8.2/widget.o +0 -0
  254. data/pkgs/tile-0.8.2/win/Tile.dsp +261 -0
  255. data/pkgs/tile-0.8.2/win/makefile.vc +527 -0
  256. data/pkgs/tile-0.8.2/win/monitor.c +164 -0
  257. data/pkgs/tile-0.8.2/win/nmakehlp.c +483 -0
  258. data/pkgs/tile-0.8.2/win/rules.vc +512 -0
  259. data/pkgs/tile-0.8.2/win/tile.rc +40 -0
  260. data/pkgs/tile-0.8.2/win/winTheme.c +734 -0
  261. data/pkgs/tile-0.8.2/win/xpTheme.c +1029 -0
  262. data/spec/app_spec.rb +48 -0
  263. data/spec/customer_spec.rb +31 -0
  264. data/spec/fixtures/graph.expectation +18 -0
  265. data/spec/payment_spec.rb +19 -0
  266. data/spec/pratt_spec.rb +148 -0
  267. data/spec/project_spec.rb +163 -0
  268. data/spec/rcov.opts +0 -0
  269. data/spec/spec.opts +1 -0
  270. data/spec/spec_helper.rb +21 -0
  271. data/spec/whence_spec.rb +54 -0
  272. data/tasks/pratt.rb +84 -0
  273. data/templates/model.eruby +12 -0
  274. data/templates/spec.eruby +8 -0
  275. data/views/env.rb +22 -0
  276. data/views/graph.eruby +20 -0
  277. data/views/invoice.eruby +148 -0
  278. data/views/main.rb +92 -0
  279. data/views/pid.eruby +3 -0
  280. data/views/pop.rb +94 -0
  281. data/views/pop2.rb +75 -0
  282. data/views/raw.eruby +11 -0
  283. metadata +390 -0
@@ -0,0 +1,66 @@
1
+ = pratt
2
+
3
+ * Pratt (url)
4
+
5
+ == DESCRIPTION:
6
+
7
+ Pratt (describe your package)
8
+ (Pro/Re)-Active Time Tracker
9
+
10
+ == FEATURES/PROBLEMS:
11
+
12
+ * (list of features or problems)
13
+ Currently you'll need to manually fix the project data since I haven't
14
+ fully integrated the support for Projects to have customer's and my current
15
+ implementation is shoddy.
16
+
17
+ == SYNOPSIS:
18
+
19
+ * pratt (code sample of usage)
20
+
21
+ ** PRATT_ENV=production pratt -D # daemonize (or if already daemonized init the
22
+ gui)
23
+ ** pratt -h (as you'd expect lists available commands)
24
+
25
+ == REQUIREMENTS:
26
+
27
+ * pratt
28
+ debian: libtcltk-ruby tk-tile libsqlite3-dev
29
+ gems: activerecord sqlite3-ruby rspec mocha
30
+ tk: http://sourceforge.net/project/showfiles.php?group_id=11464&package_id=107795&release_id=562098
31
+
32
+ == INSTALL:
33
+
34
+ * sudo gem install
35
+
36
+ Credits
37
+
38
+ Michael Goff
39
+ Thanks for helping me with some of the workflow details
40
+ Michael and Kent Krenrich
41
+ Thanks for the UI input, suggestions, and testing.
42
+
43
+ == LICENSE:
44
+
45
+ (The MIT License)
46
+
47
+ Copyright (c) 2009 QualitySmith LLC
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of this software and associated documentation files (the
51
+ 'Software'), to deal in the Software without restriction, including
52
+ without limitation the rights to use, copy, modify, merge, publish,
53
+ distribute, sublicense, and/or sell copies of the Software, and to
54
+ permit persons to whom the Software is furnished to do so, subject to
55
+ the following conditions:
56
+
57
+ The above copyright notice and this permission notice shall be
58
+ included in all copies or substantial portions of the Software.
59
+
60
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
61
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
62
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
63
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
64
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
65
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
66
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,85 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'lib/pratt'
6
+ require 'tasks/pratt'
7
+
8
+ task :default => :spec
9
+
10
+ begin
11
+ require 'jeweler'
12
+ Jeweler::Tasks.new do |gem|
13
+ gem.name = Pratt::NAME
14
+ gem.summary = Pratt::SUMMARY
15
+ gem.description = Pratt::DESCRIPTION
16
+ gem.email = "frogstarr78@gmail.com"
17
+ gem.homepage = "http://github.com/frogstarr78/pratt"
18
+ gem.authors = Pratt::AUTHORS
19
+ gem.add_development_dependency 'rspec', '>=1.2.6'
20
+ gem.add_development_dependency 'mocha', '>=0.9.5'
21
+ gem.add_runtime_dependency 'activerecord', '>=2.1.1'
22
+ gem.add_runtime_dependency 'sqlite3-ruby', '>=1.2.4'
23
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
+ end
25
+ Jeweler::GemcutterTasks.new
26
+ rescue LoadError
27
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
28
+ end
29
+
30
+ require 'spec/rake/spectask'
31
+ Spec::Rake::SpecTask.new(:spec) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.spec_files = FileList['spec/**/*_spec.rb']
34
+ end
35
+
36
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
37
+ spec.libs << 'lib' << 'spec'
38
+ spec.pattern = 'spec/**/*_spec.rb'
39
+ spec.rcov = true
40
+ end
41
+
42
+ task :spec => :check_dependencies
43
+
44
+ begin
45
+ require 'reek/rake_task'
46
+ Reek::RakeTask.new do |t|
47
+ t.fail_on_error = true
48
+ t.verbose = false
49
+ t.source_files = 'lib/**/*.rb'
50
+ end
51
+ rescue LoadError
52
+ task :reek do
53
+ abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
54
+ end
55
+ end
56
+
57
+ begin
58
+ require 'roodi'
59
+ require 'roodi_task'
60
+ RoodiTask.new do |t|
61
+ t.verbose = false
62
+ end
63
+ rescue LoadError
64
+ task :roodi do
65
+ abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
66
+ end
67
+ end
68
+
69
+ task :default => :spec
70
+
71
+ require 'rake/rdoctask'
72
+ Rake::RDocTask.new do |rdoc|
73
+ if File.exist?('VERSION')
74
+ version = File.read('VERSION')
75
+ else
76
+ version = ""
77
+ end
78
+
79
+ rdoc.rdoc_dir = 'rdoc'
80
+ rdoc.title = "pratt #{version}"
81
+ rdoc.rdoc_files.include('README*')
82
+ rdoc.rdoc_files.include('lib/**/*.rb')
83
+ end
84
+
85
+ # vim: syntax=Ruby
data/TODO ADDED
@@ -0,0 +1,54 @@
1
+ General:
2
+ [ ] Add environment ability
3
+ [ ] Change to use raw command line arguments / Remove optparse lib
4
+ [ ] Update general package setup (It's messy)
5
+ [?] Add file watching like merb
6
+ [?] Move to Observer Pattern for reporting functionality
7
+
8
+ Invoicing:
9
+ [ ] Add shifty week plugin
10
+ [ ] Add support for variable rates.
11
+
12
+ Templating:
13
+ [ ] Invoices
14
+ [ ] Move other output to template based
15
+
16
+ Data:
17
+ [ ] Change to Use DataMapper
18
+ [?] Put everything in the db? (logs, app locks, etc)
19
+
20
+ GUI:
21
+ [ ] Add TrayIcon.
22
+ [ ] Customer CRUD
23
+ [ ] Payment CRUD
24
+ [ ] Re-design prompts
25
+ [ ] Remove fork in favor of ( eval OR (!?!) GUI classes )
26
+
27
+ Test:
28
+ [ ] Add more I'm not testing everything
29
+ [ ] Flog
30
+ [ ] Flay
31
+
32
+
33
+
34
+ Done:
35
+ [x] Whence.last should be last unended.
36
+ [x] Add date range argument to timed report.
37
+ [x] Add validation for start/end at.
38
+ [x] Manual data manipulation date/time.
39
+ [x] Change pop prompt to show helpful time.
40
+ [x] Change Start/Continue interface.
41
+ [x] Add directoryless agnostic runner.
42
+ [x] Log output.
43
+ [x] Add Quiet option??
44
+ [x] Allow only one prompt at a time.
45
+ [x] Handle stop when there isn't a running project w/out errors
46
+ [x] fix gui so if last_unended is detected app knows which gui to display
47
+ [x] Intelligently detect correctly running stored pid.
48
+ [x] Fix pid directory access.
49
+
50
+ [x] Add support for Customer's model
51
+ [x] Add Template command
52
+ [x] Add Invoice command
53
+ [x] Fix colorability
54
+ [x] Add console command
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.5.6
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ bin_file = __FILE__
5
+ bin_file = File.symlink?(bin_file) ? File.readlink(bin_file) : bin_file
6
+
7
+ bin_path = File.expand_path( '..', bin_file )
8
+ pratt_dir = File.dirname( bin_path )
9
+ FileUtils.chdir pratt_dir
10
+
11
+ require "lib/pratt"
12
+
13
+ Pratt.parse(ARGV)
@@ -0,0 +1,34 @@
1
+ require 'active_record'
2
+ PRATT_ENV = ENV["PRATT_ENV"] || 'development' unless Object.const_defined? :PRATT_ENV
3
+
4
+ class Pratt
5
+ @@connected_to = nil
6
+ module Test
7
+ end
8
+
9
+ include FileUtils
10
+
11
+ class << self
12
+ def connected_to?
13
+ @@connected_to
14
+ end
15
+
16
+ def connect! to_env = :development
17
+ puts "Connecting to: #{to_env}"
18
+ @@connected_to = "#{to_env}.sqlite3"
19
+ ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => connected_to? )
20
+ unless File.exists? connected_to?
21
+ Pratt.migrate
22
+ end
23
+ end
24
+
25
+ def connected?
26
+ ActiveRecord::Base.connected?
27
+ end
28
+
29
+ def config= env
30
+ include Pratt::Test
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,527 @@
1
+ # coding: utf-8
2
+ require 'fileutils'
3
+ require 'optparse'
4
+ require 'pathname'
5
+
6
+ require 'rubygems'
7
+ require 'colored'
8
+ require 'chronic'
9
+ require 'active_record'
10
+ require 'erubis'
11
+ require 'hoe'
12
+ require 'mocha'
13
+ require 'config'
14
+
15
+ require 'lib/pratt/array'
16
+ require 'lib/pratt/string'
17
+
18
+ require 'models/app'
19
+ require 'models/customer'
20
+ require 'models/whence'
21
+ require 'models/project'
22
+ require 'models/payment'
23
+
24
+ class Pratt
25
+
26
+ NAME = 'Pratt'
27
+ URL = 'http://www.frogstarr78.com/projects/pratt'
28
+ AUTHORS = ['Scott Noel-Hemming']
29
+ SUMMARY = "Pro/Re-Active Time Tracker. Track time based on what you expect to be working on, with frequent prompts to ensure accuracy."
30
+ DESCRIPTION = %q|
31
+ Need a way to keep track of your time, but get caught up in work? Or constant interruptions?
32
+ Yeah you know who I'm talking about. Those people from the [abc] department always "NEEDING xyz FEATURE NOW!!!".
33
+ Seriously though. I'm usually just loose track of time. I wanted an app that I could start with a task I think
34
+ I'll be working on, but that get's in my face constantly to ensure I'm still working on it. And if I'm not any longer,
35
+ provides an easy way of changing to another task, or if I have changed tasks and not already updated the app, would
36
+ provide an easy way of changing the task of the previously recorded interval. That's what this is intended to do.
37
+
38
+ Time Tracking.
39
+ Proactively set what you expect to work on.
40
+ Reactively modify what you are no longer working on.
41
+ |
42
+ DEPENDENCIES = ["activerecord >=2.1.1", "sqlite3-ruby >=1.2.4", "rspec >=1.2.6", "mocha >=0.9.5"]
43
+ VERSION = File.open( File.join(Dir.pwd, 'VERSION') ).read.strip
44
+
45
+ PID_FILE='pratt.pid'
46
+ FMT = "%a %X %b %d %Y"
47
+ INVOICE_FMT = "%x"
48
+ @@color = true
49
+
50
+ attr_accessor :when_to, :scale, :color, :show_all, :env, :raw_conditions, :template
51
+ attr_reader :project, :todo
52
+ def initialize proj = nil #, when_to
53
+ @when_to = Time.now
54
+ @week = false
55
+ @day = false
56
+ @todo = []
57
+ @scale = nil
58
+ @show_all = false
59
+ @template = nil
60
+ @env = :development
61
+ @raw_conditions = ''
62
+ self.project = proj unless proj.nil?
63
+ end
64
+
65
+ def project= proj
66
+ if proj.is_a?(Project)
67
+ @project = proj
68
+ else
69
+ @project = Project.find_or_create_by_name( { :name => proj } )
70
+ end
71
+ end
72
+
73
+ def << what
74
+ @todo << what
75
+ end
76
+
77
+ def app
78
+ @app ||= App.last
79
+ @app
80
+ end
81
+
82
+ def graph
83
+ @primary = @off_total = @rest_total = 0.0
84
+ self.template = 'graph'
85
+
86
+ if project?
87
+ @projects = [project]
88
+
89
+ @primary = project.time_spent(scale, when_to)
90
+ @scaled_total = project.whences.time_spent(scale, when_to)
91
+ else
92
+ @projects = Project.all
93
+
94
+ @projects.each do |proj|
95
+ @primary = proj.time_spent(scale, when_to) if proj.name == Project.primary.name
96
+ @off_total = proj.time_spent(scale, when_to) if proj.name == Project.off.name
97
+ @rest_total += proj.time_spent(scale, when_to) if Project.rest.collect(&:name).include?(proj.name)
98
+ end
99
+ @scaled_total = Whence.time_spent(scale, when_to)-@off_total
100
+ end
101
+
102
+ if @primary + @off_total + @rest_total > 0.0
103
+ process_template!
104
+ else
105
+ "No data to report"
106
+ end
107
+ end
108
+
109
+ def invoice
110
+ self.template = 'invoice'
111
+
112
+ if project?
113
+ @projects = [project]
114
+
115
+ @total = project.time_spent(scale, when_to)
116
+ else
117
+ @projects = (Project.all - [Project.primary, Project.off])
118
+ @projects.select! {|proj| show_all or ( !show_all and proj.time_spent(scale, when_to) != 0.0 ) }
119
+
120
+ @total = @projects.inject 0.0 do |total, proj|
121
+ total += proj.time_spent(scale, when_to)
122
+ total
123
+ end
124
+ end
125
+
126
+ @projects.each do |project|
127
+ puts "#{project.name} #{project.payment.inspect}"
128
+ end
129
+ if @total > 0.0
130
+ puts process_template!
131
+ else
132
+ puts "No data to report"
133
+ end
134
+ end
135
+
136
+ def console options = []
137
+ options << %w(-r\ irb/completion -r\ lib/pratt --simple-prompt)
138
+ exec "irb #{options.join ' '}"
139
+ end
140
+
141
+ def current
142
+ project_names = ([Project.primary, Project.off] | Project.rest).collect(&:name)
143
+
144
+ if last_whence = Whence.last_unended || Whence.last
145
+ puts " projects: " << (
146
+ project_names.collect {|project_name| "'#{project_name.send(last_whence.end_at.nil? && last_whence.project.name == project_name ? :green : :magenta)}'" }
147
+ ) * ' '
148
+ if last_whence.end_at.nil?
149
+ puts " started: #{last_whence.start_at.strftime(FMT).send(:blue)}"
150
+ time_til = ( app.interval - ( Time.now - last_whence.start_at ) )
151
+ puts "next prompt: %s %s"% [Pratt.send( :fmt_i, time_til / 60.0, 'min', :yellow ), Pratt.send( :fmt_i, time_til % 60, 'sec', :yellow ), ], ''
152
+ end
153
+ else
154
+ puts " projects: " << (
155
+ project_names.collect do |project_name|
156
+ "'#{project_name.magenta}'"
157
+ end
158
+ ) * ' '
159
+ end
160
+ end
161
+
162
+ def begin
163
+ self.project.start! when_to
164
+ end
165
+ def restart
166
+ if project?
167
+ project.restart! when_to
168
+ else
169
+ Whence.last_unended.project.restart! when_to
170
+ end
171
+ end
172
+ def end
173
+ if project?
174
+ project.stop! when_to
175
+ else
176
+ Whence.last_unended.stop! when_to
177
+ end
178
+ end
179
+ def change
180
+ Whence.last.change! project.name
181
+ end
182
+ def destroy
183
+ project.destroy
184
+ end
185
+
186
+ def cpid
187
+ `pgrep -fl 'bin/pratt'`.chomp.split(' ').first
188
+ end
189
+
190
+ def pid
191
+ self.template = 'pid'
192
+ puts process_template!
193
+ end
194
+
195
+ def raw
196
+ self.template = 'raw'
197
+
198
+ if project = Project.find_by_name( raw_conditions )
199
+ @whences = project.whences.all
200
+ else
201
+ case raw_conditions
202
+ when 'all'
203
+ @whences = Whence.find raw_conditions.to_sym
204
+ when /^last$/, 'first'
205
+ @whences = [Whence.find raw_conditions.to_sym]
206
+ when /last[\(\s]?(\d+)[\)\s]?/
207
+ @whences = Whence.all.last($1.to_i)
208
+ else
209
+ @whences = Whence.all :conditions => ["start_at > ?", Chronic.parse("today 00:00")]
210
+ end
211
+ end
212
+ @whences.sort_by(&:id)
213
+ puts process_template!
214
+ end
215
+
216
+ def quit
217
+ project.stop! if project? and project.whences.last_unended
218
+ Whence.last_unended.stop! if Whence.last_unended
219
+ begin
220
+ Process.kill("KILL", app.pid.to_i)
221
+ rescue Errno::ESRCH
222
+ end
223
+ app.pid = ''
224
+ app.gui = ''
225
+ app.save!
226
+ end
227
+
228
+ def gui
229
+ if Whence.last_unended
230
+ pop
231
+ else
232
+ main
233
+ end
234
+ end
235
+
236
+ def show_env
237
+ defork { system("ruby views/env.rb ") }
238
+ end
239
+
240
+ def detect
241
+ if self.daemonized?
242
+ gui
243
+ else
244
+ daemonize!
245
+ end
246
+ end
247
+
248
+ def unlock
249
+ self.app.unlock
250
+ end
251
+
252
+ def run
253
+ self.begin if i_should? :begin
254
+ self.change if i_should? :change
255
+ self.restart if i_should? :restart
256
+ self.end if i_should? :end
257
+
258
+ self.destroy if i_should? :destroy
259
+
260
+ self.pid if i_should? :pid
261
+ self.raw if i_should? :raw
262
+ self.current if i_should? :current
263
+ puts self.graph if i_should? :graph
264
+ self.invoice if i_should? :invoice
265
+ self.console if i_should? :console
266
+ self.gui if i_should? :gui
267
+ self.show_env if i_should? :env
268
+ self.detect if i_should? :detect
269
+ self.unlock if i_should? :unlock
270
+
271
+ self.quit if i_should? :quit
272
+ self.daemonize! if i_should? :daemonize and not self.daemonized?
273
+ end
274
+
275
+ def daemonized?
276
+ !app.pid.blank? and ( cpid.to_i == app.pid )
277
+ end
278
+ def daemonize!
279
+ defork {
280
+ puts "pratt (#{Process.pid.to_s.yellow})"
281
+ app.pid = Process.pid
282
+ app.save!
283
+
284
+ gui
285
+ while(daemonized?)
286
+ sleep(app.interval)
287
+ gui
288
+ end
289
+ quit
290
+ }
291
+ end
292
+
293
+ def max
294
+ self.class.max
295
+ end
296
+
297
+ private
298
+ def main
299
+ self.app.reload
300
+ return if self.app.gui?('main', true)
301
+ self.app.log('main')
302
+ projects = ([Project.primary, Project.off] | Project.rest).collect(&:name)
303
+ if Whence.count == 0
304
+ # first run
305
+ project = Whence.new(:project => Project.primary)
306
+ else
307
+ project = Whence.last_unended || Whence.last
308
+ end
309
+ defork { system("ruby views/main.rb --projects '#{projects*"','"}' --current '#{project.project.name}'") }
310
+ end
311
+ def pop
312
+ self.app.reload
313
+ return if self.app.gui?('pop', true)
314
+ self.app.log('pop')
315
+ self.project = Whence.last_unended.project
316
+ defork { system("ruby views/pop.rb --project '#{project.name}' --start '#{project.whences.last_unended.start_at}' --project_time '#{Pratt.totals(project.time_spent)}'") }
317
+ end
318
+
319
+ def i_should? what
320
+ @todo.include?(what)
321
+ end
322
+
323
+ def project?
324
+ !@project.nil? and @project.name?
325
+ end
326
+
327
+ def process_template!
328
+ input = File.open(Pratt.root("views", "#{template}.eruby").first).read
329
+ erubis = Erubis::Eruby.new(input)
330
+ erubis.evaluate(self)
331
+ end
332
+
333
+ def padded_to_max string
334
+ self.class.padded_to_max string
335
+ end
336
+
337
+ def defork &block
338
+ Process.detach(
339
+ fork &block
340
+ )
341
+ end
342
+
343
+ class << self
344
+
345
+
346
+ def max
347
+ # TODO Fix me
348
+ @max ||= Project.all.inject(0) {|x,p| x = p.name.length if p.name.length > x; x }
349
+ end
350
+
351
+ def color
352
+ @@color
353
+ end
354
+
355
+ def color= c
356
+ @@color = c
357
+ end
358
+
359
+ def color?
360
+ @@color == true
361
+ end
362
+
363
+
364
+ def parse args
365
+ me = Pratt.new
366
+
367
+ opt = OptionParser.new do |opt|
368
+ Pratt.connect! ENV['PRATT_ENV'] || 'development' unless Pratt.connected?
369
+
370
+ opt.on('-b', "--begin PROJECT_NAME", String, "Begin project tracking.") do |proj|
371
+ me.project = proj
372
+ me << :begin
373
+ end
374
+ opt.on('-r', "--restart [PROJECT_NAME]", String, "Restart project log (stop last log and start a new one).
375
+ Applies to last un-ended project, unless a specific project is provided.") do |proj|
376
+ me.project = proj
377
+ me << :restart
378
+ end
379
+ opt.on('-e', "--end [PROJECT_NAME]", String, "Stop tracking interval for last project or supplied project if provided.") do |proj|
380
+ me.project = proj
381
+ me << :end
382
+ end
383
+ opt.on('-c', "--change PROJECT_NAME", String, "Change last time interval to this project.") do |proj|
384
+ me.project = proj
385
+ me << :change
386
+ end
387
+
388
+ opt.on('-g', "--graph [PROJECT_NAME]", String, "Display time spent on supplied project or all projects without argument value.") do |proj|
389
+ me.project = proj
390
+ me << :graph
391
+ end
392
+
393
+ opt.on('-I', "--invoice", "Create an invoice.") do
394
+ me << :invoice
395
+ end
396
+
397
+ templates = []
398
+ Pratt.root("views", "*.eruby") {|view| templates << File.basename(view, '.eruby') }
399
+ opt.on('-t', "--template TEMPLATE", templates, "Template to use for displaying work done.
400
+ Available templates are #{templates.to_sentence('or')}.") do |template|
401
+ me.template = template
402
+ end
403
+ opt.on('--destroy PROJECT_NAME', String, "Remove a project.") do |proj|
404
+ me.project = proj
405
+ me << :destroy
406
+ end
407
+
408
+ opt.on '-w', '--when_to TIME', String, 'When to do something.
409
+ (e.g. log time start|stop, or what time interval to graph)
410
+ If graphing, silently ignored w/out scale argument.' do |when_to|
411
+ me.when_to = Chronic.parse(when_to)
412
+ end
413
+ scales = %w(day week month quarter year)
414
+ opt.on('-l', '--scale SCALE', scales, "Granularity of time argument
415
+ Available scales are #{scales.to_sentence('or')}.
416
+ Only applies to graphing.") do |scale|
417
+ me.scale = scale
418
+ end
419
+
420
+ opt.on('-P', '--pid', "Process id display. (Is it still running)") do
421
+ me << :pid
422
+ end
423
+ opt.on('-R', '--raw [CONDITIONS]', "Dump logs (semi-)raw") do |conditions|
424
+ me << :raw
425
+ me.raw_conditions = conditions
426
+ end
427
+ opt.on('-L', '--log', "Redirect errors") do
428
+ FileUtils.mkdir 'log' unless File.exists? 'log'
429
+ $stderr.reopen('log/pratt.log', 'a')
430
+ $stderr.sync = true
431
+ end
432
+
433
+ opt.on('-N', '--no-color', "Display output without color or special characters.") do
434
+ Pratt.color = false
435
+ end
436
+ opt.on('-A', '--show-all', "Display all project regardless of other options.") do
437
+ me.show_all = true
438
+ end
439
+
440
+ opt.on('-C', "--current", "Show available projects and current project (if there is one)") do
441
+ me << :current
442
+ end
443
+
444
+ opt.on('-i', "--interval INTERVAL", Float, "Set the remind interval/min (Only applies to daemonized process).") do |interval|
445
+ me.app.interval = interval
446
+ me.app.save
447
+ end
448
+ opt.on('-D', '--detect', 'Detect appropriate behavior. (Daemonize or Graphical).') do
449
+ me << :detect
450
+ end
451
+ opt.on('-G', '--gui', 'Show "smart" gui.') do
452
+ me << :gui
453
+ end
454
+ opt.on("-d", "--daemonize", "Start daemon.") do
455
+ me << :daemonize
456
+ end
457
+ opt.on('-q', "--quit", "Stop daemon.") do
458
+ me << :quit
459
+ end
460
+ opt.on('-U', '--unlock', "Manually unlock a gui that has died but left it's lock around.") do
461
+ me << :unlock
462
+ end
463
+
464
+ opt.on '-V', '--version' do
465
+ puts "Pro-Reactive Time Tracker [Pratt] (#{VERSION})"
466
+ end
467
+
468
+ opt.parse!
469
+ end
470
+
471
+ me << :env if args.include? 'env'
472
+ me << :console if args.include? 'console'
473
+
474
+ me.run
475
+ end
476
+
477
+ def totals hr, fmt = false
478
+ "#{fmt_i(hr / 24, 'day', :cyan)} #{fmt_i(hr % 24, 'hour', :yellow)} #{fmt_i((60*(hr -= hr.to_i)), 'min', :green)}"
479
+ end
480
+
481
+ def percent label, off, total, color
482
+ fmt_f((off/total)*100, label, color)
483
+ end
484
+
485
+ def root *globs, &block
486
+ root = Dir.pwd
487
+ if globs.empty?
488
+ subdir = root
489
+ else
490
+ subdir = File.join(root, *globs)
491
+ end
492
+
493
+ if block_given?
494
+ Pathname.glob(subdir) {|dir_files| yield dir_files }
495
+ else
496
+ Pathname.glob(subdir)
497
+ end
498
+ end
499
+
500
+
501
+ def padded_to_max string
502
+ "%#{max}.#{max}s"% string
503
+ end
504
+
505
+ def migrate
506
+ Pratt.root( 'models', '*.rb' ) do |model_file|
507
+ klass = File.basename( model_file, '.rb' ).capitalize.constantize
508
+ begin
509
+ ActiveRecord::Base.connection.table_structure(model_file)
510
+ rescue ActiveRecord::StatementInvalid
511
+ klass.migrate :up if klass.superclass == ActiveRecord::Base
512
+ end
513
+ end
514
+ end
515
+
516
+ private
517
+
518
+ def fmt_f flt, label, color
519
+ padded_to_max(label) << " " << (("%0.2f"% flt) << '%').send(color)
520
+ end
521
+
522
+ def fmt_i int, label, color
523
+ "%s #{label}"% [("%02i"% int).send(color), label]
524
+ end
525
+
526
+ end
527
+ end