watch_tower 0.0.1.beta5 → 0.0.1.beta6

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -4,7 +4,11 @@ Gemfile.lock
4
4
  pkg/*
5
5
  .rbx
6
6
  .rvmrc
7
+ tmp
7
8
 
8
9
  # Tranmuter output
9
10
  README.pdf
10
- README.html
11
+ README.html
12
+
13
+ # Sass
14
+ .sass-cache
data/Gemfile CHANGED
@@ -30,6 +30,7 @@ platforms :ruby do
30
30
  if RbConfig::CONFIG['target_os'] =~ /linux/i
31
31
  gem 'rb-inotify', require: false
32
32
  gem 'libnotify', require: false
33
+ gem 'therubyracer', require: false
33
34
  end
34
35
  end
35
36
  end
data/README.md CHANGED
@@ -1,82 +1,106 @@
1
1
  # Watch Tower [![Build Status](http://travis-ci.org/TechnoGate/watch_tower.png)](http://travis-ci.org/TechnoGate/watch_tower) [![Still Maintained](http://stillmaintained.com/TechnoGate/watch_tower.png)](http://stillmaintained.com/TechnoGate/watch_tower)
2
2
 
3
- [![Click here to lend your support to: Open Source Projects and make a donation at www.pledgie.com !](http://www.pledgie.com/campaigns/16123.png?skin_name=chrome)](http://www.pledgie.com/campaigns/16123)
3
+ [![Click here to lend your support to: Open Source Projects and make a donation at www.pledgie.com!](http://pledgie.com/campaigns/16123.png?skin_name=chrome)](http://www.pledgie.com/campaigns/16123)
4
4
 
5
- WatchTower helps you track the time you spend on each project and on each file
6
- in the projects.
5
+ WatchTower helps you track how much time you spend on all of your projects, at
6
+ the project, directory, and file level.
7
7
 
8
8
  # Introduction
9
9
 
10
- Did you ever wonder how much each of your projects really costs? Watch Tower
11
- comes to the rescue.
10
+ Did you ever want to keep track of how much time you _really_ spend on all of
11
+ your projects? Sure, you can try to remember to keep running estimates of your
12
+ time in the hope that you can aggregate those estimates later into some
13
+ meaningful data. But sometimes you forget, or an error creeps into your
14
+ estimate. And those errors add up. Quickly.
12
15
 
13
- WatchTower runs in the background and monitors your editors (see Supported
14
- Editors) and records the time you spend on each file and thus on the project
15
- in total. Using a simple but powerful web interface, you can view details and
16
- statistics about each project and each file.
16
+ You can try some tracking software that depends on you to start and stop
17
+ timers. But what happens if you forget to start or stop one of those timers?
18
+
19
+ What you need is a passive system that will take care of all of this for you,
20
+ so you can focus on the actual work, which is where WatchTower comes into
21
+ play.
22
+
23
+ WatchTower runs in the background and keeps track of the time spent editing
24
+ each file with one of the supported editors (listed below). Since WatchTower
25
+ keeps track of the time spent on each file, and it knows which project each
26
+ file belongs to, you can view details and statistics on each project, right
27
+ down to the file level.
17
28
 
18
29
  # Features
19
30
 
20
- - Track editors (see Supported Editors) and record all files tracked under
21
- git or placed under __code_path__ defined in the config file.
22
- - Display all projects on the home page that has been worked on the current
23
- month, the date range can be changed using the date picker on the home page
24
- - For each project, a detailed overview of each file with the elapsed time
25
- within the selected date range
31
+ - Tracks the supported editors (listed below) and records the time spent on
32
+ all files as specified via the customized configuration file (Git and
33
+ __code_path__ are supported).
34
+
35
+ - A WatchTower Home Page where you can see how much time you've spent on all
36
+ watched projects, as well as a total summary. The default display includes
37
+ all projects worked on during the current month, but the page includes a
38
+ date picker for easy selection. You can select a project to view the
39
+ project's Detail Page.
40
+
41
+ ![Example: WatchTower Home Page](http://f.cl.ly/items/1C0W1W0V2L3s3k2o313f/home_page.png)
42
+
43
+ - A Project Detail Page that displays a detailed report of the time spent on
44
+ the project, each directory within the project, and each file. The default
45
+ display includes all files worked on during the current month, but the page
46
+ includes a date picker for easy selection.
47
+
48
+ ![Example: Project Detail Page](http://f.cl.ly/items/3T263A350w261b0b2U1x/project_page.png)
26
49
 
27
50
  # Supported Editors
28
51
 
29
52
  - TextMate
30
53
  - Xcode
54
+ - ViM (gVim and MacVim are also supported)
31
55
 
32
56
  # Supported Operating Systems
33
57
 
34
58
  - Mac OS X
59
+ - Linux
35
60
 
36
- # Installation
61
+ # Getting Started
37
62
 
38
- The installation has been made as simple as possible, here's the steps required:
63
+ 1. Install the WatchTower gem:
39
64
 
40
- ```bash
41
- $ gem install watch_tower --pre
42
- $ watchtower install
43
- $ watchtower load_bootloader
44
- ```
65
+ ```bash
66
+ $ gem install watch_tower --pre
67
+ ```
68
+ 2. Followed by:
69
+
70
+ ```bash
71
+ $ watchtower install
72
+ $ watchtower load_bootloader
73
+ ```
45
74
 
46
- This creates a configuration file which you __should__ review before invoking
47
- __WatchTower__, located at __~/.watch_tower/config.yml__ the configuration file
48
- is self explanatory.
75
+ 3. __Review the self-explanatory configuration file__ located at
76
+ __~/.watch_tower/config.yml__ and make any changes necessary.
49
77
 
50
78
  # Update
51
79
 
52
- It is important to run
80
+ Run
53
81
 
54
82
  ```bash
55
83
  $ watchtower install_bootloader
56
84
  $ watchtower reload_bootloader
57
85
  ```
58
86
 
59
- because the path to watchtower binary file is hardcoded in the boot loader.
87
+ to update the path to the WatchTower binary in the boot loader.
60
88
 
61
89
  # Usage
62
90
 
63
- The installation process should create a launcher on login which starts
64
- __WatchTower__ you can open the web interface by going to
65
- http://localhost:9282 or using the command
91
+ The installation process creates a launcher on login that starts
92
+ __WatchTower__. You can view your WatchTower Home Page via the web interface
93
+ by going to http://localhost:9282, or from the command line:
66
94
 
67
95
  ```bash
68
96
  $ watchtower open
69
97
  ```
70
98
 
71
- # Screenshots
72
-
73
- ## Home page
99
+ # Commands
74
100
 
75
- [![Home page](http://cloud.github.com/downloads/TechnoGate/watch_tower/home_page.png)](http://cloud.github.com/downloads/TechnoGate/watch_tower/home_page.png)
76
-
77
- ## Project page
78
-
79
- [![Project page](http://cloud.github.com/downloads/TechnoGate/watch_tower/project_page.png)](http://cloud.github.com/downloads/TechnoGate/watch_tower/project_page.png)
101
+ For more information on available commands, you can take a look at the
102
+ [WatchTower Command Line wiki
103
+ page](https://github.com/TechnoGate/watch_tower/wiki/WatchTower-Command-Line).
80
104
 
81
105
  # Contributing
82
106
 
@@ -95,10 +119,12 @@ in mind for the project
95
119
 
96
120
  # Credits
97
121
 
98
- This projects is heavily inspired by
122
+ This project is heavily inspired by
99
123
  [timetap](https://github.com/elia/timetap) created by [Elia
100
- Schito](https://github.com/elia), it also uses a number of open source
101
- projects including but not limited to:
124
+ Schito](https://github.com/elia).
125
+
126
+ It also uses a number of open source
127
+ projects including, but not limited to:
102
128
 
103
129
  - [rb-appscript](http://appscript.sourceforge.net/)
104
130
  - [activesupport](https://github.com/rails/rails)
@@ -124,6 +150,8 @@ projects including but not limited to:
124
150
  - [factory_girl](https://github.com/thoughtbot/factory_girl)
125
151
  - [timecop](https://github.com/jtrupiano/timecop)
126
152
  - [pry](https://github.com/pry/pry)
153
+ - [systemu](https://github.com/ahoward/systemu)
154
+ - [cronedit](http://cronedit.rubyforge.org)
127
155
 
128
156
  # License
129
157
 
@@ -148,4 +176,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
148
176
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
149
177
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
150
178
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
151
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
179
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -6,75 +6,12 @@ rescue LoadError
6
6
  # Define a part of the Appscript gem so WatchTower is fully operational
7
7
  module ::Appscript
8
8
  CommandError = Class.new(Exception)
9
-
10
- class Application
11
-
12
- MOCK_METHODS = [:name, :version, :unix_id, :by_pid]
13
-
14
- def is_running?
15
- false
16
- end
17
-
18
- def get
19
- "Not available"
20
- end
21
-
22
- def processes
23
- Process.new(self)
24
- end
25
-
26
- MOCK_METHODS.each do |method|
27
- define_method method do |*args|
28
- self
29
- end
30
- end
31
- end
32
-
33
- class Process
34
- def initialize(klass)
35
- @klass = klass
36
- end
37
-
38
- def [](*args)
39
- [@klass]
40
- end
41
- end
42
-
43
9
  def app(*args)
44
- Application.new
45
- end
46
-
47
- def its
48
- self
10
+ raise ::WatchTower::AppscriptNotLoadedError
49
11
  end
50
-
51
- def name
52
- self
53
- end
54
-
55
- def eq(*args)
56
- self
57
- end
58
-
59
- def Appscript.app(*args)
60
- Application.new
61
- end
62
-
63
- def Appscript.its
64
- self
65
- end
66
-
67
- def Appscript.name
68
- self
69
- end
70
-
71
- def Appscript.eq(*args)
72
- self
73
- end
74
-
75
12
  end
76
13
 
77
14
  module ::FindApp
78
15
  ApplicationNotFoundError = Class.new(Exception)
79
16
  end
80
- end
17
+ end
@@ -61,22 +61,6 @@ module WatchTower
61
61
  end
62
62
 
63
63
  protected
64
- # Taken from hub
65
- # https://github.com/defunkt/hub/blob/master/lib/hub/context.rb#L186
66
- # Cross-platform way of finding an executable in the $PATH.
67
- #
68
- # which('ruby') #=> /usr/bin/ruby
69
- def which(cmd)
70
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
71
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
72
- exts.each { |ext|
73
- exe = "\#{path}/\#{cmd}\#{ext}"
74
- return exe if File.executable? exe
75
- }
76
- end
77
- return nil
78
- end
79
-
80
64
  # Install the configuration file
81
65
  def install_config_file
82
66
  self.class.source_root(TEMPLATE_PATH)
@@ -89,6 +73,8 @@ module WatchTower
89
73
  case RbConfig::CONFIG['target_os']
90
74
  when /darwin/
91
75
  install_bootloader_on_mac
76
+ when /linux/
77
+ install_bootloader_on_linux
92
78
  else
93
79
  puts bootloader_not_supported_on_current_os
94
80
  end
@@ -98,8 +84,6 @@ module WatchTower
98
84
  def install_bootloader_on_mac
99
85
  self.class.source_root(TEMPLATE_PATH)
100
86
  create_file bootloader_path_on_mac, force: options[:force] do
101
- ruby_binary = which('ruby')
102
- watch_tower_binary = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'bin', 'watchtower'))
103
87
  template = File.expand_path(find_in_source_paths('watchtower.plist.erb'))
104
88
  ERB.new(File.read(template)).result(binding)
105
89
  end
@@ -107,6 +91,50 @@ module WatchTower
107
91
  puts "\nCreated. Now run:\n watchtower load_bootloader\n\n"
108
92
  end
109
93
 
94
+ # Install bootloader on linux
95
+ def install_bootloader_on_linux
96
+ require 'cronedit'
97
+ # Remove any old entries
98
+ uninstall_bootloader_on_linux
99
+ # Define the crontab command
100
+ crontab_command = "\#{ruby_binary} \#{watch_tower_binary} start --bootloader"
101
+ # Create a crontab instance
102
+ crontab = CronEdit::Crontab.new
103
+ # Add the command
104
+ crontab.add Time.now.strftime('%s'), { minute: "@reboot", hour: '', day: '', month: '', weekday: '', command: crontab_command }
105
+ # Commit changes
106
+ crontab.commit
107
+ end
108
+
109
+ # Uninstall bootloader on linux
110
+ def uninstall_bootloader_on_linux
111
+ require 'cronedit'
112
+ # Create a crontab instance
113
+ crontab = CronEdit::Crontab.new
114
+ # Iterate over crontab entries and remove any command having watchtower start
115
+ crontab.list.each_pair do |k, c|
116
+ if c =~ /watchtower start/
117
+ crontab.remove(k)
118
+ end
119
+ end
120
+ # Commit changes
121
+ crontab.commit
122
+ end
123
+
124
+ # Returns the absolute path to the ruby binary
125
+ #
126
+ # @return [String] The path to ruby
127
+ def ruby_binary
128
+ WatchTower.which('ruby')
129
+ end
130
+
131
+ # Returns the absolute path to the watchtower binary
132
+ #
133
+ # @return [String] The path to watch tower binary
134
+ def watch_tower_binary
135
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'bin', 'watchtower'))
136
+ end
137
+
110
138
  # Load bootloader
111
139
  def load_bootloader_on_os
112
140
  require 'rbconfig'
@@ -139,7 +167,6 @@ module WatchTower
139
167
  puts bootloader_not_supported_on_current_os
140
168
  end
141
169
  end
142
-
143
170
  # Load the bootloader
144
171
  def load_bootloader_on_mac
145
172
  system "launchctl load \#{bootloader_path_on_mac}"
@@ -178,4 +205,4 @@ MSG
178
205
  end
179
206
  end
180
207
  end
181
- end
208
+ end
@@ -23,9 +23,17 @@ module WatchTower
23
23
  def version
24
24
  raise NotImplementedError, "Please define this function in your class."
25
25
  end
26
+
27
+ # The editor's name for the log
28
+ # Child classes can overwrite this method
29
+ #
30
+ # @return [String]
31
+ def to_s
32
+ "\#{self.class.to_s.split('::').last} Editor"
33
+ end
26
34
  END
27
35
  end
28
36
  end
29
37
  end
30
38
  end
31
- end
39
+ end
@@ -0,0 +1,20 @@
1
+ " This function prints the list of open files
2
+ " Usage:
3
+ " vim --servername "<server>" --remote-expr "watchtower#ls()"
4
+
5
+ " Make sure the function is loaded only once
6
+ if exists("loaded_watchtower_ls_function")
7
+ finish
8
+ endif
9
+ let loaded_watchtower_ls_function = 1
10
+
11
+ " Provided by Marcin Szamotulski <mszamot@gmail.com>
12
+ " Modified by Wael Nasreddine <wael.nasreddine@gmail.com>
13
+ " http://groups.google.com/group/vim_use/msg/3dfb796c366b2e50
14
+ function! watchtower#ls()
15
+ let list=[]
16
+ for i in range(1, bufnr('$'))
17
+ call add(list, fnamemodify(bufname(i), ':p'))
18
+ endfor
19
+ return list
20
+ endfunction
@@ -11,9 +11,15 @@ module WatchTower
11
11
  # returns [::Appscript::Application | nil]
12
12
  def editor
13
13
  app 'Textmate'
14
+ rescue AppscriptNotLoadedError
15
+ # This is expected if appscriot not loaded, on linux for example
14
16
  rescue ::FindApp::ApplicationNotFoundError
15
- LOG.debug "#{__FILE__}:#{__LINE__ - 2}: Textmate application can't be found, maybe not installed?"
17
+ LOG.debug "#{__FILE__}:#{__LINE__ - 4}: Textmate application can't be found, maybe not installed?"
18
+ nil
19
+ rescue ::Appscript::CommandError => e
20
+ LOG.error "#{__FILE__}:#{__LINE__ - 7}: Command error #{e}"
21
+ nil
16
22
  end
17
23
  end
18
24
  end
19
- end
25
+ end
@@ -0,0 +1,113 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Somehow on my laptop, the systemu command works well for vim but does not
4
+ # work at all for gvim, in fact calling systemu just returns an empty stdout.
5
+ # TODO: Figure out if it's a problem on my box or systemu's bug
6
+
7
+ # require 'systemu'
8
+ class Object
9
+ # This method returns the output of a system command
10
+ # much like the original systemu method
11
+ #
12
+ # @param [String] cmd: The command to run.
13
+ # @return [Array] formed of status, stdout, stderr
14
+ def systemu(cmd)
15
+ [0, `#{cmd}`, '']
16
+ end
17
+ end
18
+
19
+ module WatchTower
20
+ module Editor
21
+ class Vim
22
+ include BasePs
23
+
24
+ VIM_EXTENSION_PATH = File.join(EDITOR_EXTENSIONS_PATH, 'watchtower.vim')
25
+
26
+ def initialize
27
+ # Get the list of supported vims
28
+ supported_vims
29
+ end
30
+
31
+ # Return the name of the Editor
32
+ #
33
+ # @return [String] The editor's name
34
+ def name
35
+ "ViM"
36
+ end
37
+
38
+ # Return the version of the editor
39
+ #
40
+ # @return [String] The editor's version
41
+ def version
42
+ if is_running?
43
+ status, stdout, stderr = systemu "#{editor} --version"
44
+
45
+ stdout.scan(/^VIM - Vi IMproved (\d+\.\d+).*/).first.first
46
+ end
47
+ end
48
+
49
+ # Is it running ?
50
+ #
51
+ # @return [Boolean] Is ViM running ?
52
+ def is_running?
53
+ servers.any?
54
+ end
55
+
56
+ # Return the open documents of all vim servers
57
+ #
58
+ # @return [Array] Absolute paths to all open documents
59
+ def current_paths
60
+ if is_running?
61
+ # Make sure All servers has loaded our function
62
+ send_extensions_to_editor
63
+ # Init documents
64
+ documents = []
65
+ servers.each do |server|
66
+ status, stdout, stderr = systemu "#{editor} --servername #{server} --remote-expr 'watchtower#ls()'"
67
+
68
+ documents += stdout.split("\n")
69
+ end
70
+
71
+ documents.uniq
72
+ end
73
+ end
74
+
75
+ protected
76
+ # Return a list of supported vim commands
77
+ #
78
+ # @return [Array] A list of supported vim commands.
79
+ def supported_vims
80
+ @vims ||= ['mvim', 'gvim', 'vim'].collect do |vim|
81
+ # Get the absolute path of the command
82
+ vim_path = WatchTower.which(vim)
83
+ # Print the help of the command
84
+ status, stdout, stderr = systemu "#{vim_path} --help" if vim_path
85
+ # This command is compatible if it exists and if it respond to --remote
86
+ vim_path && stdout =~ %r(--remote) ? vim_path : nil
87
+ end.reject { |vim| vim.nil? }
88
+ end
89
+
90
+ # Return the editor
91
+ #
92
+ # @return [String|nil] The editor command
93
+ def editor
94
+ @vims.any? && @vims.first
95
+ end
96
+
97
+ # Returns the running servers
98
+ #
99
+ # @return [Array] Name of running ViM Servers
100
+ def servers
101
+ status, stdout, stderr = systemu "#{editor} --serverlist"
102
+ stdout.split("\n")
103
+ end
104
+
105
+ # Send WatchTower extensions to vim
106
+ def send_extensions_to_editor
107
+ servers.each do |server|
108
+ systemu "#{editor} --servername #{server} --remote-send ':source #{VIM_EXTENSION_PATH}<CR>'"
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -15,10 +15,15 @@ module WatchTower
15
15
  # https://github.com/apalancat/timetap/blob/editors/lib/time_tap/editors.rb#L25
16
16
  pid = app('System Events').processes[its.name.eq('Xcode')].first.unix_id.get
17
17
  app.by_pid(pid)
18
+ rescue AppscriptNotLoadedError
19
+ # This is expected if appscriot not loaded, on linux for example
18
20
  rescue ::FindApp::ApplicationNotFoundError
21
+ LOG.debug "#{__FILE__}:#{__LINE__ - 5}: Xcode application can't be found, maybe not installed?"
22
+ nil
19
23
  rescue ::Appscript::CommandError
24
+ LOG.debug "#{__FILE__}:#{__LINE__ - 7}: Xcode is not running."
20
25
  nil
21
26
  end
22
27
  end
23
28
  end
24
- end
29
+ end
@@ -8,6 +8,7 @@ module WatchTower
8
8
  autoload :BasePs
9
9
  autoload :Textmate
10
10
  autoload :Xcode
11
+ autoload :Vim
11
12
 
12
13
  def self.editors
13
14
  Editor.constants. # Collect the defined constants
@@ -16,4 +17,4 @@ module WatchTower
16
17
  select { |c| c.class == Class } # Keep only classes
17
18
  end
18
19
  end
19
- end
20
+ end
@@ -12,6 +12,9 @@ module WatchTower
12
12
  PathError = Class.new ProjectError
13
13
  PathNotUnderCodePath = Class.new PathError
14
14
 
15
+ # Appscript errors
16
+ AppscriptNotLoadedError = Class.new WatchTowerError
17
+
15
18
  # Exception raised by the Editor module
16
19
  EditorError = Class.new WatchTowerError
17
20
  TextmateError = Class.new EditorError
@@ -24,4 +27,4 @@ module WatchTower
24
27
 
25
28
  # Exceptions raised by the Eye module
26
29
  EyeError = Class.new WatchTowerError
27
- end
30
+ end
@@ -30,8 +30,10 @@ module WatchTower
30
30
  # Iterate over the files to fill the database
31
31
  files_paths.each do |file_path|
32
32
  begin
33
- next unless file_path && File.exists?(file_path)
34
33
  next if file_path =~ IGNORED_PATHS
34
+ next unless file_path
35
+ next unless File.exists?(file_path)
36
+ next unless File.file?(file_path)
35
37
  # Get the file_hash of the file
36
38
  file_hash = Digest::SHA1.file(file_path).hexdigest
37
39
  # Create a project from the file_path
@@ -91,4 +93,4 @@ module WatchTower
91
93
  start(options)
92
94
  end
93
95
  end
94
- end
96
+ end
@@ -4,7 +4,7 @@ module WatchTower
4
4
  MAJOR = 0
5
5
  MINOR = 0
6
6
  TINY = 1
7
- PRE = 'beta5'
7
+ PRE = 'beta6'
8
8
 
9
9
  def self.version
10
10
  # Init the version