From 260eedb31d8ff946a41749e3f4b6bc44653c664b Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Fri, 29 Apr 2016 00:04:46 +0200 Subject: [PATCH] upgrade wakatime-cli to v6.0.0 --- packages/wakatime/__about__.py | 2 +- packages/wakatime/constants.py | 1 + packages/wakatime/languages/default.json | 80 ++++ packages/wakatime/languages/vim.json | 531 +++++++++++++++++++++++ packages/wakatime/logger.py | 14 +- packages/wakatime/main.py | 116 +++-- packages/wakatime/project.py | 10 +- packages/wakatime/stats.py | 56 ++- 8 files changed, 748 insertions(+), 62 deletions(-) create mode 100644 packages/wakatime/languages/default.json create mode 100644 packages/wakatime/languages/vim.json diff --git a/packages/wakatime/__about__.py b/packages/wakatime/__about__.py index d561bf9..2f2a1c1 100644 --- a/packages/wakatime/__about__.py +++ b/packages/wakatime/__about__.py @@ -1,7 +1,7 @@ __title__ = 'wakatime' __description__ = 'Common interface to the WakaTime api.' __url__ = 'https://github.com/wakatime/wakatime' -__version_info__ = ('5', '0', '0') +__version_info__ = ('6', '0', '0') __version__ = '.'.join(__version_info__) __author__ = 'Alan Hamlett' __author_email__ = 'alan@wakatime.com' diff --git a/packages/wakatime/constants.py b/packages/wakatime/constants.py index 9db227f..1c7316b 100644 --- a/packages/wakatime/constants.py +++ b/packages/wakatime/constants.py @@ -15,3 +15,4 @@ API_ERROR = 102 CONFIG_FILE_PARSE_ERROR = 103 AUTH_ERROR = 104 UNKNOWN_ERROR = 105 +MALFORMED_HEARTBEAT_ERROR = 106 diff --git a/packages/wakatime/languages/default.json b/packages/wakatime/languages/default.json new file mode 100644 index 0000000..c57d4f8 --- /dev/null +++ b/packages/wakatime/languages/default.json @@ -0,0 +1,80 @@ +{ + "ActionScript": "ActionScript", + "ApacheConf": "ApacheConf", + "AppleScript": "AppleScript", + "ASP": "ASP", + "Assembly": "Assembly", + "Awk": "Awk", + "Bash": "Bash", + "Basic": "Basic", + "BrightScript": "BrightScript", + "C": "C", + "C#": "C#", + "C++": "C++", + "Clojure": "Clojure", + "Cocoa": "Cocoa", + "CoffeeScript": "CoffeeScript", + "ColdFusion": "ColdFusion", + "Common Lisp": "Common Lisp", + "CSHTML": "CSHTML", + "CSS": "CSS", + "Dart": "Dart", + "Delphi": "Delphi", + "Elixir": "Elixir", + "Elm": "Elm", + "Emacs Lisp": "Emacs Lisp", + "Erlang": "Erlang", + "F#": "F#", + "Fortran": "Fortran", + "Go": "Go", + "Gous": "Gosu", + "Groovy": "Groovy", + "Haml": "Haml", + "HaXe": "HaXe", + "Haskell": "Haskell", + "HTML": "HTML", + "INI": "INI", + "Jade": "Jade", + "Java": "Java", + "JavaScript": "JavaScript", + "JSON": "JSON", + "JSX": "JSX", + "Kotlin": "Kotlin", + "LESS": "LESS", + "Lua": "Lua", + "Markdown": "Markdown", + "Matlab": "Matlab", + "Mustache": "Mustache", + "OCaml": "OCaml", + "Objective-C": "Objective-C", + "Objective-C++": "Objective-C++", + "Objective-J": "Objective-J", + "Perl": "Perl", + "PHP": "PHP", + "PowerShell": "PowerShell", + "Prolog": "Prolog", + "Puppet": "Puppet", + "Python": "Python", + "R": "R", + "reStructuredText": "reStructuredText", + "Ruby": "Ruby", + "Rust": "Rust", + "Sass": "Sass", + "Scala": "Scala", + "Scheme": "Scheme", + "SCSS": "SCSS", + "Shell": "Shell", + "Slim": "Slim", + "Smalltalk": "Smalltalk", + "SQL": "SQL", + "Swift": "Swift", + "Text": "Text", + "Turing": "Turing", + "Twig": "Twig", + "TypeScript": "TypeScript", + "VB.net": "VB.net", + "VimL": "VimL", + "XAML": "XAML", + "XML": "XML", + "YAML": "YAML" +} diff --git a/packages/wakatime/languages/vim.json b/packages/wakatime/languages/vim.json new file mode 100644 index 0000000..e75c964 --- /dev/null +++ b/packages/wakatime/languages/vim.json @@ -0,0 +1,531 @@ +{ + "a2ps": null, + "a65": "Assembly", + "aap": null, + "abap": null, + "abaqus": null, + "abc": null, + "abel": null, + "acedb": null, + "ada": null, + "aflex": null, + "ahdl": null, + "alsaconf": null, + "amiga": null, + "aml": null, + "ampl": null, + "ant": null, + "antlr": null, + "apache": null, + "apachestyle": null, + "arch": null, + "art": null, + "asm": "Assembly", + "asm68k": "Assembly", + "asmh8300": "Assembly", + "asn": null, + "aspperl": null, + "aspvbs": null, + "asterisk": null, + "asteriskvm": null, + "atlas": null, + "autohotkey": null, + "autoit": null, + "automake": null, + "ave": null, + "awk": null, + "ayacc": null, + "b": null, + "baan": null, + "basic": "Basic", + "bc": null, + "bdf": null, + "bib": null, + "bindzone": null, + "blank": null, + "bst": null, + "btm": null, + "bzr": null, + "c": "C", + "cabal": null, + "calendar": null, + "catalog": null, + "cdl": null, + "cdrdaoconf": null, + "cdrtoc": null, + "cf": null, + "cfg": null, + "ch": null, + "chaiscript": null, + "change": null, + "changelog": null, + "chaskell": null, + "cheetah": null, + "chill": null, + "chordpro": null, + "cl": null, + "clean": null, + "clipper": null, + "cmake": null, + "cmusrc": null, + "cobol": null, + "coco": null, + "conaryrecipe": null, + "conf": null, + "config": null, + "context": null, + "cpp": "C++", + "crm": null, + "crontab": "Crontab", + "cs": "C#", + "csc": null, + "csh": null, + "csp": null, + "css": null, + "cterm": null, + "ctrlh": null, + "cucumber": null, + "cuda": null, + "cupl": null, + "cuplsim": null, + "cvs": null, + "cvsrc": null, + "cweb": null, + "cynlib": null, + "cynpp": null, + "d": null, + "datascript": null, + "dcd": null, + "dcl": null, + "debchangelog": null, + "debcontrol": null, + "debsources": null, + "def": null, + "denyhosts": null, + "desc": null, + "desktop": null, + "dictconf": null, + "dictdconf": null, + "diff": null, + "dircolors": null, + "diva": null, + "django": null, + "dns": null, + "docbk": null, + "docbksgml": null, + "docbkxml": null, + "dosbatch": null, + "dosini": null, + "dot": null, + "doxygen": null, + "dracula": null, + "dsl": null, + "dtd": null, + "dtml": null, + "dtrace": null, + "dylan": null, + "dylanintr": null, + "dylanlid": null, + "ecd": null, + "edif": null, + "eiffel": null, + "elf": null, + "elinks": null, + "elmfilt": null, + "erlang": null, + "eruby": null, + "esmtprc": null, + "esqlc": null, + "esterel": null, + "eterm": null, + "eviews": null, + "exim": null, + "expect": null, + "exports": null, + "fan": null, + "fasm": null, + "fdcc": null, + "fetchmail": null, + "fgl": null, + "flexwiki": null, + "focexec": null, + "form": null, + "forth": null, + "fortran": null, + "foxpro": null, + "framescript": null, + "freebasic": null, + "fstab": null, + "fvwm": null, + "fvwm2m4": null, + "gdb": null, + "gdmo": null, + "gedcom": null, + "git": null, + "gitcommit": null, + "gitconfig": null, + "gitrebase": null, + "gitsendemail": null, + "gkrellmrc": null, + "gnuplot": null, + "gp": null, + "gpg": null, + "grads": null, + "gretl": null, + "groff": null, + "groovy": null, + "group": null, + "grub": null, + "gsp": null, + "gtkrc": null, + "haml": "Haml", + "hamster": null, + "haskell": "Haskell", + "haste": null, + "hastepreproc": null, + "hb": null, + "help": null, + "hercules": null, + "hex": null, + "hog": null, + "hostconf": null, + "html": "HTML", + "htmlcheetah": "HTML", + "htmldjango": "HTML", + "htmlm4": "HTML", + "htmlos": null, + "ia64": null, + "ibasic": null, + "icemenu": null, + "icon": null, + "idl": null, + "idlang": null, + "indent": null, + "inform": null, + "initex": null, + "initng": null, + "inittab": null, + "ipfilter": null, + "ishd": null, + "iss": null, + "ist": null, + "jal": null, + "jam": null, + "jargon": null, + "java": "Java", + "javacc": null, + "javascript": "JavaScript", + "jess": null, + "jgraph": null, + "jproperties": null, + "jsp": null, + "kconfig": null, + "kix": null, + "kscript": null, + "kwt": null, + "lace": null, + "latte": null, + "ld": null, + "ldapconf": null, + "ldif": null, + "lex": null, + "lftp": null, + "lhaskell": "Haskell", + "libao": null, + "lifelines": null, + "lilo": null, + "limits": null, + "liquid": null, + "lisp": null, + "lite": null, + "litestep": null, + "loginaccess": null, + "logindefs": null, + "logtalk": null, + "lotos": null, + "lout": null, + "lpc": null, + "lprolog": null, + "lscript": null, + "lsl": null, + "lss": null, + "lua": null, + "lynx": null, + "m4": null, + "mail": null, + "mailaliases": null, + "mailcap": null, + "make": null, + "man": null, + "manconf": null, + "manual": null, + "maple": null, + "markdown": "Markdown", + "masm": null, + "mason": null, + "master": null, + "matlab": null, + "maxima": null, + "mel": null, + "messages": null, + "mf": null, + "mgl": null, + "mgp": null, + "mib": null, + "mma": null, + "mmix": null, + "mmp": null, + "modconf": null, + "model": null, + "modsim3": null, + "modula2": null, + "modula3": null, + "monk": null, + "moo": null, + "mp": null, + "mplayerconf": null, + "mrxvtrc": null, + "msidl": null, + "msmessages": null, + "msql": null, + "mupad": null, + "mush": null, + "muttrc": null, + "mysql": null, + "named": null, + "nanorc": null, + "nasm": null, + "nastran": null, + "natural": null, + "ncf": null, + "netrc": null, + "netrw": null, + "nosyntax": null, + "nqc": null, + "nroff": null, + "nsis": null, + "obj": null, + "objc": "Objective-C", + "objcpp": "Objective-C++", + "ocaml": "OCaml", + "occam": null, + "omnimark": null, + "openroad": null, + "opl": null, + "ora": null, + "pamconf": null, + "papp": null, + "pascal": null, + "passwd": null, + "pcap": null, + "pccts": null, + "pdf": null, + "perl": "Perl", + "perl6": "Perl", + "pf": null, + "pfmain": null, + "php": "PHP", + "phtml": "PHP", + "pic": null, + "pike": null, + "pilrc": null, + "pine": null, + "pinfo": null, + "plaintex": null, + "plm": null, + "plp": null, + "plsql": null, + "po": null, + "pod": null, + "postscr": null, + "pov": null, + "povini": null, + "ppd": null, + "ppwiz": null, + "prescribe": null, + "privoxy": null, + "procmail": null, + "progress": null, + "prolog": "Prolog", + "promela": null, + "protocols": null, + "psf": null, + "ptcap": null, + "purifylog": null, + "pyrex": null, + "python": "Python", + "qf": null, + "quake": null, + "r": "R", + "racc": null, + "radiance": null, + "ratpoison": null, + "rc": null, + "rcs": null, + "rcslog": null, + "readline": null, + "rebol": null, + "registry": null, + "remind": null, + "resolv": null, + "reva": null, + "rexx": null, + "rhelp": null, + "rib": null, + "rnc": null, + "rnoweb": null, + "robots": null, + "rpcgen": null, + "rpl": null, + "rst": null, + "rtf": null, + "ruby": "Ruby", + "samba": null, + "sas": null, + "sass": "Sass", + "sather": null, + "scheme": "Scheme", + "scilab": null, + "screen": null, + "scss": "SCSS", + "sd": null, + "sdc": null, + "sdl": null, + "sed": null, + "sendpr": null, + "sensors": null, + "services": null, + "setserial": null, + "sgml": null, + "sgmldecl": null, + "sgmllnx": null, + "sh": null, + "sicad": null, + "sieve": null, + "simula": null, + "sinda": null, + "sindacmp": null, + "sindaout": null, + "sisu": null, + "skill": "SKILL", + "sl": null, + "slang": null, + "slice": null, + "slpconf": null, + "slpreg": null, + "slpspi": null, + "slrnrc": null, + "slrnsc": null, + "sm": null, + "smarty": null, + "smcl": null, + "smil": null, + "smith": null, + "sml": null, + "snnsnet": null, + "snnspat": null, + "snnsres": null, + "snobol4": null, + "spec": null, + "specman": null, + "spice": null, + "splint": null, + "spup": null, + "spyce": null, + "sql": null, + "sqlanywhere": null, + "sqlforms": null, + "sqlinformix": null, + "sqlj": null, + "sqloracle": null, + "sqr": null, + "squid": null, + "sshconfig": null, + "sshdconfig": null, + "st": null, + "stata": null, + "stp": null, + "strace": null, + "sudoers": null, + "svg": null, + "svn": null, + "syncolor": null, + "synload": null, + "syntax": null, + "sysctl": null, + "tads": null, + "tags": null, + "tak": null, + "takcmp": null, + "takout": null, + "tar": null, + "taskdata": null, + "taskedit": null, + "tasm": null, + "tcl": null, + "tcsh": null, + "terminfo": null, + "tex": null, + "texinfo": null, + "texmf": null, + "tf": null, + "tidy": null, + "tilde": null, + "tli": null, + "tpp": null, + "trasys": null, + "trustees": null, + "tsalt": null, + "tsscl": null, + "tssgm": null, + "tssop": null, + "uc": null, + "udevconf": null, + "udevperm": null, + "udevrules": null, + "uil": null, + "updatedb": null, + "valgrind": null, + "vb": "VB.net", + "vera": null, + "verilog": null, + "verilogams": null, + "vgrindefs": null, + "vhdl": null, + "vim": "VimL", + "viminfo": null, + "virata": null, + "vmasm": null, + "voscm": null, + "vrml": null, + "vsejcl": null, + "wdiff": null, + "web": null, + "webmacro": null, + "wget": null, + "winbatch": null, + "wml": null, + "wsh": null, + "wsml": null, + "wvdial": null, + "xbl": null, + "xdefaults": null, + "xf86conf": null, + "xhtml": "HTML", + "xinetd": null, + "xkb": null, + "xmath": null, + "xml": "XML", + "xmodmap": null, + "xpm": null, + "xpm2": null, + "xquery": null, + "xs": null, + "xsd": null, + "xslt": null, + "xxd": null, + "yacc": null, + "yaml": "YAML", + "z8a": null, + "zsh": null +} diff --git a/packages/wakatime/logger.py b/packages/wakatime/logger.py index 78b38ee..3d3f166 100644 --- a/packages/wakatime/logger.py +++ b/packages/wakatime/logger.py @@ -41,10 +41,10 @@ class CustomEncoder(json.JSONEncoder): class JsonFormatter(logging.Formatter): - def setup(self, timestamp, isWrite, entity, version, plugin, verbose, + def setup(self, timestamp, is_write, entity, version, plugin, verbose, warnings=False): self.timestamp = timestamp - self.isWrite = isWrite + self.is_write = is_write self.entity = entity self.version = version self.plugin = plugin @@ -61,10 +61,10 @@ class JsonFormatter(logging.Formatter): if self.verbose: data['caller'] = record.pathname data['lineno'] = record.lineno - data['isWrite'] = self.isWrite + data['is_write'] = self.is_write data['file'] = self.entity - if not self.isWrite: - del data['isWrite'] + if not self.is_write: + del data['is_write'] data['level'] = record.levelname data['message'] = record.getMessage() if self.warnings else record.msg if not self.plugin: @@ -103,7 +103,7 @@ def setup_logging(args, version): formatter = JsonFormatter(datefmt='%Y/%m/%d %H:%M:%S %z') formatter.setup( timestamp=args.timestamp, - isWrite=args.isWrite, + is_write=args.is_write, entity=args.entity, version=version, plugin=args.plugin, @@ -118,7 +118,7 @@ def setup_logging(args, version): warnings_formatter = JsonFormatter(datefmt='%Y/%m/%d %H:%M:%S %z') warnings_formatter.setup( timestamp=args.timestamp, - isWrite=args.isWrite, + is_write=args.is_write, entity=args.entity, version=version, plugin=args.plugin, diff --git a/packages/wakatime/main.py b/packages/wakatime/main.py index 2547801..737f8d9 100644 --- a/packages/wakatime/main.py +++ b/packages/wakatime/main.py @@ -37,6 +37,7 @@ from .constants import ( CONFIG_FILE_PARSE_ERROR, SUCCESS, UNKNOWN_ERROR, + MALFORMED_HEARTBEAT_ERROR, ) from .logger import setup_logging from .offlinequeue import Queue @@ -101,13 +102,13 @@ def parseArguments(): parser.add_argument('--entity', dest='entity', metavar='FILE', action=FileAction, help='absolute path to file for the heartbeat; can also be a '+ - 'url, domain, or app when --entitytype is not file') + 'url, domain, or app when --entity-type is not file') parser.add_argument('--file', dest='file', action=FileAction, help=argparse.SUPPRESS) parser.add_argument('--key', dest='key', help='your wakatime api key; uses api_key from '+ - '~/.wakatime.conf by default') - parser.add_argument('--write', dest='isWrite', + '~/.wakatime.cfg by default') + parser.add_argument('--write', dest='is_write', action='store_true', help='when set, tells api this heartbeat was triggered from '+ 'writing to a file') @@ -122,9 +123,9 @@ def parseArguments(): help='optional line number; current line being edited') parser.add_argument('--cursorpos', dest='cursorpos', help='optional cursor position in the current file') - parser.add_argument('--entitytype', dest='entity_type', + parser.add_argument('--entity-type', dest='entity_type', help='entity type for this heartbeat. can be one of "file", '+ - '"url", "domain", or "app"; defaults to file.') + '"domain", or "app"; defaults to file.') parser.add_argument('--proxy', dest='proxy', help='optional https proxy url; for example: '+ 'https://user:pass@localhost:8080') @@ -133,6 +134,9 @@ def parseArguments(): parser.add_argument('--alternate-project', dest='alternate_project', help='optional alternate project name; auto-discovered project '+ 'takes priority') + parser.add_argument('--alternate-language', dest='alternate_language', + help='optional alternate language name; auto-detected language'+ + 'takes priority') parser.add_argument('--hostname', dest='hostname', help='hostname of '+ 'current machine.') parser.add_argument('--disableoffline', dest='offline', @@ -150,14 +154,18 @@ def parseArguments(): 'POSIX regex syntax; can be used more than once') parser.add_argument('--ignore', dest='ignore', action='append', help=argparse.SUPPRESS) + parser.add_argument('--extra-heartbeats', dest='extra_heartbeats', + action='store_true', + help='reads extra heartbeats from STDIN as a JSON array until EOF') parser.add_argument('--logfile', dest='logfile', help='defaults to ~/.wakatime.log') parser.add_argument('--apiurl', dest='api_url', help='heartbeats api url; for debugging with a local server') parser.add_argument('--timeout', dest='timeout', type=int, - help='number of seconds to wait when sending heartbeats to api') + help='number of seconds to wait when sending heartbeats to api; '+ + 'defaults to 60 seconds') parser.add_argument('--config', dest='config', - help='defaults to ~/.wakatime.conf') + help='defaults to ~/.wakatime.cfg') parser.add_argument('--verbose', dest='verbose', action='store_true', help='turns on debug messages in log file') parser.add_argument('--version', action='version', version=__version__) @@ -185,8 +193,6 @@ def parseArguments(): args.key = default_key else: parser.error('Missing api key') - if not args.entity_type: - args.entity_type = 'file' if not args.entity: if args.file: args.entity = args.file @@ -292,7 +298,7 @@ def get_user_agent(plugin): def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, - entity=None, timestamp=None, isWrite=None, plugin=None, + entity=None, timestamp=None, is_write=None, plugin=None, offline=None, entity_type='file', hidefilenames=None, proxy=None, api_url=None, timeout=None, **kwargs): """Sends heartbeat as POST request to WakaTime api server. @@ -304,7 +310,7 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, if not api_url: api_url = 'https://api.wakatime.com/api/v1/heartbeats' if not timeout: - timeout = 30 + timeout = 60 log.debug('Sending heartbeat to api at %s' % api_url) data = { 'time': timestamp, @@ -324,8 +330,8 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, data['lineno'] = stats['lineno'] if stats.get('cursorpos'): data['cursorpos'] = stats['cursorpos'] - if isWrite: - data['is_write'] = isWrite + if is_write: + data['is_write'] = is_write if project: data['project'] = project if branch: @@ -432,7 +438,7 @@ def sync_offline_heartbeats(args, hostname): hostname=hostname, stats=json.loads(heartbeat['stats']), key=args.key, - isWrite=heartbeat['is_write'], + is_write=heartbeat['is_write'], plugin=heartbeat['plugin'], offline=args.offline, hidefilenames=args.hidefilenames, @@ -448,6 +454,45 @@ def sync_offline_heartbeats(args, hostname): return SUCCESS +def process_heartbeat(args, configs, hostname, heartbeat): + exclude = should_exclude(heartbeat['entity'], args.include, args.exclude) + if exclude is not False: + log.debug(u('Skipping because matches exclude pattern: {pattern}').format( + pattern=u(exclude), + )) + return SUCCESS + + if heartbeat.get('entity_type') not in ['file', 'domain', 'app']: + heartbeat['entity_type'] = 'file' + + if heartbeat['entity_type'] != 'file' or os.path.isfile(heartbeat['entity']): + + stats = get_file_stats(heartbeat['entity'], + entity_type=heartbeat['entity_type'], + lineno=heartbeat.get('lineno'), + cursorpos=heartbeat.get('cursorpos'), + plugin=args.plugin, + alternate_language=heartbeat.get('alternate_language')) + + project = heartbeat.get('project') or heartbeat.get('alternate_project') + branch = None + if heartbeat['entity_type'] == 'file': + project, branch = get_project_info(configs, heartbeat) + + heartbeat['project'] = project + heartbeat['branch'] = branch + heartbeat['stats'] = stats + heartbeat['hostname'] = hostname + heartbeat['timeout'] = args.timeout + heartbeat['key'] = args.key + + return send_heartbeat(**heartbeat) + + else: + log.debug('File does not exist; ignoring this heartbeat.') + return SUCCESS + + def execute(argv=None): if argv: sys.argv = ['wakatime'] + argv @@ -459,42 +504,25 @@ def execute(argv=None): setup_logging(args, __version__) try: - exclude = should_exclude(args.entity, args.include, args.exclude) - if exclude is not False: - log.debug(u('Skipping because matches exclude pattern: {pattern}').format( - pattern=u(exclude), - )) - return SUCCESS - if args.entity_type != 'file' or os.path.isfile(args.entity): + hostname = args.hostname or socket.gethostname() - stats = get_file_stats(args.entity, - entity_type=args.entity_type, - lineno=args.lineno, - cursorpos=args.cursorpos) + heartbeat = vars(args) + retval = process_heartbeat(args, configs, hostname, heartbeat) - project = args.project or args.alternate_project - branch = None - if args.entity_type == 'file': - project, branch = get_project_info(configs, args) + if args.extra_heartbeats: + try: + for heartbeat in json.loads(sys.stdin.readline()): + retval = process_heartbeat(args, configs, hostname, heartbeat) + except json.JSONDecodeError: + retval = MALFORMED_HEARTBEAT_ERROR - kwargs = vars(args) - kwargs['project'] = project - kwargs['branch'] = branch - kwargs['stats'] = stats - hostname = args.hostname or socket.gethostname() - kwargs['hostname'] = hostname - kwargs['timeout'] = args.timeout + if retval == SUCCESS: + retval = sync_offline_heartbeats(args, hostname) - status = send_heartbeat(**kwargs) - if status == SUCCESS: - return sync_offline_heartbeats(args, hostname) - else: - return status + return retval - else: - log.debug('File does not exist; ignoring this heartbeat.') - return SUCCESS except: log.traceback() + print(traceback.format_exc()) return UNKNOWN_ERROR diff --git a/packages/wakatime/project.py b/packages/wakatime/project.py index f3299fe..c008651 100644 --- a/packages/wakatime/project.py +++ b/packages/wakatime/project.py @@ -33,7 +33,7 @@ REV_CONTROL_PLUGINS = [ ] -def get_project_info(configs, args): +def get_project_info(configs, heartbeat): """Find the current project and branch. First looks for a .wakatime-project file. Second, uses the --project arg. @@ -50,14 +50,14 @@ def get_project_info(configs, args): plugin_name = plugin_cls.__name__.lower() plugin_configs = get_configs_for_plugin(plugin_name, configs) - project = plugin_cls(args.entity, configs=plugin_configs) + project = plugin_cls(heartbeat['entity'], configs=plugin_configs) if project.process(): project_name = project_name or project.name() branch_name = project.branch() break if project_name is None: - project_name = args.project + project_name = heartbeat.get('project') if project_name is None or branch_name is None: @@ -66,14 +66,14 @@ def get_project_info(configs, args): plugin_name = plugin_cls.__name__.lower() plugin_configs = get_configs_for_plugin(plugin_name, configs) - project = plugin_cls(args.entity, configs=plugin_configs) + project = plugin_cls(heartbeat['entity'], configs=plugin_configs) if project.process(): project_name = project_name or project.name() branch_name = branch_name or project.branch() break if project_name is None: - project_name = args.alternate_project + project_name = heartbeat.get('alternate_project') return project_name, branch_name diff --git a/packages/wakatime/stats.py b/packages/wakatime/stats.py index a7a15a4..238a3bf 100644 --- a/packages/wakatime/stats.py +++ b/packages/wakatime/stats.py @@ -23,6 +23,11 @@ from .packages import ( ClassNotFound, ) +try: + from .packages import simplejson as json # pragma: nocover +except (ImportError, SyntaxError): # pragma: nocover + import json + log = logging.getLogger('WakaTime') @@ -50,7 +55,7 @@ def smart_guess_lexer(file_name): """ lexer = None - text = get_file_contents(file_name) + text = get_file_head(file_name) lexer1, accuracy1 = guess_lexer_using_filename(file_name, text) lexer2, accuracy2 = guess_lexer_using_modeline(text) @@ -154,7 +159,8 @@ def number_lines_in_file(file_name): return lines -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, alternate_language=None): if entity_type != 'file': stats = { 'language': None, @@ -167,6 +173,8 @@ def get_file_stats(file_name, entity_type='file', lineno=None, cursorpos=None): language, lexer = guess_language(file_name) parser = DependencyParser(file_name, lexer) dependencies = parser.parse() + if language is None and alternate_language: + language = standardize_language(alternate_language, plugin) stats = { 'language': language, 'dependencies': dependencies, @@ -177,9 +185,47 @@ def get_file_stats(file_name, entity_type='file', lineno=None, cursorpos=None): return stats -def get_file_contents(file_name): - """Returns the first 512000 bytes of the file's contents. - """ +def standardize_language(language, plugin): + """Maps a string to the equivalent Pygments language.""" + + # standardize language for this plugin + if plugin: + plugin = plugin.split(' ')[-1].split('/')[0].split('-')[0] + standardized = get_language_from_json(language, plugin) + if standardized is not None: + return standardized + + # standardize language against default languages + standardized = get_language_from_json(language, 'default') + if standardized is not None: + return standardized + + return None + + +def get_language_from_json(language, key): + """Finds the given language in a json file.""" + + file_name = os.path.join( + os.path.dirname(__file__), + 'languages', + '{0}.json').format(key.lower()) + + try: + with open(file_name, 'r', encoding='utf-8') as fh: + languages = json.loads(fh.read()) + if language in languages.values(): + return language + if languages.get(language): + return languages[language] + except: + pass + + return None + + +def get_file_head(file_name): + """Returns the first 512000 bytes of the file's contents.""" text = None try: