time_tap 0.2.0 → 0.4.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +5 -37
  2. data/.rspec +1 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +3 -21
  5. data/README.md +104 -41
  6. data/Rakefile +1 -30
  7. data/bin/timetap +25 -26
  8. data/lib/time_tap.rb +126 -52
  9. data/lib/time_tap/backend.rb +9 -0
  10. data/lib/time_tap/backend/file_system.rb +36 -0
  11. data/lib/time_tap/config.yml.example +32 -0
  12. data/lib/time_tap/editor.rb +9 -0
  13. data/lib/time_tap/editor/sublime_text2.rb +15 -0
  14. data/lib/time_tap/editor/text_mate.rb +22 -0
  15. data/lib/time_tap/editor/text_mate2.rb +15 -0
  16. data/lib/time_tap/editor/xcode.rb +24 -0
  17. data/lib/time_tap/project.rb +136 -116
  18. data/lib/time_tap/server.rb +20 -20
  19. data/lib/time_tap/version.rb +3 -0
  20. data/lib/time_tap/views/project.haml +5 -3
  21. data/lib/time_tap/views/project_day.haml +1 -0
  22. data/lib/time_tap/watcher.rb +46 -43
  23. data/log/.git-keep +1 -0
  24. data/spec/lib/time_tap/project_spec.rb +12 -0
  25. data/spec/spec_helper.rb +3 -1
  26. data/spec/time_tap/backend_spec.rb +12 -0
  27. data/spec/time_tap/project_spec.rb +13 -0
  28. data/spec/time_tap/watcher_spec.rb +14 -0
  29. data/spec/time_tap_spec.rb +6 -4
  30. data/time_tap.gemspec +26 -108
  31. data/vendor/SublimeText2/.gitignore +1 -0
  32. data/vendor/SublimeText2/README.md +27 -0
  33. data/vendor/SublimeText2/TimeTap.py +10 -0
  34. data/vendor/TextMate2/TimeTap.tmbundle/Commands/Record current file.tmCommand +33 -0
  35. data/vendor/TextMate2/TimeTap.tmbundle/info.plist +16 -0
  36. data/vendor/TimeTap.tmbundle/Commands/Record current file.tmCommand +36 -0
  37. data/vendor/TimeTap.tmbundle/info.plist +18 -0
  38. metadata +161 -260
  39. data/Gemfile.lock +0 -48
  40. data/VERSION +0 -1
  41. data/config.yaml +0 -8
  42. data/lib/time_tap/editors.rb +0 -23
data/.gitignore CHANGED
@@ -1,42 +1,10 @@
1
- # rcov generated
2
1
  coverage
3
-
4
- # rdoc generated
5
2
  rdoc
6
-
7
- # yard generated
8
3
  doc
9
4
  .yardoc
10
-
11
- # bundler
5
+ pkg/*
6
+ *.gem
12
7
  .bundle
13
-
14
- # jeweler generated
15
- pkg
16
-
17
- # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
18
- #
19
- # * Create a file at ~/.gitignore
20
- # * Include files you want ignored
21
- # * Run: git config --global core.excludesfile ~/.gitignore
22
- #
23
- # After doing this, these files will be ignored in all your git projects,
24
- # saving you from having to 'pollute' every project you touch with them
25
- #
26
- # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
27
- #
28
- # For MacOS:
29
- #
30
- #.DS_Store
31
- #
32
- # For TextMate
33
- #*.tmproj
34
- #tmtags
35
- #
36
- # For emacs:
37
- #*~
38
- #\#*
39
- #.\#*
40
- #
41
- # For vim:
42
- #*.swp
8
+ .sass*
9
+ /Gemfile.lock
10
+ /log/*.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3
data/Gemfile CHANGED
@@ -1,22 +1,4 @@
1
- source "http://rubygems.org"
1
+ source 'http://rubygems.org'
2
2
 
3
- gem 'activesupport', '~> 2.3.8'
4
- gem 'actionpack', '~> 2.3.8'
5
- gem 'i18n', '~> 0.3.5'
6
- gem 'haml'
7
- gem 'rb-appscript'
8
- gem 'sinatra'
9
-
10
- # Add dependencies required to use your gem here.
11
- # Example:
12
- # gem "activesupport", ">= 2.3.5"
13
-
14
- # Add dependencies to develop your gem here.
15
- # Include everything needed to run rake, tests, features, etc.
16
- group :development do
17
- gem "rspec", ">= 2.0.0.beta.19"
18
- gem "yard", "~> 0.6.0"
19
- gem "bundler", "~> 1.0.0"
20
- gem "jeweler", "~> 1.5.0.pre3"
21
- gem "rcov", ">= 0"
22
- end
3
+ # Specify your gem's dependencies in time_tap.gemspec
4
+ gemspec
data/README.md CHANGED
@@ -2,80 +2,122 @@
2
2
 
3
3
  TimeTap helps you track the time you spend coding on each project while in TextMate.
4
4
 
5
- Once it's launched you don't have to bother anymore starting/stopping timers or
5
+ Once it's launched you don't have to bother anymore starting/stopping timers or
6
6
  inventing some arbitrary amount of time to fill your fancy time tracker.
7
7
 
8
8
  <img src="http://f.cl.ly/items/17025fecf7189518cf07/timetap-project-list.png"/>
9
9
  <img src="http://f.cl.ly/items/7b96ad2f7b49a95fdfd0/timetap-project-page.png"/>
10
10
 
11
11
 
12
- ## How does it work
13
12
 
14
- TimeTap keeps an eye on (tracks) the modification time of the frontmost file
15
- and tells you how much time you spent on each project.
13
+ ## Installing
16
14
 
17
- If you stop coding for a while while squeezing your brains TimeTap understands.
18
- TimeTap will consider "coding time" pauses to up to 30 minutes between to saves
15
+ gem install time_tap
16
+
17
+ timetap --install
18
+
19
+ … and visit [localhost:1111](http://localhost:1111/)
20
+
21
+ ### Installing from source
22
+
23
+ - Get the codez: `git clone git://github.com/elia/timetap.git`
24
+ - Run `bundle exec bin/timetap --install`
25
+
26
+
27
+
28
+
29
+ ## How it works
30
+
31
+ TimeTap keeps an eye on the modification time of the frontmost file in TextMate
32
+ and tells you how much time you spent on each project.
33
+
34
+ If you stop coding for a while while squeezing your brains TimeTap understands.
35
+ TimeTap will consider "coding time" pauses to up to 30 minutes between to saves
19
36
  in the same project.
20
37
 
21
- Technically it saves a timestamp+path of the frontmost file in TextMate every
38
+ Technically it saves a timestamp+path of the frontmost file in TextMate every
22
39
  30 seconds, then it digests all this information in a nice Sinatra webapp.
23
40
 
24
41
  The server will respond on http://0.0.0.0:1111/.
25
42
 
26
43
 
27
- ## Instructions
44
+ ### Assumptions
45
+
46
+ * You code on TextMate.
47
+ * You save often (like me), at least every 30 minutes.
48
+ * You keep your code organized (I use ~/Code as main code folder).
49
+
50
+
51
+ ## Editors
52
+
53
+ TimeTap works with TextMate, TextMate2 and SublimeText2.
54
+ For TM2 and ST2 you need to install specific bundles (in the `vendor/` folder).
55
+
56
+ ### TextMate
57
+
58
+ Need to do nothing, thanks to `rb-appscript` :)
59
+
60
+
61
+ ### TextMate 2
62
+
63
+ You need to install the bundle in [`vendor/SublimeText2`](https://github.com/elia/timetap/tree/master/vendor/TextMate2)
64
+
65
+
66
+ ### SublimeText
67
+
68
+ You need to install the package in [`vendor/SublimeText2`](https://github.com/elia/timetap/tree/master/vendor/SublimeText2)
69
+
70
+
71
+
72
+ ## Configuring
28
73
 
29
- Run `ruby -Ilib bin/timetap` or run
30
- `rake launcher && launchctl load ~/Library/LaunchAgents`
31
- to add a plist for OSX's launchd and have it launched automatically at login.
74
+ TimeTap uses a config file to control where projects are kept, etc. the path is:
32
75
 
33
- ## Setting up config file
76
+ ~/.timetap.config
34
77
 
35
- timetap uses a config file to control where projects are kept, etc. Put this config file in `~/.tap_config`.
78
+ Which can look like this:
36
79
 
37
- cp config.yaml ~/.tap_config
80
+ ```yaml
81
+ port: 1111
82
+ # the port on localhost for the web interface
38
83
 
39
- ### Keys Explained
84
+ # These are used to identify project root folders
85
+ code_folders:
86
+ - ~/Code/MyCompany
87
+ - ~/Code
40
88
 
41
- root - where the timetap logs should be saved. Recommended value: ~
42
- code - where all you project live, in a flat hierachy.
43
- nested_project_layers - see below, on nested projects. Default is 1
44
-
89
+ # It's highly recomended to use 1.9.3
90
+ ruby: /Users/elia/.rvm/bin/ruby-1.9.3-p286
91
+ ```
45
92
 
46
- Nested Projects allows you to keep your projects inside a hierarchy, instead of the original assumption of timetap (which is that all projects are flat).
47
93
 
48
- For example, you could keep your directory structure might look like:
94
+
95
+ ### About "nested project layers"
96
+
97
+ TimeTap assumes you keep your projects inside a specific folder, like this:
98
+
99
+ ~/Code/
100
+ tap/
101
+ tik_tak/
102
+ tk-win/
103
+ AcmeCorp/
104
+ website/
105
+ intranet/
106
+
107
+ But if you keep your projects grouped in subfolders like this:
49
108
 
50
109
  ~/Code/
51
110
  Clients/
52
111
  AcmeCorp/
53
112
  website/
54
- intranet
113
+ intranet/
55
114
  BetaCorp/
56
115
  skunkworks/
57
116
  OpenSource/
58
117
  project_one/
59
118
  timetap/
60
119
 
61
- A `nested_project_layers` setting of 2 (in your `.tap_config` file) would mean we track "AcmeCorp", "BetaCorp", and everything under OpenSource, as their own projects
62
-
63
- ## Assumptions
64
-
65
- * You code on TextMate.
66
- * You save often (like me), at least every 30 minutes.
67
- * You keep your code organized (I use ~/Code as main code folder).
68
-
69
-
70
- ## TODO
71
-
72
- - support other text editors, or at least make it easy to do so
73
- - (r)spec it!
74
- - make it more configurable
75
- - gemify (with jeweler)
76
- - flatten encoding quick-fixes with proper solutions (eat and spit only utf8)
77
- - integration with external (online) time tracking tools
78
- - export to csv (?)
120
+ then, the `nested_project_layers` key tells TimeTap how deep to look for project names inside a hierarchy (in the example a value of 2 will catch `AcmeCorp`, `BetaCorp`, `project_one` and `timetap`).
79
121
 
80
122
 
81
123
  ## How to Contribute
@@ -91,6 +133,27 @@ Use it, love it, then...
91
133
  * Send me a pull request. Bonus points for topic branches.
92
134
 
93
135
 
136
+ ### TODO
137
+
138
+ - <strike>make it more configurable</strike>
139
+ - <strike>gemify (with jeweler)</strike>
140
+ - <strike>support other text editors, or at least make it easy to do so</strike>
141
+ - (r)spec it!
142
+ - flatten encoding quick-fixes with proper solutions (eat and spit only utf8)
143
+ - integration with external (online) time tracking tools
144
+
145
+
146
+ ## Credits
147
+
148
+ - Ryan Wilcox [@rwilcox](https://github.com/rwilcox)
149
+
150
+
151
+ ## Related Projects
152
+
153
+ - [Watch Tower](https://github.com/TechnoGate/watch_tower)
154
+ - [Vim TimeTap](https://github.com/rainerborene/vim-timetap)
155
+
156
+
94
157
  ## Copyright
95
158
 
96
- Copyright (c) 2009 Elia Schito. See LICENSE for details.
159
+ Copyright © 2009-2012 Elia Schito. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,34 +1,5 @@
1
- require 'rubygems'
2
1
  require 'bundler'
3
- begin
4
- Bundler.setup(:default, :development)
5
- rescue Bundler::BundlerError => e
6
- $stderr.puts e.message
7
- $stderr.puts "Run `bundle install` to install missing gems"
8
- exit e.status_code
9
- end
10
- require 'rake'
11
-
12
- require 'jeweler'
13
- Jeweler::Tasks.new do |gem|
14
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
- gem.name = "time_tap"
16
- gem.summary = %Q{Unobtrusive time tracking for TextMate}
17
- gem.description = %Q{TimeTap helps you track the time you spend coding on each project while in TextMate.}
18
- gem.email = "perlelia@gmail.com"
19
- gem.homepage = "http://github.com/elia/timetap"
20
- gem.authors = ["Elia Schito"]
21
- # Include your dependencies below. Runtime dependencies are required when using your gem,
22
- # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
23
- # spec.add_runtime_dependency 'jabber4r', '> 0.1'
24
- # spec.add_development_dependency 'rspec', '> 1.2.3'
25
- gem.add_development_dependency "rspec", ">= 2.0.0.beta.19"
26
- gem.add_development_dependency "yard", "~> 0.6.0"
27
- gem.add_development_dependency "bundler", "~> 1.0.0"
28
- gem.add_development_dependency "jeweler", "~> 1.5.0.pre3"
29
- gem.add_development_dependency "rcov", ">= 0"
30
- end
31
- Jeweler::RubygemsDotOrgTasks.new
2
+ Bundler::GemHelper.install_tasks
32
3
 
33
4
  require 'rspec/core'
34
5
  require 'rspec/core/rake_task'
data/bin/timetap CHANGED
@@ -4,52 +4,51 @@
4
4
  require 'time_tap'
5
5
 
6
6
 
7
- require 'yaml'
8
- user_config = File.expand_path("~/.tap_config")
9
- config_file = File.expand_path('../../config.yaml', __FILE__)
10
-
11
- options = YAML.load_file(config_file)
12
- options.merge! YAML.load_file(user_config) if File.exist?(user_config)
13
- TimeTap.config = options
14
-
7
+ TimeTap.load_user_config!
15
8
 
16
9
  require 'optparse'
17
10
  OptionParser.new do |opts|
18
11
  opts.banner = "Usage: #{$0} [options]"
19
12
 
20
- opts.on("-f", "--foreground", "Run in foreground.") do
21
- TimeTap.config[:foreground] = true
13
+ opts.on("-f", "--foreground", "Run in foreground (default).") do
14
+ TimeTap.config[:background] = false
15
+ end
16
+
17
+ opts.on("-b", "--background", "Run in background.") do
18
+ TimeTap.config[:background] = true
22
19
  end
23
-
24
- opts.on("-p", "--port PORT", "Use specified port for server. (default #{options['port']})") do |value|
20
+
21
+ opts.on("-p", "--port PORT", "Use specified port for server. (default #{TimeTap.config['port']})") do |value|
25
22
  TimeTap.config[:port] = value
26
23
  end
27
-
28
- opts.on("--install", "Launch automatically at login.") do
29
- TimeTap.install!
30
- puts "\nInstalled. Now run:\n launchctl load ~/Library/LaunchAgents\n\n"
31
- exit
24
+
25
+ opts.on("-l", "--log-level LEVEL", "Use specified port for server. (default #{TimeTap.config['port']})") do |value|
26
+ TimeTap.config[:log_level] = value
32
27
  end
33
-
34
- opts.on("--reload", "Reload login launch agent.") do
35
- puts 'Reloading...'
36
- TimeTap.reload!
28
+
29
+ opts.on("-i", "--install", "Sets TimeTap to be launched automatically at login, and boots it.") do
30
+ TimeTap.install_launcher!
31
+ TimeTap.install_config!
32
+ TimeTap.reload_launcher!
33
+ # TimeTap::reload_launcher! uses `exec` so it never gets here
37
34
  exit
38
35
  end
39
36
  end.parse!
40
37
 
41
-
42
- unless TimeTap.config[:foreground]
38
+ TimeTap.config[:foreground] = !TimeTap.config[:background]
39
+ if TimeTap.config[:background]
43
40
  require 'time_tap/daemon'
44
-
41
+
45
42
  pid = fork {
46
43
  # Try to replace "ruby" with "TimeTap" in the command string (for "ps -A" & co.)
47
44
  $0 = 'TimeTap'
48
-
45
+
49
46
  Process.daemon(true)
50
47
  TimeTap.start
51
48
  }
52
49
  else
53
- puts "going foreground"
50
+ TimeTap.logger = Logger.new($stdout)
51
+ TimeTap.logger.level = Logger.const_get(TimeTap.config[:log_level].upcase) if TimeTap.config[:log_level]
52
+ TimeTap.logger.info '[TimeTap] Going foreground...'
54
53
  TimeTap.start
55
54
  end
data/lib/time_tap.rb CHANGED
@@ -1,81 +1,123 @@
1
- #!/usr/bin/env ruby
2
- # encoding: utf-8
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'logger'
3
3
 
4
+ module TimeTap
5
+ @config = {
6
+ :root => "~",
7
+ # root is where the logs will be saved
4
8
 
5
- require 'rubygems'
6
- gem 'activesupport', '~> 2.3.8'
7
- gem 'actionpack', '~> 2.3.8'
8
- gem 'i18n', '~> 0.3.5'
9
- gem 'haml'
10
- gem 'rb-appscript'
11
- gem 'sinatra'
9
+ # code is where all your projects live
10
+ :code_folders => %w[
11
+ ~/Code
12
+ ~/Developer
13
+ ~/Development
14
+ ~/src
15
+ ~/code
16
+ ~/rails
17
+ ~/Desktop
18
+ ~
19
+ ],
12
20
 
21
+ :nested_project_layers => 1,
22
+ # see below about nested projects
13
23
 
14
- module TimeTap
24
+ :port => 1111,
25
+ # the port on localhost for the web interface
26
+
27
+ :ruby => '/usr/bin/ruby',
28
+ # the ruby you want to use
29
+
30
+ :backend => :file_system,
31
+ :backend_options => { :file_name => '~/.timetap.history' },
32
+ :editor => :text_mate,
33
+ :log_file => '~/.timetap.log'
34
+ }.with_indifferent_access
15
35
  attr_accessor :config
16
-
36
+
37
+
38
+ @logger = Logger.new($stdout)
39
+ attr_accessor :logger
40
+
41
+
17
42
  extend self
18
-
19
-
43
+
44
+
20
45
  # CONFIGURATION
21
46
 
22
47
  # Are we on 1.9?
23
48
  # FIXME: this is wrong! :)
24
49
  RUBY19 = RUBY_VERSION.to_f >= 1.9
25
-
50
+
26
51
  def config= options = {}
27
52
  require 'active_support'
28
-
53
+
29
54
  # CONFIG
30
55
  @config = HashWithIndifferentAccess.new(options)
31
56
  @config[:root] = File.expand_path(config[:root])
32
57
  @config[:port] = config[:port].to_i
33
58
  end
34
-
35
-
59
+
60
+
61
+
62
+ # BACKEND
63
+
64
+ def backend
65
+ require 'time_tap/backend'
66
+ @backend = Backend.load config[:backend], config[:backend_options]
67
+ end
68
+
69
+ def editor
70
+ require 'time_tap/editor'
71
+ @backend = Editor.load config[:editor], config[:editor_options]
72
+ end
73
+
74
+
75
+ # START
76
+
36
77
  def start options = {}
37
78
  # REQUIREMENTS
38
-
79
+
39
80
  require 'yaml'
40
- require 'active_support'
41
81
  require 'time_tap/project'
42
- require 'time_tap/editors'
43
82
  require 'time_tap/watcher'
44
83
  require 'time_tap/server'
45
-
46
-
47
-
84
+
85
+ logger.info "[TimeTap] config: #{config.to_yaml}"
48
86
  # SIGNAL HANDLING
49
-
50
- Signal.trap("INT") {exit}
51
- Signal.trap("TERM") {exit}
52
-
53
-
87
+
88
+ Signal.trap('INT') {exit}
89
+ Signal.trap('TERM') {exit}
90
+
91
+
54
92
  # WEB SERVER
55
-
93
+
56
94
  Thread.abort_on_exception = true
57
-
95
+
58
96
  @server = Thread.new {
59
97
  Signal.trap("INT") {exit}
60
98
  Signal.trap("TERM") {exit}
61
-
99
+
62
100
  Server.run! :host => 'localhost', :port => TimeTap.config[:port]
63
101
  exit
64
102
  }
65
-
66
-
103
+
104
+
67
105
  # WATCHER
68
-
69
- include Editors
70
- Watcher.keep_watching(TextMate)
106
+
107
+ watcher = Watcher.new(editor, backend)
108
+ watcher.keep_watching
71
109
  end
72
-
110
+
111
+
73
112
  # Add a plist for OSX's launchd and have *TimeTap* launched automatically at login.
74
- def install!
113
+ def install_launcher!
114
+ puts 'Installing launcher...'
115
+
75
116
  load_plist_info!
76
117
  ruby = config[:ruby] || "/usr/bin/ruby"
77
118
  include_dir = '-I'+File.expand_path('../../lib', __FILE__)
78
119
  launcher = File.expand_path('../../bin/timetap', __FILE__)
120
+ working_directory = File.expand_path('../../', __FILE__)
79
121
 
80
122
  puts "\nCreating launchd plist in\n #{plist_path}"
81
123
 
@@ -88,17 +130,25 @@ module TimeTap
88
130
  <key>Label</key>
89
131
  <string>com.eliaesocietas.TimeTap</string>
90
132
 
91
- <key>Program</key>
92
- <string>#{ruby}</string>
93
-
94
133
  <key>ProgramArguments</key>
95
134
  <array>
96
135
  <string>#{ruby}</string>
97
136
  <string>#{include_dir}</string>
137
+ <string>-S</string>
138
+ <string>bundle</string>
139
+ <string>exec</string>
98
140
  <string>#{launcher}</string>
99
141
  <string>-f</string>
100
142
  </array>
101
143
 
144
+
145
+ <key>WorkingDirectory</key>
146
+ <string>#{working_directory}</string>
147
+ <key>StandardErrorPath</key>
148
+ <string>/usr/local/var/log/timetap.log</string>
149
+ <key>StandardOutPath</key>
150
+ <string>/usr/local/var/log/timetap.log</string>
151
+
102
152
  <key>OnDemand</key>
103
153
  <false/>
104
154
 
@@ -109,20 +159,44 @@ module TimeTap
109
159
  PLIST
110
160
  end
111
161
  end
112
-
113
- def reload!
162
+
163
+ def reload_launcher!
164
+ puts 'Reloading system launcher...'
165
+
114
166
  load_plist_info!
115
167
  command = "launchctl unload #{plist_path}; launchctl load #{plist_path}"
116
168
  exec command
117
169
  end
118
-
119
- private
120
-
121
- attr_reader :plist_path, :plist_name
122
-
123
- def load_plist_info!
124
- @plist_name ||= "com.eliaesocietas.TimeTap.plist"
125
- @plist_path ||= File.expand_path("~/Library/LaunchAgents/#{plist_name}")
170
+
171
+ def load_user_config!
172
+ require 'yaml'
173
+ TimeTap.config.merge! YAML.load_file(user_config) if File.exist?(user_config)
174
+ end
175
+
176
+ def user_config
177
+ @user_config ||= File.expand_path('~/.timetap.config')
178
+ end
179
+
180
+ def install_config!
181
+ puts 'Checking config...'
182
+ unless File.exist? user_config
183
+ require 'fileutils'
184
+ example_config = File.expand_path('../time_tap/config.yml.example', __FILE__)
185
+ FileUtils.copy example_config, user_config
186
+ puts "Added default config to #{user_config}"
126
187
  end
127
-
188
+ end
189
+
190
+
191
+
192
+
193
+ private
194
+
195
+ attr_reader :plist_path, :plist_name
196
+
197
+ def load_plist_info!
198
+ @plist_name ||= "com.eliaesocietas.TimeTap.plist"
199
+ @plist_path ||= File.expand_path("~/Library/LaunchAgents/#{plist_name}")
200
+ end
201
+
128
202
  end