Pratt 1.6.2 → 1.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +2 -0
  2. data/Pratt.gemspec +38 -12
  3. data/README.html +85 -0
  4. data/README.txt +6 -3
  5. data/TODO +6 -3
  6. data/VERSION +1 -1
  7. data/config.rb +3 -17
  8. data/db/zips.csv.zip +0 -0
  9. data/lib/models.rb +8 -0
  10. data/lib/pratt.rb +58 -293
  11. data/lib/pratt.rb.orig +626 -0
  12. data/lib/pratt/core_ext.rb +6 -0
  13. data/lib/pratt/{array.rb → core_ext/array.rb} +5 -0
  14. data/lib/pratt/core_ext/float.rb +28 -0
  15. data/lib/pratt/core_ext/nil.rb +5 -0
  16. data/lib/pratt/core_ext/numeric.rb +9 -0
  17. data/lib/pratt/{string.rb → core_ext/string.rb} +13 -0
  18. data/lib/pratt/core_ext/time.rb +20 -0
  19. data/lib/pratt/dialogs.rb +81 -0
  20. data/lib/pratt/formatting.rb +24 -0
  21. data/lib/pratt/project_actions.rb +25 -0
  22. data/lib/pratt/reports.rb +127 -0
  23. data/models/app.rb +1 -2
  24. data/models/customer.rb +22 -0
  25. data/models/invoice.rb +28 -0
  26. data/models/invoice_whence.rb +18 -0
  27. data/models/payment.rb +1 -1
  28. data/models/pratt.rb +0 -9
  29. data/models/project.rb +16 -22
  30. data/models/whence.rb +10 -1
  31. data/models/zip.rb +27 -0
  32. data/spec/fixtures/graph.expectation +0 -5
  33. data/spec/fixtures/proportions.expectation +4 -0
  34. data/spec/float_spec.rb +24 -0
  35. data/spec/numeric_spec.rb +30 -0
  36. data/spec/pratt_spec.rb +5 -4
  37. data/spec/project_spec.rb +8 -2
  38. data/spec/spec.opts +1 -1
  39. data/spec/spec_helper.rb +31 -1
  40. data/spec/string_ext_spec.rb +33 -0
  41. data/views/current.eruby +5 -0
  42. data/views/general-invoice.eruby +538 -0
  43. data/views/graph.eruby +2 -7
  44. data/views/invoice.eruby +3 -3
  45. data/views/main.rb +8 -6
  46. data/views/proportions.eruby +4 -0
  47. data/views/raw.eruby +1 -1
  48. metadata +86 -33
  49. data/lib/pratt/time.rb +0 -12
  50. data/views/env.rb +0 -22
@@ -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'
@@ -1,6 +1,11 @@
1
1
  class Pratt
2
2
  class Formats
3
3
  module Array
4
+ ##
5
+ # Turns an array into a sentence
6
+ #
7
+ # @param [String] conjunction
8
+ # @return [String]
4
9
  def to_sentence conjunction = 'and'
5
10
  self[0..-2].join(", ") << (self.size > 2 ? ',' : '') << " #{conjunction} #{self.last}"
6
11
  end
@@ -0,0 +1,28 @@
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
+ def pretty_print
26
+ "$#{@f.pretty_print}"
27
+ end
28
+ 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
@@ -16,3 +16,16 @@ end
16
16
 
17
17
  String.send :include, Pratt::ColorString
18
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 {
8
+ puts "pratt (#{Process.pid.to_s.yellow})"
9
+ app.pid = Process.pid
10
+ app.save!
11
+
12
+ gui
13
+ while(daemonized?)
14
+ sleep(app.interval)
15
+ gui
16
+ end
17
+ quit
18
+ }
19
+ end
20
+
21
+ def gui
22
+ if Whence.last_unended
23
+ pop
24
+ else
25
+ main
26
+ end
27
+ end
28
+
29
+ def detect
30
+ if self.daemonized?
31
+ gui
32
+ else
33
+ daemonize!
34
+ end
35
+ end
36
+
37
+ private
38
+ def main
39
+ reload_and_detect_lock 'main'
40
+ projects = ([Project.primary, Project.off] | Project.rest).collect(&:name)
41
+ if Whence.count == 0
42
+ # first run
43
+ project = Whence.new(:project => Project.new)
44
+ current_project_name = project.project.name
45
+ else
46
+ project = Whence.last_unended || Whence.last
47
+ current_project_name = ''
48
+ end
49
+
50
+ if Project.count > 0
51
+ projects = ([Project.primary, Project.off] | Project.rest).compact.collect(&:name)
52
+ else
53
+ projects = []
54
+ end
55
+ defork {
56
+ command = "ruby views/main.rb --projects '#{projects*"','"}' --current '#{current_project_name}'"
57
+ system command
58
+ }
59
+ end
60
+
61
+ def pop
62
+ reload_and_detect_lock 'pop'
63
+ self.project = Whence.last_unended.project
64
+ defork do
65
+ command = "ruby views/pop.rb --project '#{project.name}' --start '#{project.whences.last_unended.start_at}' --project_time '#{Pratt.totals(project.time_spent)}'"
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,24 @@
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
+
19
+ # Calculate totals. I think this should be an instance method on Projects/?/Whences
20
+ def totals hr, fmt = false
21
+ "#{(hr / 24).format_integer.cyan} day #{(hr % 24).format_integer.yellow} hour #{(60*(hr -= hr.to_i)).format_integer.green} min"
22
+ end
23
+ end
24
+ 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,127 @@
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.whences.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
+ "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(scale, when_to)
51
+ else
52
+ @projects = (Project.all - [Project.primary, Project.off])
53
+ @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(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
+ puts "No data to report"
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
+ `pgrep -fl 'bin/pratt'`.chomp.split(' ').first
113
+ end
114
+
115
+ def pid
116
+ self.template = 'pid'
117
+ process_template!
118
+ end
119
+
120
+ private
121
+ def process_template!
122
+ input = File.open(Pratt.root("views", "#{template}.eruby").first).read
123
+ erubis = Erubis::Eruby.new(input)
124
+ puts erubis.evaluate(self)
125
+ end
126
+
127
+ end
@@ -6,9 +6,8 @@ class App < ActiveRecord::Base
6
6
  save!
7
7
  end
8
8
 
9
- def gui? which = '', log_err = false
9
+ def gui? which = ''
10
10
  match = (self.gui == which)
11
- # $stderr.write("#{which} already being displayed\n") if log_err
12
11
  match
13
12
  end
14
13
 
@@ -1,10 +1,29 @@
1
1
  class Customer < ActiveRecord::Base
2
2
  has_many :projects
3
+ has_many :invoices
3
4
  has_one :payment, :as => :billable
4
5
 
6
+ validates_presence_of :name
7
+
5
8
  def amount
6
9
  payment.rate / 100.0
7
10
  end
11
+
12
+ def phone
13
+ phone = read_attribute(:phone)
14
+ class << phone
15
+ def pretty_print sep = '.'
16
+ self.split(/(\d{3})(\d{3})(\d{4})/)[1,3] * sep
17
+ end
18
+ end
19
+ phone
20
+ end
21
+
22
+ def city_state_zip
23
+ a_zip = Zip.find_by_zip(self.zip)
24
+ return "#{a_zip.city}, #{a_zip.ST} #{a_zip.zip}" if a_zip
25
+ return self.zip
26
+ end
8
27
 
9
28
  class << self
10
29
  def migrate which = :up
@@ -12,7 +31,10 @@ class Customer < ActiveRecord::Base
12
31
  if which == :up
13
32
  create_table :customers do |t|
14
33
  t.string :name, :null => false
34
+ t.string :company_name
15
35
  t.string :address
36
+ t.string :phone
37
+ t.string :email
16
38
  t.string :zip, :limit => 5
17
39
  end
18
40
  elsif which == :down
@@ -0,0 +1,28 @@
1
+ class Invoice < ActiveRecord::Base
2
+ belongs_to :customer
3
+ has_many :invoice_whences
4
+ has_many :whences, :through => :invoice_whences
5
+
6
+ validates_presence_of :customer_id, :weekday_start
7
+ validates_format_of :invoice_number, :with => /(0?[1-9]+[0-9]*)-([0-4][0-9]|5[0-4])-(0?[1-9]+[0-9]*)/, :on => :create
8
+
9
+ def before_validation_on_create
10
+ self.invoice_number = "%s-%s-%s"% [customer.id, weekday_start.week, customer.invoices.count+1 ]
11
+ end
12
+
13
+ def self.migrate which = :up
14
+ ActiveRecord::Schema.define do
15
+ if which == :up
16
+ create_table :invoices do |t|
17
+ t.references :customer
18
+ t.datetime :weekday_start
19
+ t.datetime :weekday_end
20
+ t.string 'invoice_number'
21
+ t.integer :total
22
+ end
23
+ elsif which == :down
24
+ drop_table :invoices
25
+ end
26
+ end
27
+ end
28
+ end