New --local-file argument to be used when --entity is a remote file

This commit is contained in:
Alan Hamlett 2018-08-31 18:15:00 -07:00
parent 4b9d375c90
commit 1ae230639f
9 changed files with 48 additions and 20 deletions

View File

@ -3,8 +3,8 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
[--entity-type ENTITY_TYPE] [--category CATEGORY] [--entity-type ENTITY_TYPE] [--category CATEGORY]
[--proxy PROXY] [--no-ssl-verify] [--project PROJECT] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
[--hostname HOSTNAME] [--disable-offline] [--hide-file-names] [--local-file FILE] [--hostname HOSTNAME] [--disable-offline]
[--hide-project-names] [--exclude EXCLUDE] [--hide-file-names] [--hide-project-names] [--exclude EXCLUDE]
[--exclude-unknown-project] [--include INCLUDE] [--exclude-unknown-project] [--include INCLUDE]
[--include-only-with-project-file] [--extra-heartbeats] [--include-only-with-project-file] [--extra-heartbeats]
[--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT] [--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]

View File

@ -3,8 +3,8 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
[--entity-type ENTITY_TYPE] [--category CATEGORY] [--entity-type ENTITY_TYPE] [--category CATEGORY]
[--proxy PROXY] [--no-ssl-verify] [--project PROJECT] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
[--hostname HOSTNAME] [--disable-offline] [--hide-file-names] [--local-file FILE] [--hostname HOSTNAME] [--disable-offline]
[--hide-project-names] [--exclude EXCLUDE] [--hide-file-names] [--hide-project-names] [--exclude EXCLUDE]
[--exclude-unknown-project] [--include INCLUDE] [--exclude-unknown-project] [--include INCLUDE]
[--include-only-with-project-file] [--extra-heartbeats] [--include-only-with-project-file] [--extra-heartbeats]
[--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT] [--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]

View File

@ -3,8 +3,8 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
[--entity-type ENTITY_TYPE] [--category CATEGORY] [--entity-type ENTITY_TYPE] [--category CATEGORY]
[--proxy PROXY] [--no-ssl-verify] [--project PROJECT] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
[--hostname HOSTNAME] [--disable-offline] [--hide-file-names] [--local-file FILE] [--hostname HOSTNAME] [--disable-offline]
[--hide-project-names] [--exclude EXCLUDE] [--hide-file-names] [--hide-project-names] [--exclude EXCLUDE]
[--exclude-unknown-project] [--include INCLUDE] [--exclude-unknown-project] [--include INCLUDE]
[--include-only-with-project-file] [--extra-heartbeats] [--include-only-with-project-file] [--extra-heartbeats]
[--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT] [--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]

View File

@ -3,8 +3,8 @@ usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
[--entity-type ENTITY_TYPE] [--category CATEGORY] [--entity-type ENTITY_TYPE] [--category CATEGORY]
[--proxy PROXY] [--no-ssl-verify] [--project PROJECT] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
[--hostname HOSTNAME] [--disable-offline] [--hide-file-names] [--local-file FILE] [--hostname HOSTNAME] [--disable-offline]
[--hide-project-names] [--exclude EXCLUDE] [--hide-file-names] [--hide-project-names] [--exclude EXCLUDE]
[--exclude-unknown-project] [--include INCLUDE] [--exclude-unknown-project] [--include INCLUDE]
[--include-only-with-project-file] [--extra-heartbeats] [--include-only-with-project-file] [--extra-heartbeats]
[--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT] [--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]
@ -46,6 +46,10 @@ optional arguments:
project takes priority. project takes priority.
--language LANGUAGE Optional language name. If valid, takes priority over --language LANGUAGE Optional language name. If valid, takes priority over
auto-detected language. auto-detected language.
--local-file FILE 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.
--hostname HOSTNAME Hostname of current machine. --hostname HOSTNAME Hostname of current machine.
--disable-offline Disables offline time logging instead of queuing --disable-offline Disables offline time logging instead of queuing
logged time. logged time.

View File

@ -22,6 +22,7 @@ class HeartbeatTestCase(TestCase):
include = [] include = []
plugin = None plugin = None
include_only_with_project_file = None include_only_with_project_file = None
local_file = None
data = { data = {
'entity': os.path.realpath('tests/samples/codefiles/python.py'), 'entity': os.path.realpath('tests/samples/codefiles/python.py'),
@ -56,6 +57,7 @@ class HeartbeatTestCase(TestCase):
include = [] include = []
plugin = None plugin = None
include_only_with_project_file = None include_only_with_project_file = None
local_file = None
data = { data = {
'entity': os.path.realpath('tests/samples/codefiles/python.py'), 'entity': os.path.realpath('tests/samples/codefiles/python.py'),

View File

@ -102,8 +102,9 @@ class LanguagesTestCase(utils.TestCase):
with utils.mock.patch('wakatime.stats.smart_guess_lexer') as mock_guess_lexer: with utils.mock.patch('wakatime.stats.smart_guess_lexer') as mock_guess_lexer:
mock_guess_lexer.return_value = None mock_guess_lexer.return_value = None
source_file = 'tests/samples/codefiles/python.py' source_file = 'tests/samples/codefiles/python.py'
result = guess_language(source_file) local_file = None
mock_guess_lexer.assert_called_once_with(source_file) result = guess_language(source_file, local_file)
mock_guess_lexer.assert_called_once_with(source_file, local_file)
self.assertEquals(result, (None, None)) self.assertEquals(result, (None, None))
def test_guess_language_from_vim_modeline(self): def test_guess_language_from_vim_modeline(self):
@ -112,6 +113,13 @@ class LanguagesTestCase(utils.TestCase):
entity='python_without_extension', entity='python_without_extension',
) )
def test_guess_language_when_entity_not_exist_but_local_file_exists(self):
source_file = 'tests/samples/codefiles/does_not_exist.py'
local_file = 'tests/samples/codefiles/python.py'
self.assertFalse(os.path.exists(source_file))
result = guess_language(source_file, local_file)
self.assertEquals(result[0], 'Python')
def test_language_arg_takes_priority_over_detected_language(self): def test_language_arg_takes_priority_over_detected_language(self):
self.shared( self.shared(
expected_language='Java', expected_language='Java',

View File

@ -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.')

View File

@ -85,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
@ -106,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
@ -228,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)

View File

@ -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: