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