timesheets 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c601f6f68e11d6e01651251bf92e0a844ccb7e16
4
- data.tar.gz: 8a3d7a5507cafa7f20aefa320a0e603bf850e8ed
3
+ metadata.gz: c632d977a47f14254837ce09b33af73f1e61725d
4
+ data.tar.gz: 427c2f8e184c21b261b5ebc4051f6e50ee7551f4
5
5
  SHA512:
6
- metadata.gz: 88caf9a6424459274e5a36d11ffc8de76350e9f5776f8a6eebdcf6408921a47fa74d6bd294cc1f6177aa8ca7876bdcbb66ee2f072d81b3bd5207c0b3e1a2f458
7
- data.tar.gz: e1220935adb1cda7db7ec0d5474f3e70bf66ab5f7ec5fb2d5cfa265c1c0d762a92bff67f789821c27546ac0c737060fcde284e3b6acd4593252493d697482ea2
6
+ metadata.gz: 90c5365557a0560e2b07d40e576c75da15b59e6898a8f36c27c5468a0f07fffac3e34c21afe3fe2738e1c65abfad25cb15157b2207da63965876b49ec674a8c2
7
+ data.tar.gz: d87d8e382e56c7f56383a386e201c3b893df17153a3cced8864445224c712436c09537f55b3c484bb8541be06027fed1ec88dbb307d00a9ee0aad26a3d572790
@@ -0,0 +1,7 @@
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ insert_final_newline = true
6
+ indent_style = spaces
7
+ indent_size = 2
@@ -7,6 +7,8 @@ require "timesheets/commands/edit"
7
7
  require "timesheets/commands/start"
8
8
  require "timesheets/commands/status"
9
9
  require "timesheets/commands/stop"
10
+ require 'timesheets/html_table'
11
+ require 'timesheets/csv_table'
10
12
  require "timesheets/commands/summary"
11
13
 
12
14
  module Timesheets
@@ -32,8 +34,12 @@ module Timesheets
32
34
  end
33
35
 
34
36
  desc "summary", "See a summary table of time worked"
37
+ option :format
38
+ option :'week-of'
39
+ option :rate
40
+ option :template
35
41
  def summary
36
- Commands::Summary.run
42
+ Commands::Summary.run(options)
37
43
  end
38
44
  end
39
45
  end
@@ -1,8 +1,14 @@
1
1
  module Timesheets
2
2
  module Commands
3
3
  class Base
4
- def self.run
5
- new.run
4
+ attr_accessor :options
5
+
6
+ def self.run(options = {})
7
+ new(options).run
8
+ end
9
+
10
+ def initialize(options = {})
11
+ @options = options
6
12
  end
7
13
 
8
14
  private
@@ -7,29 +7,60 @@ module Timesheets
7
7
 
8
8
  private
9
9
 
10
+ def tableClass
11
+ {
12
+ 'html' => Timesheets::HTMLTable,
13
+ 'csv' => Timesheets::CSVTable,
14
+ }[options[:format]] || Terminal::Table
15
+ end
16
+
17
+ def table_renderer(&block)
18
+ tableClass.new({ headings: heading, template: options[:template] }, &block)
19
+ end
20
+
10
21
  def summary_table
11
- Terminal::Table.new(headings: heading) {|t|
22
+ table_renderer {|t|
12
23
  entries_by_week.each_with_index {|entries, index|
13
- format_entries(entries).each {|entry| t << entry }
14
- t << :separator
15
- t << (heading.length - 2).times.map { '' } + ['Weekly Total:', sprintf('%0.02f', hours_in_entries(entries))]
16
- t << :separator
17
- }
18
-
19
- t << (heading.length - 2).times.map { '' } + ['Total:', sprintf('%0.02f', total_hours)]
24
+ rows_for_entries(entries).each {|row| t << row }
25
+ t << :separator unless index == entries_by_week.length - 1
26
+ }
27
+ t << total_row(entries) unless options['week-of']
20
28
 
21
29
  heading.length.times {|i| t.align_column(i, :right) }
22
30
  }
23
31
  end
24
32
 
33
+ def rows_for_entries(entries)
34
+ format_entries(entries) + [
35
+ :separator,
36
+ total_row(entries)
37
+ ]
38
+ end
39
+
25
40
  def heading
26
- ['Weekday', 'Date', 'Time', 'Hour(s)']
41
+ the_heading = ['Weekday', 'Date', 'Time', 'Hour(s)']
42
+
43
+ the_heading += ['Hourly Rate', 'Total']
44
+
45
+ the_heading
27
46
  end
28
47
 
29
- def total_hours
30
- hours_in_entries(entries)
48
+ def total_row(entries)
49
+ padN = heading.length - 2
50
+ padN -= 2 if options[:rate]
51
+ the_row = padN.times.map { '' } + ['Total:', sprintf('%0.02f', hours_in_entries(entries))]
52
+
53
+ if options[:rate]
54
+ the_row += ['', "$#{comma_numbers(hours_in_entries(entries).to_f * options[:rate].to_f)}"]
55
+ end
56
+
57
+ the_row
31
58
  end
32
59
 
60
+ def comma_numbers(number, delimiter = ',')
61
+ sprintf('%0.02f', number).reverse.gsub(%r{([0-9]{3}(?=([0-9])))}, "\\1#{delimiter}").reverse
62
+ end
63
+
33
64
  def hours_in_entries(the_entries)
34
65
  the_entries.map {|entry| hours_in_entry(entry) }.reduce(:+)
35
66
  end
@@ -38,20 +69,46 @@ module Timesheets
38
69
  @entries_by_week ||= entries.group_by {|entry| entry.first.strftime('%U') }.values
39
70
  end
40
71
 
41
- def format_entries(entries)
72
+ def entries
73
+ the_entries = super
74
+
75
+ if options['week-of']
76
+ week_num = DateTime.parse(options['week-of']).strftime("%U")
77
+ the_entries = the_entries.select {|entry| entry.first.strftime("%U") == week_num }
78
+ end
79
+
80
+ the_entries
81
+ end
82
+
83
+ def entries_by_day(entries)
42
84
  entries.group_by {|entry|
43
- entry.first.strftime('%B %e, %Y')
44
- }.map {|day, entries|
45
- [
85
+ entry.first.strftime('%B %e, %Y')
86
+ }
87
+ end
88
+
89
+ def times_in_entry(entry)
90
+ entry.map {|time| time.strftime('%l:%M%p') }.join(' - ')
91
+ end
92
+
93
+ def times_for_entries(entries)
94
+ entries.map {|entry| times_in_entry(entry) }.join(', ')
95
+ end
96
+
97
+ def format_entries(entries)
98
+ entries_by_day(entries).map {|day, entries|
99
+ entry = [
46
100
  entries.first.first.strftime('%A'),
47
101
  day,
48
- entries.map {|entry|
49
- entry.map {|time|
50
- time.strftime('%l:%M%p')
51
- }.join(' - ')
52
- }.join(', '),
53
- sprintf('%0.02f', entries.map {|entry| hours_in_entry(entry) }.reduce(:+))
54
- ].flatten
102
+ times_for_entries(entries),
103
+ sprintf('%0.02f', hours_in_entries(entries))
104
+ ].flatten
105
+
106
+ if options[:rate]
107
+ entry << sprintf('$%0.02f', options[:rate])
108
+ entry << sprintf('$%0.02f', hours_in_entries(entries).to_f * options[:rate].to_f)
109
+ end
110
+
111
+ entry
55
112
  }
56
113
  end
57
114
  end
@@ -0,0 +1,20 @@
1
+ module Timesheets
2
+ class CSVTable
3
+ def initialize(options, &block)
4
+ @heading = options[:headings]
5
+ @rows = []
6
+ block.call(self)
7
+ end
8
+
9
+ def <<(row)
10
+ @rows << row unless row == :separator
11
+ end
12
+
13
+ def align_column(n, alignment)
14
+ end
15
+
16
+ def to_s
17
+ ([@heading] + @rows).map {|row| row.map {|cell| "\"#{cell}\"" }.join(",") }.join("\n")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,76 @@
1
+ require 'digest'
2
+
3
+ module Timesheets
4
+ class HTMLTable
5
+ def initialize(options, &block)
6
+ @heading = options[:headings]
7
+ @template = options[:template]
8
+ @rows = []
9
+ @styles = ["table.timesheets-summary-table th { text-align: left; }"]
10
+ block.call(self)
11
+ end
12
+
13
+ def <<(row)
14
+ @rows << row
15
+ end
16
+
17
+ def align_column(n, alignment)
18
+ @styles << "table.timesheets-summary-table td:nth-child(#{n+1}) { text-align: #{alignment}; }"
19
+ end
20
+
21
+ def to_s
22
+ styles = @styles.join
23
+ table = [
24
+ "<table class='timesheets-summary-table'><thead>",
25
+ @heading.map {|cell| "<th>#{cell}</th>" },
26
+ "</thead><tbody>",
27
+ @rows.map {|row|
28
+ if row == :separator
29
+ ""
30
+ else
31
+ ["<tr>", row.map {|cell| "<td>#{cell}</td>" }.join, "</tr>"]
32
+ end
33
+ }.join,
34
+ "</tbody></table>"
35
+ ].join
36
+
37
+ template
38
+ .gsub("{{styles}}", styles)
39
+ .gsub("{{table}}", table)
40
+ .gsub("{{today}}", Time.new.strftime('%Y-%m-%d'))
41
+ .gsub("{{invoiceID}}", (Digest::SHA2.new << template << Time.new.to_i.to_s).to_s[0...8])
42
+ .gsub("{{firstDayNice}}", @rows.first[1])
43
+ end
44
+
45
+ private
46
+
47
+ def template
48
+ @template ? File.read(@template) : default_template
49
+ end
50
+
51
+ def default_template
52
+ <<-TEMPLATE
53
+ <!DOCTYPE html>
54
+ <html>
55
+ <head>
56
+ <style>
57
+ {{styles}}
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <h1>Invoice <small>for the week of {{firstDayNice}}</h1>
62
+ <p>
63
+ <strong>Date:</strong><br/>
64
+ {{today}}
65
+ </p>
66
+ <p>
67
+ <strong>Invoice ID:</strong><br/>
68
+ {{invoiceID}}
69
+ </p>
70
+ {{table}}
71
+ </body>
72
+ </html>
73
+ TEMPLATE
74
+ end
75
+ end
76
+ end
@@ -1,3 +1,3 @@
1
1
  module Timesheets
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timesheets
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bradley J. Spaulding
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-06 00:00:00.000000000 Z
11
+ date: 2014-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -88,6 +88,7 @@ executables:
88
88
  extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
+ - ".editorconfig"
91
92
  - ".gitignore"
92
93
  - Gemfile
93
94
  - LICENSE.txt
@@ -102,6 +103,8 @@ files:
102
103
  - lib/timesheets/commands/status.rb
103
104
  - lib/timesheets/commands/stop.rb
104
105
  - lib/timesheets/commands/summary.rb
106
+ - lib/timesheets/csv_table.rb
107
+ - lib/timesheets/html_table.rb
105
108
  - lib/timesheets/version.rb
106
109
  - spec/spec_helper.rb
107
110
  - timesheets.gemspec