forked from luna/vim-rana-local
add version 0.1.2
This commit is contained in:
parent
30e9697622
commit
4346a055e3
5 changed files with 395 additions and 3 deletions
20
LICENSE.txt
Normal file
20
LICENSE.txt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2013 Wakatime
|
||||||
|
https://wakatime.com
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
22
README.md
22
README.md
|
@ -1,4 +1,20 @@
|
||||||
vim-wakatime
|
vim-wakatime 0.1.2
|
||||||
============
|
===========
|
||||||
|
|
||||||
|
Automatic time tracking.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Get an api key by signing up at https://www.wakati.me
|
||||||
|
|
||||||
|
Using [Vundle](https://github.com/gmarik/vundle), the Vim plugin manager:
|
||||||
|
|
||||||
|
Add `Bundle 'wakatime/vim-wakatime' to your `~/.vimrc`
|
||||||
|
|
||||||
|
Then run these shell commands:
|
||||||
|
|
||||||
|
sudo touch /var/log/wakatime.log
|
||||||
|
echo "api_key=MY_API_KEY" > ~/.wakatime
|
||||||
|
vim +BundleInstall +qall
|
||||||
|
|
||||||
automatic time tracking for Vim
|
|
||||||
|
|
0
doc/wakatime.txt
Normal file
0
doc/wakatime.txt
Normal file
185
plugin/wakatime.py
Normal file
185
plugin/wakatime.py
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import platform
|
||||||
|
import urllib2
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import uuid
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
from collections import OrderedDict
|
||||||
|
from httplib import BadStatusLine, IncompleteRead
|
||||||
|
from urllib2 import HTTPError, URLError
|
||||||
|
import logging as log
|
||||||
|
|
||||||
|
|
||||||
|
# Config
|
||||||
|
version = '0.1.2'
|
||||||
|
user_agent = 'vim-wakatime/%s (%s)' % (version, platform.platform())
|
||||||
|
|
||||||
|
|
||||||
|
def project_from_path(path):
|
||||||
|
project = git_project(path)
|
||||||
|
if project:
|
||||||
|
return project
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def tags_from_path(path):
|
||||||
|
tags = []
|
||||||
|
if os.path.exists(path):
|
||||||
|
tags.extend(git_tags(path))
|
||||||
|
tags.extend(mercurial_tags(path))
|
||||||
|
return list(set(tags))
|
||||||
|
|
||||||
|
|
||||||
|
def git_project(path):
|
||||||
|
config_file = find_git_config(path)
|
||||||
|
if config_file:
|
||||||
|
folder = os.path.split(os.path.split(os.path.split(config_file)[0])[0])[1]
|
||||||
|
if folder:
|
||||||
|
return folder
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_git_config(path):
|
||||||
|
path = os.path.realpath(path)
|
||||||
|
log.info(path)
|
||||||
|
if os.path.isfile(path):
|
||||||
|
path = os.path.split(path)[0]
|
||||||
|
if os.path.isfile(os.path.join(path, '.git', 'config')):
|
||||||
|
return os.path.join(path, '.git', 'config')
|
||||||
|
split_path = os.path.split(path)
|
||||||
|
if split_path[1] == '':
|
||||||
|
return None
|
||||||
|
return find_git_config(split_path[0])
|
||||||
|
|
||||||
|
|
||||||
|
def parse_git_config(config):
|
||||||
|
sections = OrderedDict()
|
||||||
|
try:
|
||||||
|
f = open(config, 'r')
|
||||||
|
except IOError as e:
|
||||||
|
log.exception("Exception:")
|
||||||
|
else:
|
||||||
|
with f:
|
||||||
|
section = None
|
||||||
|
for line in f.readlines():
|
||||||
|
line = line.lstrip()
|
||||||
|
if len(line) > 0 and line[0] == '[':
|
||||||
|
section = line[1:].split(']', 1)[0]
|
||||||
|
temp = section.split(' ', 1)
|
||||||
|
section = temp[0].lower()
|
||||||
|
if len(temp) > 1:
|
||||||
|
section = ' '.join([section, temp[1]])
|
||||||
|
sections[section] = OrderedDict()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
(setting, value) = line.split('=', 1)
|
||||||
|
except ValueError:
|
||||||
|
setting = line.split('#', 1)[0].split(';', 1)[0]
|
||||||
|
value = 'true'
|
||||||
|
setting = setting.strip().lower()
|
||||||
|
value = value.split('#', 1)[0].split(';', 1)[0].strip()
|
||||||
|
sections[section][setting] = value
|
||||||
|
f.close()
|
||||||
|
return sections
|
||||||
|
|
||||||
|
|
||||||
|
def git_tags(path):
|
||||||
|
tags = []
|
||||||
|
config_file = find_git_config(path)
|
||||||
|
if config_file:
|
||||||
|
sections = parse_git_config(config_file)
|
||||||
|
for section in sections:
|
||||||
|
if section.split(' ', 1)[0] == 'remote' and 'url' in sections[section]:
|
||||||
|
tags.append(sections[section]['url'])
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
def mercurial_tags(path):
|
||||||
|
tags = []
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
def svn_tags(path):
|
||||||
|
tags = []
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
def log_action(**kwargs):
|
||||||
|
kwargs['User-Agent'] = user_agent
|
||||||
|
log.info(json.dumps(kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def send_action(key, instance, action, task, timestamp, project, tags):
|
||||||
|
url = 'https://www.wakati.me/api/v1/actions'
|
||||||
|
data = {
|
||||||
|
'type': action,
|
||||||
|
'task': task,
|
||||||
|
'time': time.time(),
|
||||||
|
'instance_id': instance,
|
||||||
|
'project': project,
|
||||||
|
'tags': tags,
|
||||||
|
}
|
||||||
|
if timestamp:
|
||||||
|
data['time'] = timestamp
|
||||||
|
request = urllib2.Request(url=url, data=json.dumps(data))
|
||||||
|
request.add_header('User-Agent', user_agent)
|
||||||
|
request.add_header('Content-Type', 'application/json')
|
||||||
|
request.add_header('Authorization', 'Basic %s' % base64.b64encode(key))
|
||||||
|
log_action(**data)
|
||||||
|
response = None
|
||||||
|
try:
|
||||||
|
response = urllib2.urlopen(request)
|
||||||
|
except HTTPError as ex:
|
||||||
|
log.error("%s:\ndata=%s\nresponse=%s" % (ex.getcode(), json.dumps(data), ex.read()))
|
||||||
|
if log.getLogger().isEnabledFor(log.DEBUG):
|
||||||
|
log.exception("Exception for %s:\n%s" % (data['time'], json.dumps(data)))
|
||||||
|
except (URLError, IncompleteRead, BadStatusLine) as ex:
|
||||||
|
log.error("%s:\ndata=%s\nmessage=%s" % (ex.__class__.__name__, json.dumps(data), ex))
|
||||||
|
if log.getLogger().isEnabledFor(log.DEBUG):
|
||||||
|
log.exception("Exception for %s:\n%s" % (data['time'], json.dumps(data)))
|
||||||
|
if response:
|
||||||
|
log.debug('response_code=%s response_content=%s' % (response.getcode(), response.read()))
|
||||||
|
if response and (response.getcode() == 200 or response.getcode() == 201):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(argv):
|
||||||
|
parser = argparse.ArgumentParser(description='Log time to the wakati.me api')
|
||||||
|
parser.add_argument('--key', dest='key', required=True,
|
||||||
|
help='your wakati.me api key')
|
||||||
|
parser.add_argument('--action', dest='action', required=True,
|
||||||
|
choices=['open_file', 'ping', 'close_file', 'write_file', 'open_editor', 'quit_editor', 'minimize_editor', 'maximize_editor', 'start', 'stop'])
|
||||||
|
parser.add_argument('--task', dest='task', required=True,
|
||||||
|
help='path to file or named task')
|
||||||
|
parser.add_argument('--instance', dest='instance', required=True,
|
||||||
|
help='the UUID4 representing the current editor')
|
||||||
|
parser.add_argument('--time', dest='timestamp', metavar='time', type=float,
|
||||||
|
help='optional floating-point timestamp in seconds')
|
||||||
|
parser.add_argument('--verbose', dest='verbose', action='store_true',
|
||||||
|
help='turns on debug messages in logfile')
|
||||||
|
parser.add_argument('--version', action='version', version=version)
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
args = parse_args(argv)
|
||||||
|
level = log.INFO
|
||||||
|
if args.verbose:
|
||||||
|
level = log.DEBUG
|
||||||
|
del args.verbose
|
||||||
|
log.basicConfig(filename='/var/log/wakatime.log', format='%(asctime)s vim-wakatime/'+version+' %(levelname)s %(message)s', datefmt='%Y-%m-%dT%H:%M:%SZ', level=level)
|
||||||
|
tags = tags_from_path(args.task)
|
||||||
|
project = project_from_path(args.task)
|
||||||
|
send_action(project=project, tags=tags, **vars(args))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
171
plugin/wakatime.vim
Normal file
171
plugin/wakatime.vim
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
" ============================================================================
|
||||||
|
" File: wakatime.vim
|
||||||
|
" Description: invisible time tracker using Wakati.Me
|
||||||
|
" Maintainer: Wakati.Me <support@wakatime.com>
|
||||||
|
" Version: 0.0.1
|
||||||
|
" ============================================================================
|
||||||
|
|
||||||
|
" Init {{{
|
||||||
|
|
||||||
|
" Check Vim version
|
||||||
|
if v:version < 700
|
||||||
|
echoerr "This plugin requires vim >= 7."
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
" Check for Python support
|
||||||
|
if !has('python')
|
||||||
|
echoerr "This plugin requires Vim to be compiled with Python support."
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
" Check for required user-defined settings
|
||||||
|
if !exists("g:wakatime_api_key")
|
||||||
|
if filereadable(expand("$HOME/.wakatime"))
|
||||||
|
for s:line in readfile(expand("$HOME/.wakatime"))
|
||||||
|
let s:setting = split(s:line, "=")
|
||||||
|
if s:setting[0] == "api_key"
|
||||||
|
let g:wakatime_api_key = s:setting[1]
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
endif
|
||||||
|
if !exists("g:wakatime_api_key")
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
" Only load plugin once
|
||||||
|
if exists("g:loaded_wakatime")
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
let g:loaded_wakatime = 1
|
||||||
|
|
||||||
|
" Backup & Override cpoptions
|
||||||
|
let s:old_cpo = &cpo
|
||||||
|
set cpo&vim
|
||||||
|
|
||||||
|
let s:plugin_directory = expand("<sfile>:p:h")
|
||||||
|
|
||||||
|
" Set a nice updatetime value, if updatetime is too short
|
||||||
|
if &updatetime < 60 * 1000 * 2
|
||||||
|
let &updatetime = 60 * 1000 * 15 " 15 minutes
|
||||||
|
endif
|
||||||
|
|
||||||
|
python << ENDPYTHON
|
||||||
|
import vim
|
||||||
|
import uuid
|
||||||
|
import time
|
||||||
|
|
||||||
|
instance_id = str(uuid.uuid4())
|
||||||
|
vim.command('let s:instance_id = "%s"' % instance_id)
|
||||||
|
ENDPYTHON
|
||||||
|
|
||||||
|
" }}}
|
||||||
|
|
||||||
|
" Function Definitions {{{
|
||||||
|
|
||||||
|
function! s:initVariable(var, value)
|
||||||
|
if !exists(a:var)
|
||||||
|
exec 'let ' . a:var . ' = ' . "'" . substitute(a:value, "'", "''", "g") . "'"
|
||||||
|
return 1
|
||||||
|
endif
|
||||||
|
return 0
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:GetCurrentFile()
|
||||||
|
return expand("%:p")
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:api(type, task)
|
||||||
|
exec "silent !python " . s:plugin_directory . "/wakatime.py --key" g:wakatime_api_key "--instance" s:instance_id "--action" a:type "--task" a:task . " &"
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:api_with_time(type, task, time)
|
||||||
|
exec "silent !python " . s:plugin_directory . "/wakatime.py --key" g:wakatime_api_key "--instance" s:instance_id "--action" a:type "--task" a:task "--time" printf("%f", a:time) . " &"
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:getchar()
|
||||||
|
let c = getchar()
|
||||||
|
if c =~ '^\d\+$'
|
||||||
|
let c = nr2char(c)
|
||||||
|
endif
|
||||||
|
return c
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" }}}
|
||||||
|
|
||||||
|
" Event Handlers {{{
|
||||||
|
|
||||||
|
function! s:bufenter()
|
||||||
|
let task = s:GetCurrentFile()
|
||||||
|
call s:api("open_file", shellescape(task))
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:bufleave()
|
||||||
|
let task = s:GetCurrentFile()
|
||||||
|
call s:api("close_file", shellescape(task))
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:vimenter()
|
||||||
|
let task = s:GetCurrentFile()
|
||||||
|
call s:api("open_editor", shellescape(task))
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:vimleave()
|
||||||
|
let task = s:GetCurrentFile()
|
||||||
|
call s:api("quit_editor", shellescape(task))
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:bufwrite()
|
||||||
|
let task = s:GetCurrentFile()
|
||||||
|
call s:api("write_file", shellescape(task))
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:cursorhold()
|
||||||
|
let s:away_task = s:GetCurrentFile()
|
||||||
|
python vim.command("let s:away_start=%f" % (time.time() - (float(vim.eval("&updatetime")) / 1000.0)))
|
||||||
|
autocmd Wakatime CursorMoved,CursorMovedI * call s:cursormoved()
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:cursormoved()
|
||||||
|
autocmd! Wakatime CursorMoved,CursorMovedI *
|
||||||
|
python vim.command("let away_end=%f" % time.time())
|
||||||
|
let away_unit = "minutes"
|
||||||
|
let away_duration = (away_end - s:away_start) / 60
|
||||||
|
if away_duration > 59
|
||||||
|
let away_duration = away_duration / 60
|
||||||
|
let away_unit = "hours"
|
||||||
|
endif
|
||||||
|
if away_duration > 59
|
||||||
|
let away_duration = away_duration / 60
|
||||||
|
let away_unit = "days"
|
||||||
|
endif
|
||||||
|
let answer = input(printf("You were away %.f %s. Add time to current file? (y/n)", away_duration, away_unit))
|
||||||
|
if answer != "y"
|
||||||
|
call s:api_with_time("minimize_editor", shellescape(s:away_task), s:away_start)
|
||||||
|
call s:api_with_time("maximize_editor", shellescape(s:away_task), away_end)
|
||||||
|
let s:away_start = 0
|
||||||
|
else
|
||||||
|
call s:api("ping", shellescape(s:away_task))
|
||||||
|
endif
|
||||||
|
"redraw!
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" }}}
|
||||||
|
|
||||||
|
" Autocommand Events {{{
|
||||||
|
|
||||||
|
augroup Wakatime
|
||||||
|
autocmd!
|
||||||
|
autocmd BufEnter * call s:bufenter()
|
||||||
|
autocmd BufLeave * call s:bufleave()
|
||||||
|
autocmd VimEnter * call s:vimenter()
|
||||||
|
autocmd VimLeave * call s:vimleave()
|
||||||
|
autocmd BufWritePost * call s:bufwrite()
|
||||||
|
autocmd CursorHold,CursorHoldI * call s:cursorhold()
|
||||||
|
augroup END
|
||||||
|
|
||||||
|
" }}}
|
||||||
|
|
||||||
|
" Restore cpoptions
|
||||||
|
let &cpo = s:old_cpo
|
Loading…
Reference in a new issue