Pratt 1.6.8-x86-linux

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 (72) hide show
  1. data/.exrc +61 -0
  2. data/.gitignore +6 -0
  3. data/History.txt +6 -0
  4. data/Manifest.txt +46 -0
  5. data/Pratt.gemspec +173 -0
  6. data/Pratt.mm +1867 -0
  7. data/README.txt +67 -0
  8. data/Rakefile +96 -0
  9. data/TODO +56 -0
  10. data/VERSION +1 -0
  11. data/bin/pratt +13 -0
  12. data/config.rb +19 -0
  13. data/db/sqlite_databases_go_here +0 -0
  14. data/db/zips.csv.zip +0 -0
  15. data/lib/models.rb +8 -0
  16. data/lib/pratt.rb +308 -0
  17. data/lib/pratt/core_ext.rb +6 -0
  18. data/lib/pratt/core_ext/array.rb +20 -0
  19. data/lib/pratt/core_ext/float.rb +29 -0
  20. data/lib/pratt/core_ext/nil.rb +5 -0
  21. data/lib/pratt/core_ext/numeric.rb +9 -0
  22. data/lib/pratt/core_ext/string.rb +31 -0
  23. data/lib/pratt/core_ext/time.rb +20 -0
  24. data/lib/pratt/dialogs.rb +81 -0
  25. data/lib/pratt/formatting.rb +19 -0
  26. data/lib/pratt/project_actions.rb +25 -0
  27. data/lib/pratt/reports.rb +140 -0
  28. data/models/app.rb +39 -0
  29. data/models/customer.rb +50 -0
  30. data/models/invoice.rb +28 -0
  31. data/models/invoice_whence.rb +18 -0
  32. data/models/payment.rb +22 -0
  33. data/models/pratt.rb +15 -0
  34. data/models/project.rb +89 -0
  35. data/models/whence.rb +77 -0
  36. data/models/zip.rb +27 -0
  37. data/pratt.mm +1875 -0
  38. data/spec/app_spec.rb +48 -0
  39. data/spec/array_spec.rb +24 -0
  40. data/spec/customer_spec.rb +31 -0
  41. data/spec/fixtures/empty_graph.expectation +8 -0
  42. data/spec/fixtures/graph.expectation +13 -0
  43. data/spec/fixtures/proportions.expectation +4 -0
  44. data/spec/float_spec.rb +24 -0
  45. data/spec/formatting_spec.rb +7 -0
  46. data/spec/money_spec.rb +9 -0
  47. data/spec/nil_class_spec.rb +8 -0
  48. data/spec/numeric_spec.rb +30 -0
  49. data/spec/payment_spec.rb +19 -0
  50. data/spec/pratt_spec.rb +106 -0
  51. data/spec/project_spec.rb +182 -0
  52. data/spec/rcov.opts +0 -0
  53. data/spec/report_action_spec.rb +83 -0
  54. data/spec/report_spec.rb +205 -0
  55. data/spec/seed_data.rb +33 -0
  56. data/spec/spec.opts +1 -0
  57. data/spec/spec_helper.rb +24 -0
  58. data/spec/string_ext_spec.rb +33 -0
  59. data/spec/whence_spec.rb +54 -0
  60. data/tasks/pratt.rb +87 -0
  61. data/templates/model.eruby +12 -0
  62. data/templates/spec.eruby +8 -0
  63. data/views/current.eruby +5 -0
  64. data/views/general-invoice.eruby +538 -0
  65. data/views/graph.eruby +16 -0
  66. data/views/invoice.eruby +148 -0
  67. data/views/main.rb +90 -0
  68. data/views/pid.eruby +3 -0
  69. data/views/pop.rb +78 -0
  70. data/views/proportions.eruby +4 -0
  71. data/views/raw.eruby +11 -0
  72. metadata +275 -0
@@ -0,0 +1,6 @@
1
+ require 'lib/pratt/core_ext/array'
2
+ require 'lib/pratt/core_ext/float'
3
+ require 'lib/pratt/core_ext/nil'
4
+ require 'lib/pratt/core_ext/numeric'
5
+ require 'lib/pratt/core_ext/string'
6
+ require 'lib/pratt/core_ext/time'
@@ -0,0 +1,20 @@
1
+ class Pratt
2
+ class Formats
3
+ module Array
4
+ ##
5
+ # Turns an array into a sentence
6
+ #
7
+ # @param [String] conjunction
8
+ # @return [String]
9
+ def to_sentence conjunction = 'and'
10
+ if self.size >= 2
11
+ self[0..-2].join(", ") << (self.size > 2 ? ',' : '') << " #{conjunction} #{self.last}"
12
+ elsif self.size <= 1
13
+ self.first.to_s
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ Array.send :include, Pratt::Formats::Array
@@ -0,0 +1,29 @@
1
+ class Float
2
+ def pretty_print
3
+ "%0.2f"% self
4
+ end
5
+
6
+ def percentage of_total = 1
7
+ ( (self/of_total)*100.0 ).pretty_print << "%"
8
+ end
9
+
10
+ def to_money
11
+ Money.new(self)
12
+ end
13
+ end
14
+
15
+ class Money
16
+ include Comparable
17
+
18
+ def <=> other
19
+ @f <=> other
20
+ end
21
+
22
+ def initialize f
23
+ @f = Float(f)
24
+ end
25
+
26
+ def pretty_print
27
+ "$#{@f.pretty_print}"
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ class NilClass
2
+ def pretty_print
3
+ ''
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class Numeric
2
+ def format_integer
3
+ colored_integer = ("%02i"% self)
4
+ end
5
+
6
+ def with_label label
7
+ self.to_s.with_label label
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ class Pratt
2
+ module ColorString
3
+ def or format_alternative
4
+ return self unless Pratt.color?
5
+ format_alternative
6
+ end
7
+ end
8
+
9
+ module Colored
10
+ def colorize(string, options = {})
11
+ return self unless Pratt.color?
12
+ super
13
+ end
14
+ end
15
+ end
16
+
17
+ String.send :include, Pratt::ColorString
18
+ String.send :include, Pratt::Colored
19
+
20
+ class String
21
+ def with_label label
22
+ "#{padded_to_max(label)} #{self}"
23
+ end
24
+
25
+ private
26
+ # Pad the output string to the maximum Project name
27
+ def padded_to_max string
28
+ project_padding = Project.longest_project_name
29
+ "%#{project_padding}.#{project_padding}s"% string
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ class Date
2
+ ##
3
+ # Return "zero hour" of the first day of the, possibly shifted, week.
4
+ #
5
+ # @return [Date]
6
+ def beginning_of_week
7
+ (self - wday_offset).beginning_of_day
8
+ end
9
+
10
+ ##
11
+ # Return "last second" of the last day of the, possibly shifted, week.
12
+ #
13
+ # @return [Date]
14
+ def end_of_week
15
+ (beginning_of_week+6).end_of_day
16
+ end
17
+ end
18
+
19
+ class DateTime < Date
20
+ end
@@ -0,0 +1,81 @@
1
+ class Pratt
2
+ def daemonized?
3
+ !app.pid.blank? and ( cpid.to_i == app.pid )
4
+ end
5
+
6
+ def daemonize!
7
+ defork do
8
+ puts "pratt (#{Process.pid.to_s.yellow})"
9
+ app.pid = Process.pid
10
+ app.save!
11
+
12
+ while daemonized?
13
+ gui
14
+ sleep(app.interval)
15
+ end
16
+ quit
17
+ end
18
+ end
19
+
20
+ def gui
21
+ if Whence.last_unended
22
+ pop
23
+ else
24
+ main
25
+ end
26
+ end
27
+
28
+ def detect
29
+ if self.daemonized?
30
+ gui
31
+ else
32
+ daemonize!
33
+ end
34
+ end
35
+
36
+ private
37
+ def main
38
+ reload_and_detect_lock 'main'
39
+ projects = ([Project.primary, Project.off] | Project.rest).collect(&:name)
40
+ if Whence.count == 0
41
+ # first run
42
+ project = Whence.new(:project => Project.new)
43
+ current_project_name = project.project.name
44
+ else
45
+ project = Whence.last_unended || Whence.last
46
+ current_project_name = ''
47
+ end
48
+
49
+ if Project.count > 0
50
+ projects = ([Project.primary, Project.off] | Project.rest).compact.collect(&:name)
51
+ else
52
+ projects = []
53
+ end
54
+ defork {
55
+ command = "ruby views/main.rb --projects '#{projects*"','"}' --current '#{current_project_name}'"
56
+ system command
57
+ }
58
+ end
59
+
60
+ def pop
61
+ reload_and_detect_lock 'pop'
62
+ self.project = Whence.last_unended.project
63
+ project_time = project.formatted_time_spent_totals( project.time_spent(scale, when_to) )
64
+ defork do
65
+ command = "ruby views/pop.rb --project '#{project.name}' --start '#{project.whences.last_unended.start_at}' --project_time '#{project_time}'"
66
+ system command
67
+ end
68
+ end
69
+
70
+ def reload_and_detect_lock of
71
+ self.app.reload
72
+ return if self.app.gui? of
73
+ self.app.log of
74
+ end
75
+
76
+ def defork &block
77
+ Process.detach(
78
+ fork &block
79
+ )
80
+ end
81
+ end
@@ -0,0 +1,19 @@
1
+ class Pratt
2
+
3
+ @@color = true
4
+
5
+ class << self
6
+
7
+ def color
8
+ @@color
9
+ end
10
+
11
+ def color= color_bool
12
+ @@color = color_bool
13
+ end
14
+
15
+ def color?
16
+ @@color == true
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ class Pratt
2
+ def begin
3
+ self.project.start! when_to
4
+ end
5
+ def restart
6
+ if project?
7
+ project.restart! when_to
8
+ else
9
+ Whence.last_unended.project.restart! when_to
10
+ end
11
+ end
12
+ def end
13
+ if project?
14
+ project.stop! when_to
15
+ else
16
+ Whence.last_unended.stop! when_to
17
+ end
18
+ end
19
+ def change
20
+ Whence.last.change! project.name
21
+ end
22
+ def destroy
23
+ project.destroy
24
+ end
25
+ end
@@ -0,0 +1,140 @@
1
+ class Pratt
2
+
3
+ # TODO Rename
4
+ def graph
5
+ self.template = 'graph'
6
+
7
+ if project?
8
+ @projects = [ project ]
9
+ else
10
+ @projects = Project.all
11
+ end
12
+
13
+ process_template!
14
+ end
15
+
16
+ def proportions
17
+ @primary = @off_total = @rest_total = 0.0
18
+ self.template = 'proportions'
19
+
20
+ if project?
21
+ @projects = [project]
22
+
23
+ @primary = project.time_spent(scale, when_to)
24
+ @scaled_total = project.time_spent(scale, when_to)
25
+ else
26
+ @projects = Project.all
27
+
28
+ @projects.each do |proj|
29
+ @primary = proj.time_spent(scale, when_to) if proj.name == Project.primary.name
30
+ @off_total = proj.time_spent(scale, when_to) if proj.name == Project.off.name
31
+ @rest_total += proj.time_spent(scale, when_to) if Project.rest.collect(&:name).include?(proj.name)
32
+ end
33
+ @scaled_total = Whence.time_spent(scale, when_to) - @off_total
34
+ end
35
+
36
+ if @primary + @off_total + @rest_total > 0.0
37
+ process_template!
38
+ else
39
+ $stdout.puts "No data to report"
40
+ end
41
+ end
42
+
43
+ # Generate an invoice for a given time period
44
+ def invoice
45
+ self.template ||= 'invoice'
46
+
47
+ if project?
48
+ @projects = [project]
49
+
50
+ @total = project.amount( project.time_spent(scale, when_to) )
51
+ else
52
+ @projects = (Project.all - [Project.primary, Project.off])
53
+ @projects = @projects.select {|proj| show_all or ( !show_all and proj.time_spent(scale, when_to) != 0.0 ) }
54
+
55
+ @total = @projects.inject 0.0 do |total, proj|
56
+ total += proj.amount( proj.time_spent(scale, when_to) )
57
+ total
58
+ end
59
+ end
60
+
61
+ @projects.each do |project|
62
+ puts "<!-- #{project.name} #{project.payment.inspect} -->"
63
+ end
64
+ if @total > 0.0
65
+ process_template!
66
+ else
67
+ $stdout.puts "No data to report in Pratt#invoice"
68
+ end
69
+ end
70
+
71
+ def current
72
+ self.template = 'current'
73
+ @last_whence = Whence.last_unended || Whence.last || Whence.new( :end_at => nil, :project => Project.new )
74
+
75
+ projects = ([Project.primary, Project.off] | Project.rest).compact
76
+ @project_names = projects.collect do |proj|
77
+
78
+ if @last_whence.end_at.nil? && @last_whence.project.name == proj.name
79
+ colored_name = proj.name.green
80
+ else
81
+ colored_name = proj.name.magenta
82
+ end
83
+ "'" << colored_name << "'"
84
+ end
85
+ @time_til = ( app.interval - ( Time.now - @last_whence.start_at ) ) if @last_whence.end_at.nil?
86
+
87
+ process_template!
88
+ end
89
+
90
+ def raw
91
+ self.template = 'raw'
92
+
93
+ if project?
94
+ @whences = project.whences.all
95
+ else
96
+ case raw_conditions
97
+ when 'all'
98
+ @whences = Whence.find raw_conditions.to_sym
99
+ when /^last$/, 'first'
100
+ @whences = [Whence.find raw_conditions.to_sym]
101
+ when /last[\(\s]?(\d+)[\)\s]?/
102
+ @whences = Whence.all.last($1.to_i)
103
+ else
104
+ @whences = Whence.all :conditions => ["start_at > ?", Chronic.parse("today 00:00")]
105
+ end
106
+ end
107
+ @whences.sort_by(&:id)
108
+ process_template!
109
+ end
110
+
111
+ def cpid
112
+ rubies = %x(ps -C ruby -o pid=,cmd=)
113
+ pratts = rubies.select do |ruby_process|
114
+ pid, interp, cmd, args = ruby_process.chomp.split(' ')
115
+ cmd =~ /pratt/ && args == '-d'
116
+ end
117
+ return '' unless pratts.size == 1
118
+ pratts.first.split(' ').first
119
+ end
120
+
121
+ def pid
122
+ self.template = 'pid'
123
+ process_template!
124
+ end
125
+
126
+ private
127
+ def output
128
+ # for testing
129
+ @__output
130
+ end
131
+
132
+ def process_template!
133
+ input = File.open(Pratt.root("views", "#{template}.eruby").first).read
134
+ erubis = Erubis::Eruby.new(input)
135
+ @__output = erubis.evaluate(self)
136
+ $stdout.puts @__output
137
+ nil
138
+ end
139
+
140
+ end
@@ -0,0 +1,39 @@
1
+ class App < ActiveRecord::Base
2
+ has_many :logs
3
+
4
+ def log which, overwrite = false
5
+ self.gui = which if (overwrite and gui?(which) ) || gui?('')
6
+ save!
7
+ end
8
+
9
+ def gui? which = ''
10
+ match = (self.gui == which)
11
+ match
12
+ end
13
+
14
+ def unlock
15
+ self.gui = ''
16
+ self.save!
17
+ end
18
+
19
+ def interval= intvl
20
+ write_attribute(:interval, intvl*60)
21
+ end
22
+
23
+ class << self
24
+ def migrate which = :up
25
+ ActiveRecord::Schema.define do
26
+ if which == :up
27
+ create_table :apps do |t|
28
+ t.integer :pid
29
+ t.string :gui, :default => ''
30
+ t.float :interval, :default => 15.0*60
31
+ end
32
+ App.create!(:gui => '', :interval => 15.0)
33
+ elsif which == :down
34
+ drop_table :apps
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,50 @@
1
+ class Customer < ActiveRecord::Base
2
+ has_many :projects
3
+ has_many :invoices
4
+ has_one :payment, :as => :billable
5
+
6
+ validates_presence_of :name
7
+
8
+ def amount
9
+ payment.rate / 100.0
10
+ end
11
+
12
+ def phone
13
+ phone = read_attribute(:phone)
14
+ class << phone
15
+ def pretty_print sep = '.'
16
+ if self.blank?
17
+ ""
18
+ else
19
+ self.split(/(\d{3})(\d{3})(\d{4})/)[1,3] * sep
20
+ end
21
+ end
22
+ end
23
+ phone
24
+ end
25
+
26
+ def city_state_zip
27
+ a_zip = Zip.find_by_zip(self.zip)
28
+ return "#{a_zip.city}, #{a_zip.ST} #{a_zip.zip}" if a_zip
29
+ return self.zip
30
+ end
31
+
32
+ class << self
33
+ def migrate which = :up
34
+ ActiveRecord::Schema.define do
35
+ if which == :up
36
+ create_table :customers do |t|
37
+ t.string :name, :null => false
38
+ t.string :company_name
39
+ t.string :address
40
+ t.string :phone
41
+ t.string :email
42
+ t.string :zip, :limit => 5
43
+ end
44
+ elsif which == :down
45
+ drop_table :customers
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end