Class: PunchCard

Inherits:
Object
  • Object
show all
Defined in:
lib/punchcard.rb

Constant Summary collapse

SETTINGS_DIR =
ENV['PUNCHCARD_DIR'] || File.expand_path('~/.punchcard')
HOURLY_RATE_PATTERN =
/^\s*(\d+)([^\d]+)*\s*/i.freeze
TIME_POINT_PATTERN =
/^((\d+|.+?\s[\+\-]\d{4}?\s*)(\-)*(\d+|\s.+\d?)*)$/.freeze
META_KEY_PATTERN =
/^([a-zA-Z0-9]+)\:\s*(.*)$/.freeze
VERSION =
'1.3.3'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_name) ⇒ PunchCard

Returns a new instance of PunchCard.



20
21
22
23
24
25
26
27
28
29
# File 'lib/punchcard.rb', line 20

def initialize(project_name)
  @wilcard_for_filename = ''
  @meta_data            = {}
  find_or_make_settings_dir
  return unless project_name

  self.project = project_name
  find_or_make_file
  read_project_data
end

Instance Attribute Details

#titleObject

Returns the value of attribute title.



18
19
20
# File 'lib/punchcard.rb', line 18

def title
  @title
end

Class Method Details

.decimal_digits(digit) ⇒ Object



214
215
216
217
218
219
220
# File 'lib/punchcard.rb', line 214

def self.decimal_digits(digit)
  if digit.to_i < 10
    "0#{digit}"
  else
    digit.to_s
  end
end

.format_time(datetime) ⇒ Object



201
202
203
# File 'lib/punchcard.rb', line 201

def self.format_time(datetime)
  datetime.strftime('%F %T')
end

.humanize_duration(duration) ⇒ Object



205
206
207
208
209
210
211
212
# File 'lib/punchcard.rb', line 205

def self.humanize_duration(duration)
  return nil unless duration

  hours   = duration / (60 * 60)
  minutes = (duration / 60) % 60
  seconds = duration % 60
  "#{decimal_digits(hours)}:#{decimal_digits(minutes)}:#{decimal_digits(seconds)}"
end

Instance Method Details

#csv(start_at: nil, end_at: nil) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/punchcard.rb', line 106

def csv(start_at: nil, end_at: nil)
  project_exists_or_stop!
  find_or_make_file
  durations = []
  last_activity = nil
  project_data_without_comments.map do |line|
    points = line_to_time_points(line)
    next unless points

    start_time = points[0]
    end_time   = points[1] || timestamp

    next if time_range_is_excluded_by_filter?(
      start_at: start_at,
      end_at: end_at,
      start_time: start_time,
      end_time: end_time
    )

    last_activity = points[1] || points[0]
    durations.push end_time - start_time
  end
  total_duration = self.class.humanize_duration(
    durations.reduce(&:+) || 0
  )
  '"' + [
    title_and_project,
    running_status,
    last_activity ? self.class.format_time(Time.at(last_activity).to_datetime) : '',
    total_duration,
    hourly_rate ? hourly_rate[:hourlyRate].to_s + " #{hourly_rate[:currency]}" : '',
    hourly_rate ? (hourly_rate[:hourlyRate] * total / 3600.0).round(2).to_s + " #{hourly_rate[:currency]}" : ''
  ].join('","') + '"'
end

#detailsObject



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/punchcard.rb', line 85

def details
  project_exists_or_stop!
  find_or_make_file
  output = []
  data = project_data
  data[0] = "#{data[0]} (#{running_status})"
  data.map do |line|
    points = line_to_time_points(line)
    unless points
      output << line + "\n"
      next
    end

    starttime = points[0]
    endtime   = points[1] || timestamp
    output << duration(starttime, endtime) + "\t" + self.class.format_time(Time.at(starttime)) + ' - ' + self.class.format_time(Time.at(endtime))
  end
  output << "========\n#{humanized_total}\t(total)"
  output.join("\n")
end

#projectObject



166
167
168
# File 'lib/punchcard.rb', line 166

def project
  @project.strip
end

#project=(project_name) ⇒ Object



157
158
159
160
161
162
163
164
# File 'lib/punchcard.rb', line 157

def project=(project_name)
  @project = project_name
  if @project.end_with?('*')
    @wilcard_for_filename = '*'
    @project              = @project.chomp('*')
  end
  @project.strip
end

#removeObject



141
142
143
144
145
146
# File 'lib/punchcard.rb', line 141

def remove
  if File.exist?(project_file)
    File.delete(project_file)
    "Deleted #{project_file}"
  end
end

#rename(new_project_name) ⇒ Object



148
149
150
151
152
153
154
155
# File 'lib/punchcard.rb', line 148

def rename(new_project_name)
  old_filename = project_filename
  data         = project_data
  data[0]      = new_project_name
  write_string_to_project_file! data.join("\n")
  self.project = new_project_name
  File.rename(old_filename, project_filename) && "#{old_filename} -> #{project_filename}"
end

#set(key, value) ⇒ Object



170
171
172
173
174
175
176
177
178
# File 'lib/punchcard.rb', line 170

def set(key, value)
  unless key =~ /^[a-zA-Z0-9]+$/
    raise PunchCardError, "Key '#{key}' can only be alphanumeric"
  end

  @meta_data[key.to_sym] = value
  write_to_project_file!
  @meta_data
end

#startObject



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/punchcard.rb', line 31

def start
  output = []
  if start_time && !end_time
    output << "'#{title_or_project}' already started (#{humanized_total} total)"
    output << duration(start_time, timestamp).to_s
  else
    output << "'#{title_or_project}' started (#{humanized_total} total)"
    self.start_time = timestamp
  end
  output.join("\n")
end

#statusObject



76
77
78
79
80
81
82
83
# File 'lib/punchcard.rb', line 76

def status
  project_exists_or_stop!
  find_or_make_file
  output = []
  output << (title_or_project + " (#{running_status})\n")
  output << humanized_total
  output.join("\n")
end

#stopObject



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/punchcard.rb', line 43

def stop
  output = []
  if end_time
    output << "'#{title_or_project}' already stopped (#{humanized_total} total)"
  elsif start_time
    output << "'#{title_or_project}' stopped (#{humanized_total} total)"
    self.end_time = timestamp
  else
    output << 'Nothing to stop'
  end
  output.join("\n")
end

#title_and_projectObject



68
69
70
71
72
73
74
# File 'lib/punchcard.rb', line 68

def title_and_project
  if title != project
    "#{title} [#{project}]"
  else
    project
  end
end

#title_or_projectObject



64
65
66
# File 'lib/punchcard.rb', line 64

def title_or_project
  title || project
end

#toggleObject



56
57
58
59
60
61
62
# File 'lib/punchcard.rb', line 56

def toggle
  if active?
    stop
  else
    start
  end
end

#total(start_at: nil, end_at: nil) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/punchcard.rb', line 180

def total(start_at: nil, end_at: nil)
  total = 0
  project_data_without_comments.map do |line|
    points = line_to_time_points(line)
    next unless points

    start_time = points[0]
    end_time   = points[1] || timestamp

    next if time_range_is_excluded_by_filter?(
      start_at: start_at,
      end_at: end_at,
      start_time: start_time,
      end_time: end_time
    )

    total += end_time - start_time
  end
  total
end