timesheets 1.1.1 → 1.2.0

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