New --category argument

This commit is contained in:
Alan Hamlett 2018-04-06 22:37:01 -07:00
parent 6022f1f531
commit 1fe41b6677
9 changed files with 284 additions and 143 deletions

View file

@ -1,10 +1,11 @@
usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN] usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
[--time time] [--lineno LINENO] [--cursorpos CURSORPOS] [--time time] [--lineno LINENO] [--cursorpos CURSORPOS]
[--entity-type ENTITY_TYPE] [--proxy PROXY] [--no-ssl-verify] [--entity-type ENTITY_TYPE] [--category CATEGORY]
[--project PROJECT] [--alternate-project ALTERNATE_PROJECT] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
[--language LANGUAGE] [--hostname HOSTNAME] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
[--disable-offline] [--hide-filenames] [--exclude EXCLUDE] [--hostname HOSTNAME] [--disable-offline] [--hide-filenames]
[--include INCLUDE] [--include-only-with-project-file] [--exclude EXCLUDE] [--include INCLUDE]
[--extra-heartbeats] [--log-file LOG_FILE] [--api-url API_URL] [--include-only-with-project-file] [--extra-heartbeats]
[--timeout TIMEOUT] [--config CONFIG] [--verbose] [--version] [--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]
wakatime: error: Missing api key. Find your api key from wakatime.com/settings. [--config CONFIG] [--verbose] [--version]
wakatime: error: Missing api key. Find your api key from wakatime.com/settings/api-key.

View file

@ -1,10 +1,11 @@
usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN] usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
[--time time] [--lineno LINENO] [--cursorpos CURSORPOS] [--time time] [--lineno LINENO] [--cursorpos CURSORPOS]
[--entity-type ENTITY_TYPE] [--proxy PROXY] [--no-ssl-verify] [--entity-type ENTITY_TYPE] [--category CATEGORY]
[--project PROJECT] [--alternate-project ALTERNATE_PROJECT] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
[--language LANGUAGE] [--hostname HOSTNAME] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
[--disable-offline] [--hide-filenames] [--exclude EXCLUDE] [--hostname HOSTNAME] [--disable-offline] [--hide-filenames]
[--include INCLUDE] [--include-only-with-project-file] [--exclude EXCLUDE] [--include INCLUDE]
[--extra-heartbeats] [--log-file LOG_FILE] [--api-url API_URL] [--include-only-with-project-file] [--extra-heartbeats]
[--timeout TIMEOUT] [--config CONFIG] [--verbose] [--version] [--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]
wakatime: error: Missing api key. Find your api key from wakatime.com/settings. [--config CONFIG] [--verbose] [--version]
wakatime: error: Missing api key. Find your api key from wakatime.com/settings/api-key.

View file

@ -1,10 +1,11 @@
usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN] usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
[--time time] [--lineno LINENO] [--cursorpos CURSORPOS] [--time time] [--lineno LINENO] [--cursorpos CURSORPOS]
[--entity-type ENTITY_TYPE] [--proxy PROXY] [--no-ssl-verify] [--entity-type ENTITY_TYPE] [--category CATEGORY]
[--project PROJECT] [--alternate-project ALTERNATE_PROJECT] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
[--language LANGUAGE] [--hostname HOSTNAME] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
[--disable-offline] [--hide-filenames] [--exclude EXCLUDE] [--hostname HOSTNAME] [--disable-offline] [--hide-filenames]
[--include INCLUDE] [--include-only-with-project-file] [--exclude EXCLUDE] [--include INCLUDE]
[--extra-heartbeats] [--log-file LOG_FILE] [--api-url API_URL] [--include-only-with-project-file] [--extra-heartbeats]
[--timeout TIMEOUT] [--config CONFIG] [--verbose] [--version] [--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]
[--config CONFIG] [--verbose] [--version]
wakatime: error: argument --timeout: invalid int value: 'abc' wakatime: error: argument --timeout: invalid int value: 'abc'

View file

@ -1,62 +1,68 @@
usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN] usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
[--time time] [--lineno LINENO] [--cursorpos CURSORPOS] [--time time] [--lineno LINENO] [--cursorpos CURSORPOS]
[--entity-type ENTITY_TYPE] [--proxy PROXY] [--no-ssl-verify] [--entity-type ENTITY_TYPE] [--category CATEGORY]
[--project PROJECT] [--alternate-project ALTERNATE_PROJECT] [--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
[--language LANGUAGE] [--hostname HOSTNAME] [--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
[--disable-offline] [--hide-filenames] [--exclude EXCLUDE] [--hostname HOSTNAME] [--disable-offline] [--hide-filenames]
[--include INCLUDE] [--include-only-with-project-file] [--exclude EXCLUDE] [--include INCLUDE]
[--extra-heartbeats] [--log-file LOG_FILE] [--api-url API_URL] [--include-only-with-project-file] [--extra-heartbeats]
[--timeout TIMEOUT] [--config CONFIG] [--verbose] [--version] [--log-file LOG_FILE] [--api-url API_URL] [--timeout TIMEOUT]
[--config CONFIG] [--verbose] [--version]
Common interface for the WakaTime api. Common interface for the WakaTime api.
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
--entity FILE absolute path to file for the heartbeat; can also be a --entity FILE Absolute path to file for the heartbeat. Can also be a
url, domain, or app when --entity-type is not file url, domain, or app when --entity-type is not file.
--key KEY your wakatime api key; uses api_key from --key KEY Your wakatime api key; uses api_key from
~/.wakatime.cfg by default ~/.wakatime.cfg by default.
--write when set, tells api this heartbeat was triggered from --write When set, tells api this heartbeat was triggered from
writing to a file writing to a file.
--plugin PLUGIN optional text editor plugin name and version for User- --plugin PLUGIN Optional text editor plugin name and version for User-
Agent header Agent header.
--time time optional floating-point unix epoch timestamp; uses --time time Optional floating-point unix epoch timestamp. Uses
current time by default current time by default.
--lineno LINENO optional line number; current line being edited --lineno LINENO Optional line number. This is the current line being
edited.
--cursorpos CURSORPOS --cursorpos CURSORPOS
optional cursor position in the current file Optional cursor position in the current file.
--entity-type ENTITY_TYPE --entity-type ENTITY_TYPE
entity type for this heartbeat. can be one of "file", Entity type for this heartbeat. Can be "file",
"domain", or "app"; defaults to file. "domain", or "app". Defaults to "file".
--proxy PROXY optional proxy configuration. Supports HTTPS and SOCKS --category CATEGORY Category of this heartbeat activity. Can be "coding",
"building", "debugging", "running tests", "browsing",
or "code reviewing". Defaults to "coding".
--proxy PROXY Optional proxy configuration. Supports HTTPS and SOCKS
proxies. For example: https://user:pass@host:port or proxies. For example: https://user:pass@host:port or
socks5://user:pass@host:port or domain\user:pass socks5://user:pass@host:port or domain\user:pass
--no-ssl-verify disables SSL certificate verification for HTTPS --no-ssl-verify Disables SSL certificate verification for HTTPS
requests. By default, SSL certificates are verified. requests. By default, SSL certificates are verified.
--project PROJECT optional project name --project PROJECT Optional project name.
--alternate-project ALTERNATE_PROJECT --alternate-project ALTERNATE_PROJECT
optional alternate project name; auto-discovered Optional alternate project name. Auto-discovered
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.
--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.
--hide-filenames obfuscate filenames; will not send file names to api --hide-filenames Obfuscate filenames. Will not send file names to api.
--exclude EXCLUDE filename patterns to exclude from logging; POSIX regex --exclude EXCLUDE Filename patterns to exclude from logging. POSIX regex
syntax; can be used more than once syntax. Can be used more than once.
--include INCLUDE filename patterns to log; when used in combination --include INCLUDE Filename patterns to log. When used in combination
with --exclude, files matching include will still be with --exclude, files matching include will still be
logged; POSIX regex syntax; can be used more than once logged. POSIX regex syntax. Can be used more than
once.
--include-only-with-project-file --include-only-with-project-file
disables tracking folders unless they contain a Disables tracking folders unless they contain a
.wakatime-project file; defaults to false .wakatime-project file. Defaults to false.
--extra-heartbeats reads extra heartbeats from STDIN as a JSON array --extra-heartbeats Reads extra heartbeats from STDIN as a JSON array
until EOF until EOF.
--log-file LOG_FILE defaults to ~/.wakatime.log --log-file LOG_FILE Defaults to ~/.wakatime.log.
--api-url API_URL heartbeats api url; for debugging with a local server --api-url API_URL Heartbeats api url. For debugging with a local server.
--timeout TIMEOUT number of seconds to wait when sending heartbeats to --timeout TIMEOUT Number of seconds to wait when sending heartbeats to
api; defaults to 60 seconds api. Defaults to 60 seconds.
--config CONFIG defaults to ~/.wakatime.cfg --config CONFIG Defaults to ~/.wakatime.cfg.
--verbose turns on debug messages in log file --verbose Turns on debug messages in log file.
--version show program's version number and exit --version show program's version number and exit

View file

@ -244,7 +244,7 @@ class ArgumentsTestCase(TestCase):
self.assertEquals(int(str(e.exception)), AUTH_ERROR) self.assertEquals(int(str(e.exception)), AUTH_ERROR)
self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stdout.getvalue(), '')
expected = 'error: Missing api key. Find your api key from wakatime.com/settings.' expected = 'error: Missing api key. Find your api key from wakatime.com/settings/api-key.'
self.assertIn(expected, sys.stderr.getvalue()) self.assertIn(expected, sys.stderr.getvalue())
log_output = u("\n").join([u(' ').join(x) for x in logs.actual()]) log_output = u("\n").join([u(' ').join(x) for x in logs.actual()])
@ -272,7 +272,7 @@ class ArgumentsTestCase(TestCase):
self.assertEquals(int(str(e.exception)), AUTH_ERROR) self.assertEquals(int(str(e.exception)), AUTH_ERROR)
self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stdout.getvalue(), '')
expected = 'error: Invalid api key. Find your api key from wakatime.com/settings.' expected = 'error: Invalid api key. Find your api key from wakatime.com/settings/api-key.'
self.assertIn(expected, sys.stderr.getvalue()) self.assertIn(expected, sys.stderr.getvalue())
log_output = u("\n").join([u(' ').join(x) for x in logs.actual()]) log_output = u("\n").join([u(' ').join(x) for x in logs.actual()])
@ -488,6 +488,97 @@ class ArgumentsTestCase(TestCase):
self.assertOfflineHeartbeatsSynced() self.assertOfflineHeartbeatsSynced()
self.assertSessionCacheSaved() self.assertSessionCacheSaved()
@log_capture()
def test_valid_categories(self, logs):
logging.disable(logging.NOTSET)
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = CustomResponse()
with TemporaryDirectory() as tempdir:
entity = 'tests/samples/codefiles/emptyfile.txt'
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
config = 'tests/samples/configs/good_config.cfg'
now = u(int(time.time()))
valid_categories = [
'coding',
'building',
'debugging',
'running tests',
'browsing',
'code reviewing',
]
for category in valid_categories:
args = ['--entity', entity, '--category', category, '--config', config, '--time', now]
self.resetMocks()
retval = execute(args)
self.assertEquals(retval, SUCCESS)
self.assertNothingPrinted()
self.assertNothingLogged(logs)
heartbeat = {
'entity': u(entity),
'time': float(now),
'type': 'file',
'category': category,
'cursorpos': None,
'language': 'Text only',
'lines': 0,
'is_write': False,
'dependencies': [],
'user_agent': ANY,
}
self.assertHeartbeatSent(heartbeat)
self.assertHeartbeatNotSavedOffline()
self.assertOfflineHeartbeatsSynced()
self.assertSessionCacheSaved()
@log_capture()
def test_invalid_category(self, logs):
logging.disable(logging.NOTSET)
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = CustomResponse()
with TemporaryDirectory() as tempdir:
entity = 'tests/samples/codefiles/emptyfile.txt'
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
config = 'tests/samples/configs/good_config.cfg'
now = u(int(time.time()))
category = 'foobar'
args = ['--entity', entity, '--category', category, '--config', config, '--time', now]
retval = execute(args)
self.assertEquals(retval, SUCCESS)
self.assertNothingPrinted()
self.assertNothingLogged(logs)
heartbeat = {
'entity': u(entity),
'time': float(now),
'type': 'file',
'category': None,
'cursorpos': None,
'language': 'Text only',
'lines': 0,
'is_write': False,
'dependencies': [],
'user_agent': ANY,
}
self.assertHeartbeatSent(heartbeat)
self.assertHeartbeatNotSavedOffline()
self.assertOfflineHeartbeatsSynced()
self.assertSessionCacheSaved()
@log_capture() @log_capture()
def test_old_alternate_language_argument_still_supported(self, logs): def test_old_alternate_language_argument_still_supported(self, logs):
logging.disable(logging.NOTSET) logging.disable(logging.NOTSET)

View file

@ -276,7 +276,7 @@ class MainTestCase(utils.TestCase):
self.assertEquals(int(str(e.exception)), AUTH_ERROR) self.assertEquals(int(str(e.exception)), AUTH_ERROR)
self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stdout.getvalue(), '')
expected = 'error: Invalid api key. Find your api key from wakatime.com/settings.' expected = 'error: Invalid api key. Find your api key from wakatime.com/settings/api-key.'
self.assertIn(expected, sys.stderr.getvalue()) self.assertIn(expected, sys.stderr.getvalue())
log_output = u("\n").join([u(' ').join(x) for x in logs.actual()]) log_output = u("\n").join([u(' ').join(x) for x in logs.actual()])

View file

@ -144,6 +144,10 @@ class TestCase(unittest.TestCase):
def getLogOutput(self, logs): def getLogOutput(self, logs):
return u("\n").join([u(' ').join(x) for x in logs.actual()]) return u("\n").join([u(' ').join(x) for x in logs.actual()])
def resetMocks(self):
for key in self.patched:
self.patched[key].reset_mock()
@property @property
def isPy35OrNewer(self): def isPy35OrNewer(self):
if sys.version_info[0] > 3: if sys.version_info[0] > 3:

View file

@ -52,100 +52,122 @@ def parse_arguments():
""" """
# define supported command line arguments # define supported command line arguments
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(description='Common interface for the ' +
description='Common interface for the WakaTime api.') 'WakaTime api.')
parser.add_argument('--entity', dest='entity', metavar='FILE', parser.add_argument('--entity', dest='entity', metavar='FILE',
action=FileAction, action=FileAction,
help='absolute path to file for the heartbeat; can also be a '+ help='Absolute path to file for the heartbeat. Can ' +
'url, domain, or app when --entity-type is not file') 'also be a url, domain, or app when ' +
'--entity-type is not file.')
parser.add_argument('--file', dest='file', action=FileAction, parser.add_argument('--file', dest='file', action=FileAction,
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument('--key', dest='key', action=StoreWithoutQuotes, parser.add_argument('--key', dest='key', action=StoreWithoutQuotes,
help='your wakatime api key; uses api_key from '+ help='Your wakatime api key; uses api_key from ' +
'~/.wakatime.cfg by default') '~/.wakatime.cfg by default.')
parser.add_argument('--write', dest='is_write', parser.add_argument('--write', dest='is_write', action='store_true',
action='store_true', help='When set, tells api this heartbeat was ' +
help='when set, tells api this heartbeat was triggered from '+ 'triggered from writing to a file.')
'writing to a file')
parser.add_argument('--plugin', dest='plugin', action=StoreWithoutQuotes, parser.add_argument('--plugin', dest='plugin', action=StoreWithoutQuotes,
help='optional text editor plugin name and version '+ help='Optional text editor plugin name and version ' +
'for User-Agent header') 'for User-Agent header.')
parser.add_argument('--time', dest='timestamp', metavar='time', parser.add_argument('--time', dest='timestamp', metavar='time',
type=float, action=StoreWithoutQuotes, type=float, action=StoreWithoutQuotes,
help='optional floating-point unix epoch timestamp; '+ help='Optional floating-point unix epoch timestamp. ' +
'uses current time by default') 'Uses current time by default.')
parser.add_argument('--lineno', dest='lineno', action=StoreWithoutQuotes, parser.add_argument('--lineno', dest='lineno', action=StoreWithoutQuotes,
help='optional line number; current line being edited') help='Optional line number. This is the current ' +
parser.add_argument('--cursorpos', dest='cursorpos', action=StoreWithoutQuotes, 'line being edited.')
help='optional cursor position in the current file') parser.add_argument('--cursorpos', dest='cursorpos',
parser.add_argument('--entity-type', dest='entity_type', action=StoreWithoutQuotes, action=StoreWithoutQuotes,
help='entity type for this heartbeat. can be one of "file", '+ help='Optional cursor position in the current file.')
'"domain", or "app"; defaults to file.') parser.add_argument('--entity-type', dest='entity_type',
action=StoreWithoutQuotes,
help='Entity type for this heartbeat. Can be ' +
'"file", "domain", or "app". Defaults to "file".')
parser.add_argument('--category', dest='category',
action=StoreWithoutQuotes,
help='Category of this heartbeat activity. Can be ' +
'"coding", "building", "debugging", ' +
'"running tests", "browsing", or ' +
'"code reviewing". Defaults to "coding".')
parser.add_argument('--proxy', dest='proxy', action=StoreWithoutQuotes, parser.add_argument('--proxy', dest='proxy', action=StoreWithoutQuotes,
help='optional proxy configuration. Supports HTTPS '+ help='Optional proxy configuration. Supports HTTPS '+
'and SOCKS proxies. For example: '+ 'and SOCKS proxies. For example: '+
'https://user:pass@host:port or '+ 'https://user:pass@host:port or '+
'socks5://user:pass@host:port or ' + 'socks5://user:pass@host:port or ' +
'domain\\user:pass') 'domain\\user:pass')
parser.add_argument('--no-ssl-verify', dest='nosslverify', parser.add_argument('--no-ssl-verify', dest='nosslverify',
action='store_true', action='store_true',
help='disables SSL certificate verification for HTTPS '+ help='Disables SSL certificate verification for HTTPS '+
'requests. By default, SSL certificates are verified.') 'requests. By default, SSL certificates are ' +
'verified.')
parser.add_argument('--project', dest='project', action=StoreWithoutQuotes, parser.add_argument('--project', dest='project', action=StoreWithoutQuotes,
help='optional project name') help='Optional project name.')
parser.add_argument('--alternate-project', dest='alternate_project', action=StoreWithoutQuotes, parser.add_argument('--alternate-project', dest='alternate_project',
help='optional alternate project name; auto-discovered project '+ action=StoreWithoutQuotes,
'takes priority') help='Optional alternate project name. ' +
parser.add_argument('--alternate-language', dest='alternate_language', action=StoreWithoutQuotes, 'Auto-discovered project takes priority.')
parser.add_argument('--alternate-language', dest='alternate_language',
action=StoreWithoutQuotes,
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument('--language', dest='language', action=StoreWithoutQuotes, parser.add_argument('--language', dest='language',
help='optional language name; if valid, takes priority over '+ action=StoreWithoutQuotes,
'auto-detected language') help='Optional language name. If valid, takes ' +
parser.add_argument('--hostname', dest='hostname', action=StoreWithoutQuotes, help='hostname of '+ 'priority over auto-detected language.')
'current machine.') parser.add_argument('--hostname', dest='hostname',
action=StoreWithoutQuotes,
help='Hostname of current machine.')
parser.add_argument('--disable-offline', dest='offline', parser.add_argument('--disable-offline', dest='offline',
action='store_false', action='store_false',
help='disables offline time logging instead of queuing logged time') help='Disables offline time logging instead of ' +
'queuing logged time.')
parser.add_argument('--disableoffline', dest='offline_deprecated', parser.add_argument('--disableoffline', dest='offline_deprecated',
action='store_true', help=argparse.SUPPRESS) action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--hide-filenames', dest='hide_filenames', parser.add_argument('--hide-filenames', dest='hide_filenames',
action='store_true', action='store_true',
help='obfuscate filenames; will not send file names to api') help='Obfuscate filenames. Will not send file names ' +
'to api.')
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('--exclude', dest='exclude', action='append', parser.add_argument('--exclude', dest='exclude', action='append',
help='filename patterns to exclude from logging; POSIX regex '+ help='Filename patterns to exclude from logging. ' +
'syntax; can be used more than once') 'POSIX regex syntax. Can be used more than once.')
parser.add_argument('--include', dest='include', action='append', parser.add_argument('--include', dest='include', action='append',
help='filename patterns to log; when used in combination with '+ help='Filename patterns to log. When used in ' +
'--exclude, files matching include will still be logged; '+ 'combination with --exclude, files matching ' +
'POSIX regex syntax; can be used more than once') 'include will still be logged. POSIX regex ' +
'syntax. Can be used more than once.')
parser.add_argument('--include-only-with-project-file', parser.add_argument('--include-only-with-project-file',
dest='include_only_with_project_file', dest='include_only_with_project_file',
action='store_true', action='store_true',
help='disables tracking folders unless they contain '+ help='Disables tracking folders unless they contain '+
'a .wakatime-project file; defaults to false') 'a .wakatime-project file. Defaults to false.')
parser.add_argument('--ignore', dest='ignore', action='append', parser.add_argument('--ignore', dest='ignore', action='append',
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument('--extra-heartbeats', dest='extra_heartbeats', parser.add_argument('--extra-heartbeats', dest='extra_heartbeats',
action='store_true', action='store_true',
help='reads extra heartbeats from STDIN as a JSON array until EOF') help='Reads extra heartbeats from STDIN as a JSON ' +
parser.add_argument('--log-file', dest='log_file', action=StoreWithoutQuotes, 'array until EOF.')
help='defaults to ~/.wakatime.log') parser.add_argument('--log-file', dest='log_file',
action=StoreWithoutQuotes,
help='Defaults to ~/.wakatime.log.')
parser.add_argument('--logfile', dest='logfile', action=StoreWithoutQuotes, parser.add_argument('--logfile', dest='logfile', action=StoreWithoutQuotes,
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument('--api-url', dest='api_url', action=StoreWithoutQuotes, parser.add_argument('--api-url', dest='api_url', action=StoreWithoutQuotes,
help='heartbeats api url; for debugging with a local server') help='Heartbeats api url. For debugging with a ' +
'local server.')
parser.add_argument('--apiurl', dest='apiurl', action=StoreWithoutQuotes, parser.add_argument('--apiurl', dest='apiurl', action=StoreWithoutQuotes,
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument('--timeout', dest='timeout', type=int, action=StoreWithoutQuotes, parser.add_argument('--timeout', dest='timeout', type=int,
help='number of seconds to wait when sending heartbeats to api; '+ action=StoreWithoutQuotes,
'defaults to 60 seconds') help='Number of seconds to wait when sending ' +
'heartbeats to api. Defaults to 60 seconds.')
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',
help='turns on debug messages in log file') help='Turns on debug messages in log file.')
parser.add_argument('--version', action='version', version=__version__) parser.add_argument('--version', action='version', version=__version__)
# parse command line arguments # parse command line arguments
@ -172,14 +194,14 @@ def parse_arguments():
args.key = default_key args.key = default_key
else: else:
try: try:
parser.error('Missing api key. Find your api key from wakatime.com/settings.') parser.error('Missing api key. Find your api key from wakatime.com/settings/api-key.')
except SystemExit: except SystemExit:
raise SystemExit(AUTH_ERROR) raise SystemExit(AUTH_ERROR)
is_valid = not not re.match(r'^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$', args.key, re.I) is_valid = not not re.match(r'^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$', args.key, re.I)
if not is_valid: if not is_valid:
try: try:
parser.error('Invalid api key. Find your api key from wakatime.com/settings.') parser.error('Invalid api key. Find your api key from wakatime.com/settings/api-key.')
except SystemExit: except SystemExit:
raise SystemExit(AUTH_ERROR) raise SystemExit(AUTH_ERROR)

View file

@ -31,6 +31,7 @@ class Heartbeat(object):
time = None time = None
entity = None entity = None
type = None type = None
category = None
is_write = None is_write = None
project = None project = None
branch = None branch = None
@ -58,6 +59,18 @@ class Heartbeat(object):
if self.type not in ['file', 'domain', 'app']: if self.type not in ['file', 'domain', 'app']:
self.type = 'file' self.type = 'file'
self.category = data.get('category')
allowed_categories = [
'coding',
'building',
'debugging',
'running tests',
'browsing',
'code reviewing'
]
if self.category not in allowed_categories:
self.category = None
if not _clone: if not _clone:
exclude = self._excluded_by_pattern() exclude = self._excluded_by_pattern()
if exclude: if exclude:
@ -155,6 +168,7 @@ class Heartbeat(object):
'time': self.time, 'time': self.time,
'entity': self._unicode(self.entity), 'entity': self._unicode(self.entity),
'type': self.type, 'type': self.type,
'category': self.category,
'is_write': self.is_write, 'is_write': self.is_write,
'project': self._unicode(self.project), 'project': self._unicode(self.project),
'branch': self._unicode(self.branch), 'branch': self._unicode(self.branch),
@ -170,9 +184,10 @@ class Heartbeat(object):
return self.dict().items() return self.dict().items()
def get_id(self): def get_id(self):
return u('{time}-{type}-{project}-{branch}-{entity}-{is_write}').format( return u('{time}-{type}-{category}-{project}-{branch}-{entity}-{is_write}').format(
time=self.time, time=self.time,
type=self.type, type=self.type,
category=self.category,
project=self._unicode(self.project), project=self._unicode(self.project),
branch=self._unicode(self.branch), branch=self._unicode(self.branch),
entity=self._unicode(self.entity), entity=self._unicode(self.entity),