upgrade wakatime-cli to v10.2.4
This commit is contained in:
parent
f61a34eda7
commit
360a491cda
13 changed files with 319 additions and 142 deletions
|
@ -1,7 +1,7 @@
|
||||||
__title__ = 'wakatime'
|
__title__ = 'wakatime'
|
||||||
__description__ = 'Common interface to the WakaTime api.'
|
__description__ = 'Common interface to the WakaTime api.'
|
||||||
__url__ = 'https://github.com/wakatime/wakatime'
|
__url__ = 'https://github.com/wakatime/wakatime'
|
||||||
__version_info__ = ('10', '2', '1')
|
__version_info__ = ('10', '2', '4')
|
||||||
__version__ = '.'.join(__version_info__)
|
__version__ = '.'.join(__version_info__)
|
||||||
__author__ = 'Alan Hamlett'
|
__author__ = 'Alan Hamlett'
|
||||||
__author_email__ = 'alan@wakatime.com'
|
__author_email__ = 'alan@wakatime.com'
|
||||||
|
|
|
@ -20,7 +20,7 @@ import traceback
|
||||||
from .__about__ import __version__
|
from .__about__ import __version__
|
||||||
from .compat import basestring
|
from .compat import basestring
|
||||||
from .configs import parseConfigFile
|
from .configs import parseConfigFile
|
||||||
from .constants import AUTH_ERROR
|
from .constants import AUTH_ERROR, DEFAULT_SYNC_OFFLINE_ACTIVITY
|
||||||
from .packages import argparse
|
from .packages import argparse
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,6 +116,12 @@ def parse_arguments():
|
||||||
action=StoreWithoutQuotes,
|
action=StoreWithoutQuotes,
|
||||||
help='Optional language name. If valid, takes ' +
|
help='Optional language name. If valid, takes ' +
|
||||||
'priority over auto-detected language.')
|
'priority over auto-detected language.')
|
||||||
|
parser.add_argument('--local-file', dest='local_file', metavar='FILE',
|
||||||
|
action=FileAction,
|
||||||
|
help='Absolute path to local file for the ' +
|
||||||
|
'heartbeat. When --entity is a remote file, ' +
|
||||||
|
'this local file will be used for stats and ' +
|
||||||
|
'just the value of --entity sent with heartbeat.')
|
||||||
parser.add_argument('--hostname', dest='hostname',
|
parser.add_argument('--hostname', dest='hostname',
|
||||||
action=StoreWithoutQuotes,
|
action=StoreWithoutQuotes,
|
||||||
help='Hostname of current machine.')
|
help='Hostname of current machine.')
|
||||||
|
@ -126,13 +132,23 @@ def parse_arguments():
|
||||||
parser.add_argument('--disableoffline', dest='offline_deprecated',
|
parser.add_argument('--disableoffline', dest='offline_deprecated',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help=argparse.SUPPRESS)
|
help=argparse.SUPPRESS)
|
||||||
parser.add_argument('--hide-filenames', dest='hide_filenames',
|
parser.add_argument('--hide-file-names', dest='hide_file_names',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Obfuscate filenames. Will not send file names ' +
|
help='Obfuscate filenames. Will not send file names ' +
|
||||||
'to api.')
|
'to api.')
|
||||||
|
parser.add_argument('--hide-filenames', dest='hide_filenames',
|
||||||
|
action='store_true',
|
||||||
|
help=argparse.SUPPRESS)
|
||||||
parser.add_argument('--hidefilenames', dest='hidefilenames',
|
parser.add_argument('--hidefilenames', dest='hidefilenames',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help=argparse.SUPPRESS)
|
help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument('--hide-project-names', dest='hide_project_names',
|
||||||
|
action='store_true',
|
||||||
|
help='Obfuscate project names. When a project ' +
|
||||||
|
'folder is detected instead of using the ' +
|
||||||
|
'folder name as the project, a ' +
|
||||||
|
'.wakatime-project file is created with a ' +
|
||||||
|
'random project name.')
|
||||||
parser.add_argument('--exclude', dest='exclude', action='append',
|
parser.add_argument('--exclude', dest='exclude', action='append',
|
||||||
help='Filename patterns to exclude from logging. ' +
|
help='Filename patterns to exclude from logging. ' +
|
||||||
'POSIX regex syntax. Can be used more than once.')
|
'POSIX regex syntax. Can be used more than once.')
|
||||||
|
@ -170,6 +186,17 @@ def parse_arguments():
|
||||||
action=StoreWithoutQuotes,
|
action=StoreWithoutQuotes,
|
||||||
help='Number of seconds to wait when sending ' +
|
help='Number of seconds to wait when sending ' +
|
||||||
'heartbeats to api. Defaults to 60 seconds.')
|
'heartbeats to api. Defaults to 60 seconds.')
|
||||||
|
parser.add_argument('--sync-offline-activity',
|
||||||
|
dest='sync_offline_activity',
|
||||||
|
action=StoreWithoutQuotes,
|
||||||
|
help='Amount of offline activity to sync from your ' +
|
||||||
|
'local ~/.wakatime.db sqlite3 file to your ' +
|
||||||
|
'WakaTime Dashboard before exiting. Can be ' +
|
||||||
|
'"none" or a positive integer number. Defaults ' +
|
||||||
|
'to 5, meaning for every heartbeat sent while ' +
|
||||||
|
'online 5 offline heartbeats are synced. Can ' +
|
||||||
|
'be used without --entity to only sync offline ' +
|
||||||
|
'activity without generating new heartbeats.')
|
||||||
parser.add_argument('--config', dest='config', action=StoreWithoutQuotes,
|
parser.add_argument('--config', dest='config', action=StoreWithoutQuotes,
|
||||||
help='Defaults to ~/.wakatime.cfg.')
|
help='Defaults to ~/.wakatime.cfg.')
|
||||||
parser.add_argument('--verbose', dest='verbose', action='store_true',
|
parser.add_argument('--verbose', dest='verbose', action='store_true',
|
||||||
|
@ -214,9 +241,20 @@ def parse_arguments():
|
||||||
if not args.entity:
|
if not args.entity:
|
||||||
if args.file:
|
if args.file:
|
||||||
args.entity = args.file
|
args.entity = args.file
|
||||||
else:
|
elif not args.sync_offline_activity or args.sync_offline_activity == 'none':
|
||||||
parser.error('argument --entity is required')
|
parser.error('argument --entity is required')
|
||||||
|
|
||||||
|
if not args.sync_offline_activity:
|
||||||
|
args.sync_offline_activity = DEFAULT_SYNC_OFFLINE_ACTIVITY
|
||||||
|
if args.sync_offline_activity == 'none':
|
||||||
|
args.sync_offline_activity = 0
|
||||||
|
try:
|
||||||
|
args.sync_offline_activity = int(args.sync_offline_activity)
|
||||||
|
if args.sync_offline_activity < 0:
|
||||||
|
raise Exception('Error')
|
||||||
|
except:
|
||||||
|
parser.error('argument --sync-offline-activity must be "none" or an integer number')
|
||||||
|
|
||||||
if not args.language and args.alternate_language:
|
if not args.language and args.alternate_language:
|
||||||
args.language = args.alternate_language
|
args.language = args.alternate_language
|
||||||
|
|
||||||
|
@ -249,24 +287,8 @@ def parse_arguments():
|
||||||
pass
|
pass
|
||||||
if not args.exclude_unknown_project and configs.has_option('settings', 'exclude_unknown_project'):
|
if not args.exclude_unknown_project and configs.has_option('settings', 'exclude_unknown_project'):
|
||||||
args.exclude_unknown_project = configs.getboolean('settings', 'exclude_unknown_project')
|
args.exclude_unknown_project = configs.getboolean('settings', 'exclude_unknown_project')
|
||||||
if not args.hide_filenames and args.hidefilenames:
|
boolean_or_list('hide_file_names', args, configs, alternative_names=['hide_filenames', 'hidefilenames'])
|
||||||
args.hide_filenames = args.hidefilenames
|
boolean_or_list('hide_project_names', args, configs, alternative_names=['hide_projectnames', 'hideprojectnames'])
|
||||||
if args.hide_filenames:
|
|
||||||
args.hide_filenames = ['.*']
|
|
||||||
else:
|
|
||||||
args.hide_filenames = []
|
|
||||||
option = None
|
|
||||||
if configs.has_option('settings', 'hidefilenames'):
|
|
||||||
option = configs.get('settings', 'hidefilenames')
|
|
||||||
if configs.has_option('settings', 'hide_filenames'):
|
|
||||||
option = configs.get('settings', 'hide_filenames')
|
|
||||||
if option is not None:
|
|
||||||
if option.strip().lower() == 'true':
|
|
||||||
args.hide_filenames = ['.*']
|
|
||||||
elif option.strip().lower() != 'false':
|
|
||||||
for pattern in option.split("\n"):
|
|
||||||
if pattern.strip() != '':
|
|
||||||
args.hide_filenames.append(pattern)
|
|
||||||
if args.offline_deprecated:
|
if args.offline_deprecated:
|
||||||
args.offline = False
|
args.offline = False
|
||||||
if args.offline and configs.has_option('settings', 'offline'):
|
if args.offline and configs.has_option('settings', 'offline'):
|
||||||
|
@ -307,3 +329,30 @@ def parse_arguments():
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
|
|
||||||
return args, configs
|
return args, configs
|
||||||
|
|
||||||
|
|
||||||
|
def boolean_or_list(config_name, args, configs, alternative_names=[]):
|
||||||
|
"""Get a boolean or list of regexes from args and configs."""
|
||||||
|
|
||||||
|
# when argument flag present, set to wildcard regex
|
||||||
|
for key in alternative_names:
|
||||||
|
if hasattr(args, key) and getattr(args, key):
|
||||||
|
setattr(args, config_name, ['.*'])
|
||||||
|
return
|
||||||
|
|
||||||
|
setattr(args, config_name, [])
|
||||||
|
|
||||||
|
option = None
|
||||||
|
alternative_names.insert(0, config_name)
|
||||||
|
for key in alternative_names:
|
||||||
|
if configs.has_option('settings', key):
|
||||||
|
option = configs.get('settings', key)
|
||||||
|
break
|
||||||
|
|
||||||
|
if option is not None:
|
||||||
|
if option.strip().lower() == 'true':
|
||||||
|
setattr(args, config_name, ['.*'])
|
||||||
|
elif option.strip().lower() != 'false':
|
||||||
|
for pattern in option.split("\n"):
|
||||||
|
if pattern.strip() != '':
|
||||||
|
getattr(args, config_name).append(pattern)
|
||||||
|
|
|
@ -45,3 +45,12 @@ Files larger than this in bytes will not have a line count stat for performance.
|
||||||
Default is 2MB.
|
Default is 2MB.
|
||||||
"""
|
"""
|
||||||
MAX_FILE_SIZE_SUPPORTED = 2000000
|
MAX_FILE_SIZE_SUPPORTED = 2000000
|
||||||
|
|
||||||
|
""" Default number of offline heartbeats to sync before exiting."""
|
||||||
|
DEFAULT_SYNC_OFFLINE_ACTIVITY = 100
|
||||||
|
|
||||||
|
""" Number of heartbeats per api request.
|
||||||
|
Even when sending more heartbeats, this is the number of heartbeats sent per
|
||||||
|
individual https request to the WakaTime API.
|
||||||
|
"""
|
||||||
|
HEARTBEATS_PER_REQUEST = 10
|
||||||
|
|
|
@ -42,6 +42,8 @@ class Heartbeat(object):
|
||||||
cursorpos = None
|
cursorpos = None
|
||||||
user_agent = None
|
user_agent = None
|
||||||
|
|
||||||
|
_sensitive = ('dependencies', 'lines', 'lineno', 'cursorpos', 'branch')
|
||||||
|
|
||||||
def __init__(self, data, args, configs, _clone=None):
|
def __init__(self, data, args, configs, _clone=None):
|
||||||
if not data:
|
if not data:
|
||||||
self.skip = u('Skipping because heartbeat data is missing.')
|
self.skip = u('Skipping because heartbeat data is missing.')
|
||||||
|
@ -83,13 +85,16 @@ class Heartbeat(object):
|
||||||
return
|
return
|
||||||
if self.type == 'file':
|
if self.type == 'file':
|
||||||
self.entity = format_file_path(self.entity)
|
self.entity = format_file_path(self.entity)
|
||||||
if not self.entity or not os.path.isfile(self.entity):
|
if not self._file_exists():
|
||||||
self.skip = u('File does not exist; ignoring this heartbeat.')
|
self.skip = u('File does not exist; ignoring this heartbeat.')
|
||||||
return
|
return
|
||||||
if self._excluded_by_missing_project_file():
|
if self._excluded_by_missing_project_file():
|
||||||
self.skip = u('Skipping because missing .wakatime-project file in parent path.')
|
self.skip = u('Skipping because missing .wakatime-project file in parent path.')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if args.local_file and not os.path.isfile(args.local_file):
|
||||||
|
args.local_file = None
|
||||||
|
|
||||||
project, branch = get_project_info(configs, self, data)
|
project, branch = get_project_info(configs, self, data)
|
||||||
self.project = project
|
self.project = project
|
||||||
self.branch = branch
|
self.branch = branch
|
||||||
|
@ -104,7 +109,8 @@ class Heartbeat(object):
|
||||||
lineno=data.get('lineno'),
|
lineno=data.get('lineno'),
|
||||||
cursorpos=data.get('cursorpos'),
|
cursorpos=data.get('cursorpos'),
|
||||||
plugin=args.plugin,
|
plugin=args.plugin,
|
||||||
language=data.get('language'))
|
language=data.get('language'),
|
||||||
|
local_file=args.local_file)
|
||||||
except SkipHeartbeat as ex:
|
except SkipHeartbeat as ex:
|
||||||
self.skip = u(ex) or 'Skipping'
|
self.skip = u(ex) or 'Skipping'
|
||||||
return
|
return
|
||||||
|
@ -132,7 +138,7 @@ class Heartbeat(object):
|
||||||
Returns a Heartbeat.
|
Returns a Heartbeat.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.args.hide_filenames:
|
if not self.args.hide_file_names:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
if self.entity is None:
|
if self.entity is None:
|
||||||
|
@ -141,29 +147,12 @@ class Heartbeat(object):
|
||||||
if self.type != 'file':
|
if self.type != 'file':
|
||||||
return self
|
return self
|
||||||
|
|
||||||
for pattern in self.args.hide_filenames:
|
if self.should_obfuscate_filename():
|
||||||
try:
|
self._sanitize_metadata()
|
||||||
compiled = re.compile(pattern, re.IGNORECASE)
|
|
||||||
if compiled.search(self.entity):
|
|
||||||
|
|
||||||
sanitized = {}
|
|
||||||
sensitive = ['dependencies', 'lines', 'lineno', 'cursorpos', 'branch']
|
|
||||||
for key, val in self.items():
|
|
||||||
if key in sensitive:
|
|
||||||
sanitized[key] = None
|
|
||||||
else:
|
|
||||||
sanitized[key] = val
|
|
||||||
|
|
||||||
extension = u(os.path.splitext(self.entity)[1])
|
extension = u(os.path.splitext(self.entity)[1])
|
||||||
sanitized['entity'] = u('HIDDEN{0}').format(extension)
|
self.entity = u('HIDDEN{0}').format(extension)
|
||||||
|
elif self.should_obfuscate_project():
|
||||||
return self.update(sanitized)
|
self._sanitize_metadata()
|
||||||
|
|
||||||
except re.error as ex:
|
|
||||||
log.warning(u('Regex error ({msg}) for include pattern: {pattern}').format(
|
|
||||||
msg=u(ex),
|
|
||||||
pattern=u(pattern),
|
|
||||||
))
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -201,6 +190,38 @@ class Heartbeat(object):
|
||||||
is_write=self.is_write,
|
is_write=self.is_write,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def should_obfuscate_filename(self):
|
||||||
|
"""Returns True if hide_file_names is true or the entity file path
|
||||||
|
matches one in the list of obfuscated file paths."""
|
||||||
|
|
||||||
|
for pattern in self.args.hide_file_names:
|
||||||
|
try:
|
||||||
|
compiled = re.compile(pattern, re.IGNORECASE)
|
||||||
|
if compiled.search(self.entity):
|
||||||
|
return True
|
||||||
|
except re.error as ex:
|
||||||
|
log.warning(u('Regex error ({msg}) for hide_file_names pattern: {pattern}').format(
|
||||||
|
msg=u(ex),
|
||||||
|
pattern=u(pattern),
|
||||||
|
))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def should_obfuscate_project(self):
|
||||||
|
"""Returns True if hide_project_names is true or the entity file path
|
||||||
|
matches one in the list of obfuscated project paths."""
|
||||||
|
|
||||||
|
for pattern in self.args.hide_project_names:
|
||||||
|
try:
|
||||||
|
compiled = re.compile(pattern, re.IGNORECASE)
|
||||||
|
if compiled.search(self.entity):
|
||||||
|
return True
|
||||||
|
except re.error as ex:
|
||||||
|
log.warning(u('Regex error ({msg}) for hide_project_names pattern: {pattern}').format(
|
||||||
|
msg=u(ex),
|
||||||
|
pattern=u(pattern),
|
||||||
|
))
|
||||||
|
return False
|
||||||
|
|
||||||
def _unicode(self, value):
|
def _unicode(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
@ -211,6 +232,10 @@ class Heartbeat(object):
|
||||||
return None
|
return None
|
||||||
return [self._unicode(value) for value in values]
|
return [self._unicode(value) for value in values]
|
||||||
|
|
||||||
|
def _file_exists(self):
|
||||||
|
return (self.entity and os.path.isfile(self.entity) or
|
||||||
|
self.args.local_file and os.path.isfile(self.args.local_file))
|
||||||
|
|
||||||
def _excluded_by_pattern(self):
|
def _excluded_by_pattern(self):
|
||||||
return should_exclude(self.entity, self.args.include, self.args.exclude)
|
return should_exclude(self.entity, self.args.include, self.args.exclude)
|
||||||
|
|
||||||
|
@ -224,6 +249,10 @@ class Heartbeat(object):
|
||||||
return False
|
return False
|
||||||
return find_project_file(self.entity) is None
|
return find_project_file(self.entity) is None
|
||||||
|
|
||||||
|
def _sanitize_metadata(self):
|
||||||
|
for key in self._sensitive:
|
||||||
|
setattr(self, key, None)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.json()
|
return self.json()
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
"f#": "F#",
|
"f#": "F#",
|
||||||
"fortran": "Fortran",
|
"fortran": "Fortran",
|
||||||
"go": "Go",
|
"go": "Go",
|
||||||
"gous": "Gosu",
|
"gosu": "Gosu",
|
||||||
"groovy": "Groovy",
|
"groovy": "Groovy",
|
||||||
"haml": "Haml",
|
"haml": "Haml",
|
||||||
"haskell": "Haskell",
|
"haskell": "Haskell",
|
||||||
|
|
|
@ -24,7 +24,7 @@ from .__about__ import __version__
|
||||||
from .api import send_heartbeats
|
from .api import send_heartbeats
|
||||||
from .arguments import parse_arguments
|
from .arguments import parse_arguments
|
||||||
from .compat import u, json
|
from .compat import u, json
|
||||||
from .constants import SUCCESS, UNKNOWN_ERROR
|
from .constants import SUCCESS, UNKNOWN_ERROR, HEARTBEATS_PER_REQUEST
|
||||||
from .logger import setup_logging
|
from .logger import setup_logging
|
||||||
|
|
||||||
log = logging.getLogger('WakaTime')
|
log = logging.getLogger('WakaTime')
|
||||||
|
@ -63,12 +63,22 @@ def execute(argv=None):
|
||||||
msg=u(ex),
|
msg=u(ex),
|
||||||
))
|
))
|
||||||
|
|
||||||
retval = send_heartbeats(heartbeats, args, configs)
|
retval = SUCCESS
|
||||||
|
while heartbeats:
|
||||||
|
retval = send_heartbeats(heartbeats[:HEARTBEATS_PER_REQUEST], args, configs)
|
||||||
|
heartbeats = heartbeats[HEARTBEATS_PER_REQUEST:]
|
||||||
|
if retval != SUCCESS:
|
||||||
|
break
|
||||||
|
|
||||||
|
if heartbeats:
|
||||||
|
Queue(args, configs).push_many(heartbeats)
|
||||||
|
|
||||||
if retval == SUCCESS:
|
if retval == SUCCESS:
|
||||||
queue = Queue(args, configs)
|
queue = Queue(args, configs)
|
||||||
offline_heartbeats = queue.pop_many()
|
for offline_heartbeats in queue.pop_many(args.sync_offline_activity):
|
||||||
if len(offline_heartbeats) > 0:
|
|
||||||
retval = send_heartbeats(offline_heartbeats, args, configs)
|
retval = send_heartbeats(offline_heartbeats, args, configs)
|
||||||
|
if retval != SUCCESS:
|
||||||
|
break
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import os
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from .compat import json
|
from .compat import json
|
||||||
|
from .constants import DEFAULT_SYNC_OFFLINE_ACTIVITY, HEARTBEATS_PER_REQUEST
|
||||||
from .heartbeat import Heartbeat
|
from .heartbeat import Heartbeat
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,19 +105,23 @@ class Queue(object):
|
||||||
|
|
||||||
def pop_many(self, limit=None):
|
def pop_many(self, limit=None):
|
||||||
if limit is None:
|
if limit is None:
|
||||||
limit = 5
|
limit = DEFAULT_SYNC_OFFLINE_ACTIVITY
|
||||||
|
|
||||||
heartbeats = []
|
heartbeats = []
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
while limit == 0 or count < limit:
|
while count < limit:
|
||||||
heartbeat = self.pop()
|
heartbeat = self.pop()
|
||||||
if not heartbeat:
|
if not heartbeat:
|
||||||
break
|
break
|
||||||
heartbeats.append(heartbeat)
|
heartbeats.append(heartbeat)
|
||||||
count += 1
|
count += 1
|
||||||
|
if count % HEARTBEATS_PER_REQUEST == 0:
|
||||||
|
yield heartbeats
|
||||||
|
heartbeats = []
|
||||||
|
|
||||||
return heartbeats
|
if heartbeats:
|
||||||
|
yield heartbeats
|
||||||
|
|
||||||
def _get_db_file(self):
|
def _get_db_file(self):
|
||||||
home = '~'
|
home = '~'
|
||||||
|
|
|
@ -9,8 +9,11 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
|
|
||||||
|
from .compat import open
|
||||||
from .projects.git import Git
|
from .projects.git import Git
|
||||||
from .projects.mercurial import Mercurial
|
from .projects.mercurial import Mercurial
|
||||||
from .projects.projectfile import ProjectFile
|
from .projects.projectfile import ProjectFile
|
||||||
|
@ -65,6 +68,8 @@ def get_project_info(configs, heartbeat, data):
|
||||||
if project_name is None:
|
if project_name is None:
|
||||||
project_name = data.get('project') or heartbeat.args.project
|
project_name = data.get('project') or heartbeat.args.project
|
||||||
|
|
||||||
|
hide_project = heartbeat.should_obfuscate_project()
|
||||||
|
|
||||||
if project_name is None or branch_name is None:
|
if project_name is None or branch_name is None:
|
||||||
|
|
||||||
for plugin_cls in REV_CONTROL_PLUGINS:
|
for plugin_cls in REV_CONTROL_PLUGINS:
|
||||||
|
@ -76,9 +81,18 @@ def get_project_info(configs, heartbeat, data):
|
||||||
if project.process():
|
if project.process():
|
||||||
project_name = project_name or project.name()
|
project_name = project_name or project.name()
|
||||||
branch_name = branch_name or project.branch()
|
branch_name = branch_name or project.branch()
|
||||||
|
if hide_project:
|
||||||
|
branch_name = None
|
||||||
|
project_name = generate_project_name()
|
||||||
|
project_file = os.path.join(project.folder(), '.wakatime-project')
|
||||||
|
try:
|
||||||
|
with open(project_file, 'w') as fh:
|
||||||
|
fh.write(project_name)
|
||||||
|
except IOError:
|
||||||
|
project_name = None
|
||||||
break
|
break
|
||||||
|
|
||||||
if project_name is None:
|
if project_name is None and not hide_project:
|
||||||
project_name = data.get('alternate_project') or heartbeat.args.alternate_project
|
project_name = data.get('alternate_project') or heartbeat.args.alternate_project
|
||||||
|
|
||||||
return project_name, branch_name
|
return project_name, branch_name
|
||||||
|
@ -88,3 +102,42 @@ def get_configs_for_plugin(plugin_name, configs):
|
||||||
if configs and configs.has_section(plugin_name):
|
if configs and configs.has_section(plugin_name):
|
||||||
return dict(configs.items(plugin_name))
|
return dict(configs.items(plugin_name))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_project_name():
|
||||||
|
"""Generates a random project name."""
|
||||||
|
|
||||||
|
adjectives = [
|
||||||
|
'aged', 'ancient', 'autumn', 'billowing', 'bitter', 'black', 'blue', 'bold',
|
||||||
|
'broad', 'broken', 'calm', 'cold', 'cool', 'crimson', 'curly', 'damp',
|
||||||
|
'dark', 'dawn', 'delicate', 'divine', 'dry', 'empty', 'falling', 'fancy',
|
||||||
|
'flat', 'floral', 'fragrant', 'frosty', 'gentle', 'green', 'hidden', 'holy',
|
||||||
|
'icy', 'jolly', 'late', 'lingering', 'little', 'lively', 'long', 'lucky',
|
||||||
|
'misty', 'morning', 'muddy', 'mute', 'nameless', 'noisy', 'odd', 'old',
|
||||||
|
'orange', 'patient', 'plain', 'polished', 'proud', 'purple', 'quiet', 'rapid',
|
||||||
|
'raspy', 'red', 'restless', 'rough', 'round', 'royal', 'shiny', 'shrill',
|
||||||
|
'shy', 'silent', 'small', 'snowy', 'soft', 'solitary', 'sparkling', 'spring',
|
||||||
|
'square', 'steep', 'still', 'summer', 'super', 'sweet', 'throbbing', 'tight',
|
||||||
|
'tiny', 'twilight', 'wandering', 'weathered', 'white', 'wild', 'winter', 'wispy',
|
||||||
|
'withered', 'yellow', 'young'
|
||||||
|
]
|
||||||
|
nouns = [
|
||||||
|
'art', 'band', 'bar', 'base', 'bird', 'block', 'boat', 'bonus',
|
||||||
|
'bread', 'breeze', 'brook', 'bush', 'butterfly', 'cake', 'cell', 'cherry',
|
||||||
|
'cloud', 'credit', 'darkness', 'dawn', 'dew', 'disk', 'dream', 'dust',
|
||||||
|
'feather', 'field', 'fire', 'firefly', 'flower', 'fog', 'forest', 'frog',
|
||||||
|
'frost', 'glade', 'glitter', 'grass', 'hall', 'hat', 'haze', 'heart',
|
||||||
|
'hill', 'king', 'lab', 'lake', 'leaf', 'limit', 'math', 'meadow',
|
||||||
|
'mode', 'moon', 'morning', 'mountain', 'mouse', 'mud', 'night', 'paper',
|
||||||
|
'pine', 'poetry', 'pond', 'queen', 'rain', 'recipe', 'resonance', 'rice',
|
||||||
|
'river', 'salad', 'scene', 'sea', 'shadow', 'shape', 'silence', 'sky',
|
||||||
|
'smoke', 'snow', 'snowflake', 'sound', 'star', 'sun', 'sun', 'sunset',
|
||||||
|
'surf', 'term', 'thunder', 'tooth', 'tree', 'truth', 'union', 'unit',
|
||||||
|
'violet', 'voice', 'water', 'waterfall', 'wave', 'wildflower', 'wind', 'wood'
|
||||||
|
]
|
||||||
|
numbers = [str(x) for x in range(10)]
|
||||||
|
return ' '.join([
|
||||||
|
random.choice(adjectives).capitalize(),
|
||||||
|
random.choice(nouns).capitalize(),
|
||||||
|
random.choice(numbers) + random.choice(numbers),
|
||||||
|
])
|
||||||
|
|
|
@ -43,3 +43,8 @@ class BaseProject(object):
|
||||||
""" Returns the current branch.
|
""" Returns the current branch.
|
||||||
"""
|
"""
|
||||||
raise NotYetImplemented()
|
raise NotYetImplemented()
|
||||||
|
|
||||||
|
def folder(self):
|
||||||
|
""" Returns the project's top folder path.
|
||||||
|
"""
|
||||||
|
raise NotYetImplemented()
|
||||||
|
|
|
@ -25,6 +25,7 @@ class Git(BaseProject):
|
||||||
_submodule = None
|
_submodule = None
|
||||||
_project_name = None
|
_project_name = None
|
||||||
_head_file = None
|
_head_file = None
|
||||||
|
_project_folder = None
|
||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
return self._find_git_config_file(self.path)
|
return self._find_git_config_file(self.path)
|
||||||
|
@ -40,6 +41,9 @@ class Git(BaseProject):
|
||||||
return self._get_branch_from_head_file(line)
|
return self._get_branch_from_head_file(line)
|
||||||
return u('master')
|
return u('master')
|
||||||
|
|
||||||
|
def folder(self):
|
||||||
|
return self._project_folder
|
||||||
|
|
||||||
def _find_git_config_file(self, path):
|
def _find_git_config_file(self, path):
|
||||||
path = os.path.realpath(path)
|
path = os.path.realpath(path)
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
|
@ -47,6 +51,7 @@ class Git(BaseProject):
|
||||||
if os.path.isfile(os.path.join(path, '.git', 'config')):
|
if os.path.isfile(os.path.join(path, '.git', 'config')):
|
||||||
self._project_name = os.path.basename(path)
|
self._project_name = os.path.basename(path)
|
||||||
self._head_file = os.path.join(path, '.git', 'HEAD')
|
self._head_file = os.path.join(path, '.git', 'HEAD')
|
||||||
|
self._project_folder = path
|
||||||
return True
|
return True
|
||||||
|
|
||||||
link_path = self._path_from_gitdir_link_file(path)
|
link_path = self._path_from_gitdir_link_file(path)
|
||||||
|
@ -56,12 +61,14 @@ class Git(BaseProject):
|
||||||
if self._is_worktree(link_path):
|
if self._is_worktree(link_path):
|
||||||
self._project_name = self._project_from_worktree(link_path)
|
self._project_name = self._project_from_worktree(link_path)
|
||||||
self._head_file = os.path.join(link_path, 'HEAD')
|
self._head_file = os.path.join(link_path, 'HEAD')
|
||||||
|
self._project_folder = path
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# next check if this is a submodule
|
# next check if this is a submodule
|
||||||
if self._submodules_supported_for_path(path):
|
if self._submodules_supported_for_path(path):
|
||||||
self._project_name = os.path.basename(path)
|
self._project_name = os.path.basename(path)
|
||||||
self._head_file = os.path.join(link_path, 'HEAD')
|
self._head_file = os.path.join(link_path, 'HEAD')
|
||||||
|
self._project_folder = path
|
||||||
return True
|
return True
|
||||||
|
|
||||||
split_path = os.path.split(path)
|
split_path = os.path.split(path)
|
||||||
|
|
|
@ -47,6 +47,11 @@ class Mercurial(BaseProject):
|
||||||
log.traceback(logging.WARNING)
|
log.traceback(logging.WARNING)
|
||||||
return u('default')
|
return u('default')
|
||||||
|
|
||||||
|
def folder(self):
|
||||||
|
if self.configDir:
|
||||||
|
return os.path.dirname(self.configDir)
|
||||||
|
return None
|
||||||
|
|
||||||
def _find_hg_config_dir(self, path):
|
def _find_hg_config_dir(self, path):
|
||||||
path = os.path.realpath(path)
|
path = os.path.realpath(path)
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
|
|
|
@ -41,6 +41,11 @@ class Subversion(BaseProject):
|
||||||
return None # pragma: nocover
|
return None # pragma: nocover
|
||||||
return u(self.info['URL'].split('/')[-1].split('\\')[-1])
|
return u(self.info['URL'].split('/')[-1].split('\\')[-1])
|
||||||
|
|
||||||
|
def folder(self):
|
||||||
|
if 'Repository Root' not in self.info:
|
||||||
|
return None
|
||||||
|
return self.info['Repository Root']
|
||||||
|
|
||||||
def _find_binary(self):
|
def _find_binary(self):
|
||||||
if self.binary_location:
|
if self.binary_location:
|
||||||
return self.binary_location
|
return self.binary_location
|
||||||
|
|
|
@ -40,7 +40,7 @@ log = logging.getLogger('WakaTime')
|
||||||
|
|
||||||
|
|
||||||
def get_file_stats(file_name, entity_type='file', lineno=None, cursorpos=None,
|
def get_file_stats(file_name, entity_type='file', lineno=None, cursorpos=None,
|
||||||
plugin=None, language=None):
|
plugin=None, language=None, local_file=None):
|
||||||
if entity_type != 'file':
|
if entity_type != 'file':
|
||||||
stats = {
|
stats = {
|
||||||
'language': None,
|
'language': None,
|
||||||
|
@ -52,24 +52,24 @@ def get_file_stats(file_name, entity_type='file', lineno=None, cursorpos=None,
|
||||||
else:
|
else:
|
||||||
language, lexer = standardize_language(language, plugin)
|
language, lexer = standardize_language(language, plugin)
|
||||||
if not language:
|
if not language:
|
||||||
language, lexer = guess_language(file_name)
|
language, lexer = guess_language(file_name, local_file)
|
||||||
|
|
||||||
language = use_root_language(language, lexer)
|
language = use_root_language(language, lexer)
|
||||||
|
|
||||||
parser = DependencyParser(file_name, lexer)
|
parser = DependencyParser(local_file or file_name, lexer)
|
||||||
dependencies = parser.parse()
|
dependencies = parser.parse()
|
||||||
|
|
||||||
stats = {
|
stats = {
|
||||||
'language': language,
|
'language': language,
|
||||||
'dependencies': dependencies,
|
'dependencies': dependencies,
|
||||||
'lines': number_lines_in_file(file_name),
|
'lines': number_lines_in_file(local_file or file_name),
|
||||||
'lineno': lineno,
|
'lineno': lineno,
|
||||||
'cursorpos': cursorpos,
|
'cursorpos': cursorpos,
|
||||||
}
|
}
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
|
||||||
def guess_language(file_name):
|
def guess_language(file_name, local_file):
|
||||||
"""Guess lexer and language for a file.
|
"""Guess lexer and language for a file.
|
||||||
|
|
||||||
Returns a tuple of (language_str, lexer_obj).
|
Returns a tuple of (language_str, lexer_obj).
|
||||||
|
@ -81,14 +81,14 @@ def guess_language(file_name):
|
||||||
if language:
|
if language:
|
||||||
lexer = get_lexer(language)
|
lexer = get_lexer(language)
|
||||||
else:
|
else:
|
||||||
lexer = smart_guess_lexer(file_name)
|
lexer = smart_guess_lexer(file_name, local_file)
|
||||||
if lexer:
|
if lexer:
|
||||||
language = u(lexer.name)
|
language = u(lexer.name)
|
||||||
|
|
||||||
return language, lexer
|
return language, lexer
|
||||||
|
|
||||||
|
|
||||||
def smart_guess_lexer(file_name):
|
def smart_guess_lexer(file_name, local_file):
|
||||||
"""Guess Pygments lexer for a file.
|
"""Guess Pygments lexer for a file.
|
||||||
|
|
||||||
Looks for a vim modeline in file contents, then compares the accuracy
|
Looks for a vim modeline in file contents, then compares the accuracy
|
||||||
|
@ -99,7 +99,7 @@ def smart_guess_lexer(file_name):
|
||||||
|
|
||||||
text = get_file_head(file_name)
|
text = get_file_head(file_name)
|
||||||
|
|
||||||
lexer1, accuracy1 = guess_lexer_using_filename(file_name, text)
|
lexer1, accuracy1 = guess_lexer_using_filename(local_file or file_name, text)
|
||||||
lexer2, accuracy2 = guess_lexer_using_modeline(text)
|
lexer2, accuracy2 = guess_lexer_using_modeline(text)
|
||||||
|
|
||||||
if lexer1:
|
if lexer1:
|
||||||
|
|
Loading…
Reference in a new issue