club 0.0.1
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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README.md +46 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/club.gemspec +34 -0
- data/lib/club.rb +4 -0
- data/lib/club/version.rb +3 -0
- data/lib/controllers/time_controller.rb +101 -0
- data/lib/core_ext/array.rb +19 -0
- data/lib/entities/time_chunk.rb +62 -0
- data/vendor/.gitkeep +0 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: afd6387d5fb6099d3514b834bd4e3b16577626e6
|
4
|
+
data.tar.gz: db6cf6eef0289dfa03115675319207410b7166db
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7bdd0849ae6448f3463f9526fb2f3be9d722574fb819d8bed6a71349364b373d1973413f0d064086f10a50f4570518ab415c73d8843e2cf0df8aca1958abab79
|
7
|
+
data.tar.gz: 52b4b7523d3f8af2ad02cee95770736ecceea33d39a30d836f108b8240196e8b3d11c8b02321b2f221dd1905d8079b1b02eb5b6185fb8827cdcd0f2c2221b41b
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# CLUB -- Command-line Utility for Business
|
2
|
+
|
3
|
+
|
4
|
+
CLUB is a tool that I wrote to move the majority of the applications I use for business into the command line. Most notably, I wanted to be able to track my time from the command-line because it reduces the friction I experience when trying to track my time appropriately.
|
5
|
+
|
6
|
+
|
7
|
+
## Time Tracking
|
8
|
+
|
9
|
+
CLUB's first role is in tracking time.
|
10
|
+
|
11
|
+
### Starting a timer
|
12
|
+
|
13
|
+
|
14
|
+
Starting an unnamed timer:
|
15
|
+
|
16
|
+
`club time go`
|
17
|
+
|
18
|
+
Stopping an unnamed timer:
|
19
|
+
|
20
|
+
`club time stop`
|
21
|
+
|
22
|
+
Starting a named timer:
|
23
|
+
|
24
|
+
`club time go <name>`
|
25
|
+
|
26
|
+
Starting a timer scoped to a specific project:
|
27
|
+
|
28
|
+
`club time go --project <abbrev>`
|
29
|
+
|
30
|
+
Starting a timer with known duration:
|
31
|
+
|
32
|
+
`club time go --end 30m`
|
33
|
+
|
34
|
+
Starting a pomodoro cycle (25 on, 5 off):
|
35
|
+
|
36
|
+
`club time go --pomo`
|
37
|
+
|
38
|
+
|
39
|
+
## Starting
|
40
|
+
|
41
|
+
- TimeChunk
|
42
|
+
- Holds started_time, time_elapsed, scheduled_end_time, name, state, reference to project
|
43
|
+
- Project
|
44
|
+
- Long name, abbreviation
|
45
|
+
|
46
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "club"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/club.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'club/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "club"
|
8
|
+
spec.version = Club::VERSION
|
9
|
+
spec.authors = ["Rob Yurkowski"]
|
10
|
+
spec.email = ["rob@yurkowski.net"]
|
11
|
+
|
12
|
+
spec.summary = %q{What is this I don't even}
|
13
|
+
spec.description = %q{What is this I don't even really}
|
14
|
+
spec.homepage = "https://github.com/robyurkowski/club"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
if spec.respond_to?(:metadata)
|
22
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com' to prevent pushes to rubygems.org, or delete to allow pushes to any server."
|
23
|
+
end
|
24
|
+
|
25
|
+
# spec.add_dependency "thor"
|
26
|
+
spec.add_dependency "aasm"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "pry"
|
31
|
+
spec.add_development_dependency "pry-byebug"
|
32
|
+
spec.add_development_dependency "rspec"
|
33
|
+
|
34
|
+
end
|
data/lib/club.rb
ADDED
data/lib/club/version.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'entities/time_chunk'
|
2
|
+
require 'core_ext/array'
|
3
|
+
|
4
|
+
class NoActiveTimeChunkException < Exception; end
|
5
|
+
class AmbiguousTimeChunkException < Exception; end
|
6
|
+
class InvalidTimeChunkException < Exception; end
|
7
|
+
|
8
|
+
class TimeController
|
9
|
+
using CoreExt::Array
|
10
|
+
|
11
|
+
def initialize(timechunk_factory: TimeChunk.method(:new))
|
12
|
+
self.timechunks = []
|
13
|
+
self.active_timechunks = []
|
14
|
+
self.timechunk_factory = timechunk_factory
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
################################################################################
|
19
|
+
# Public API
|
20
|
+
################################################################################
|
21
|
+
attr_accessor :timechunks,
|
22
|
+
:active_timechunks,
|
23
|
+
:timechunk_factory
|
24
|
+
|
25
|
+
|
26
|
+
# Creates a new timechunk, starting it at the moment of creation (i.e. an
|
27
|
+
# instantly running timer).
|
28
|
+
#
|
29
|
+
# @since x.x.x
|
30
|
+
#
|
31
|
+
def start_timer
|
32
|
+
new_timechunk = timechunk_factory.call.tap(&:start)
|
33
|
+
append_active_timechunk(new_timechunk)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Finalizes a running timechunk (a 'timer').
|
38
|
+
#
|
39
|
+
# If there are no running timers, raises `NoActiveTimeChunkException`.
|
40
|
+
#
|
41
|
+
# If there is exactly one running timer, stops that timer.
|
42
|
+
#
|
43
|
+
# If there is more than one timer, and a valid id is provided for one of
|
44
|
+
# them, stops that timer. Otherwise, if the provided id is invalid, raises
|
45
|
+
# `InvalidTimeChunkException`, and if no id is provided,
|
46
|
+
# `AmbiguousTimeChunkException` is raised.
|
47
|
+
#
|
48
|
+
# @param id
|
49
|
+
# A full or partial id of an active timechunk.
|
50
|
+
#
|
51
|
+
# @since x.x.x
|
52
|
+
def stop_timer(id: nil)
|
53
|
+
case active_timechunks.count
|
54
|
+
|
55
|
+
when 0
|
56
|
+
raise NoActiveTimeChunkException
|
57
|
+
|
58
|
+
when 1
|
59
|
+
timechunk = active_timechunks.shift
|
60
|
+
timechunk.finish
|
61
|
+
|
62
|
+
else
|
63
|
+
raise AmbiguousTimeChunkException if id.nil?
|
64
|
+
timechunk = find_first_active_timechunk_by(id: id)
|
65
|
+
active_timechunks.delete(timechunk)
|
66
|
+
timechunk.finish
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Lists all active timers.
|
72
|
+
#
|
73
|
+
# @since x.x.x
|
74
|
+
def list_active_timers
|
75
|
+
active_timechunks
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
################################################################################
|
80
|
+
# Private
|
81
|
+
################################################################################
|
82
|
+
|
83
|
+
# Finds an active timechunk by its full or partial id
|
84
|
+
private def find_first_active_timechunk_by(id:)
|
85
|
+
chunks = active_timechunks.select {|tc| tc.id.to_s.include? id.to_s }
|
86
|
+
|
87
|
+
if chunks.none?
|
88
|
+
raise InvalidTimeChunkException
|
89
|
+
elsif chunks.many?
|
90
|
+
raise AmbiguousTimeChunkException
|
91
|
+
else
|
92
|
+
chunks.first
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
private def append_active_timechunk(tc)
|
98
|
+
timechunks << tc
|
99
|
+
active_timechunks << tc
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CoreExt
|
2
|
+
module Array
|
3
|
+
refine ::Array do
|
4
|
+
|
5
|
+
# Swiped directly from ActiveSupport.
|
6
|
+
def many?
|
7
|
+
cnt = 0
|
8
|
+
if block_given?
|
9
|
+
any? do |element|
|
10
|
+
cnt += 1 if yield element
|
11
|
+
cnt > 1
|
12
|
+
end
|
13
|
+
else
|
14
|
+
any? { (cnt += 1) > 1 }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'aasm'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
class TimeChunk
|
5
|
+
################################################################################
|
6
|
+
# Inclusions
|
7
|
+
################################################################################
|
8
|
+
include AASM
|
9
|
+
|
10
|
+
|
11
|
+
################################################################################
|
12
|
+
# Public Interface
|
13
|
+
################################################################################
|
14
|
+
def initialize(name: nil, scheduled_duration: nil, timestamping_method: Time.method(:now))
|
15
|
+
self.id = SecureRandom.uuid
|
16
|
+
self.name = name
|
17
|
+
self.scheduled_duration = scheduled_duration
|
18
|
+
self.timestamping_method = timestamping_method # Injectable clock
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
attr_accessor :id,
|
23
|
+
:name,
|
24
|
+
:scheduled_duration,
|
25
|
+
:timestamping_method,
|
26
|
+
|
27
|
+
:start_time,
|
28
|
+
:finish_time
|
29
|
+
|
30
|
+
alias_method :state, :aasm_read_state
|
31
|
+
|
32
|
+
|
33
|
+
################################################################################
|
34
|
+
# State
|
35
|
+
################################################################################
|
36
|
+
aasm do
|
37
|
+
state :idle, initial: true
|
38
|
+
state :running
|
39
|
+
state :finished
|
40
|
+
|
41
|
+
event :start, after: :did_start do
|
42
|
+
transitions from: :idle, to: :running
|
43
|
+
end
|
44
|
+
|
45
|
+
event :finish, after: :did_finish do
|
46
|
+
transitions from: :running, to: :finished
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
################################################################################
|
52
|
+
# State transition callbacks
|
53
|
+
################################################################################
|
54
|
+
private def did_start
|
55
|
+
self.start_time = timestamping_method.call
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
private def did_finish
|
60
|
+
self.finish_time = timestamping_method.call
|
61
|
+
end
|
62
|
+
end
|
data/vendor/.gitkeep
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: club
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rob Yurkowski
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aasm
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.9'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: What is this I don't even really
|
98
|
+
email:
|
99
|
+
- rob@yurkowski.net
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- Gemfile
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- bin/console
|
110
|
+
- bin/setup
|
111
|
+
- club.gemspec
|
112
|
+
- lib/club.rb
|
113
|
+
- lib/club/version.rb
|
114
|
+
- lib/controllers/time_controller.rb
|
115
|
+
- lib/core_ext/array.rb
|
116
|
+
- lib/entities/time_chunk.rb
|
117
|
+
- vendor/.gitkeep
|
118
|
+
homepage: https://github.com/robyurkowski/club
|
119
|
+
licenses: []
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.4.5
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: What is this I don't even
|
141
|
+
test_files: []
|