Pratt 1.6.2 → 1.6.4

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 (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