mirror of
https://github.com/wakatime/sublime-wakatime.git
synced 2023-08-10 21:13:02 +03:00
Compare commits
115 Commits
Author | SHA1 | Date | |
---|---|---|---|
1b07d0442b | |||
fbd8e84ea1 | |||
01c0e7758e | |||
28556de3b6 | |||
809e43cfe5 | |||
22ddbe27b0 | |||
ddaf60b8b0 | |||
2e6a87c67e | |||
01503b1c20 | |||
5a4ac9c11d | |||
fa0a3aacb5 | |||
d588451468 | |||
483d8f596e | |||
03f2d6d580 | |||
e1390d7647 | |||
c87bdd041c | |||
3a65395636 | |||
0b2f3aa9a4 | |||
58ef2cd794 | |||
04173d3bcc | |||
3206a07476 | |||
9330236816 | |||
885c11f01a | |||
8acda0157a | |||
935ddbd5f6 | |||
b57b1eb696 | |||
6ec097b9d1 | |||
b3ed36d3b2 | |||
3669e4df6a | |||
3504096082 | |||
5990947706 | |||
2246e31244 | |||
b55fe702d3 | |||
e0fbbb50bb | |||
32c0cb5a97 | |||
67d8b0d24f | |||
b8b2f4944b | |||
a20161164c | |||
405211bb07 | |||
ffc879c4eb | |||
1e23919694 | |||
b2086a3cd2 | |||
005b07520c | |||
60608bd322 | |||
cde8f8f1de | |||
4adfca154c | |||
f7b3924a30 | |||
db00024455 | |||
9a6be7ca4e | |||
1ea9b2a761 | |||
bd5e87e030 | |||
0256ff4a6a | |||
9d170b3276 | |||
c54e575210 | |||
07513d8f10 | |||
30902cc050 | |||
aa7962d49a | |||
d8c662f3db | |||
10d88ebf2d | |||
2f28c561b1 | |||
24968507df | |||
641cd539ed | |||
0c65d7e5b2 | |||
f0532f5b8e | |||
8094db9680 | |||
bf20551849 | |||
2b6e32b578 | |||
363c3d38e2 | |||
88466d7db2 | |||
122fcbbee5 | |||
c41fcec5d8 | |||
be09b34d44 | |||
e1ee1c1216 | |||
a37061924b | |||
da01fa268b | |||
c279418651 | |||
5cf2c8f7ac | |||
d1455e77a8 | |||
8499e7bafe | |||
abc26a0864 | |||
71ad97ffe9 | |||
3ec5995c99 | |||
195cf4de36 | |||
b39eefb4f5 | |||
bbf5761e26 | |||
c4df1dc633 | |||
360a491cda | |||
f61a34eda7 | |||
48123d7409 | |||
c8a15d7ac0 | |||
202df81e04 | |||
5e34f3f6a7 | |||
d4441e5575 | |||
9eac8e2bd3 | |||
11d8fc3a09 | |||
d1f1f51f23 | |||
b10bb36c09 | |||
dc9474befa | |||
b910807e98 | |||
bc770515f0 | |||
9e102d7c5c | |||
5c1770fb48 | |||
683397534c | |||
1c92017543 | |||
fda1307668 | |||
1c84d457c5 | |||
1e680ce739 | |||
376adbb7d7 | |||
e0040e185b | |||
c4a88541d0 | |||
0cf621d177 | |||
db9d6cec97 | |||
2c17f49a6b | |||
95116d6007 | |||
8c52596f8f |
1
AUTHORS
1
AUTHORS
@ -14,3 +14,4 @@ Patches and Suggestions
|
||||
- Jimmy Selgen Nielsen <jimmy.selgen@gmail.com>
|
||||
- Patrik Kernstock <info@pkern.at>
|
||||
- Krishna Glick <krishnaglick@gmail.com>
|
||||
- Carlos Henrique Gandarez <gandarez@gmail.com>
|
||||
|
279
HISTORY.rst
279
HISTORY.rst
@ -3,6 +3,285 @@ History
|
||||
-------
|
||||
|
||||
|
||||
10.0.0 (2020-12-28)
|
||||
++++++++++++++++++
|
||||
|
||||
- Support for standalone wakatime-cli, disabled by default.
|
||||
|
||||
|
||||
9.1.2 (2020-02-13)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v13.0.7.
|
||||
- Split bundled pygments library for Python 2.7+.
|
||||
- Upgrade pygments for py27+ to v2.5.2 development master.
|
||||
- Force requests to use bundled ca cert from certifi by default.
|
||||
- Upgrade bundled certifi to v2019.11.28.
|
||||
|
||||
|
||||
9.1.1 (2020-02-11)
|
||||
++++++++++++++++++
|
||||
|
||||
- Fix typo in python detection on Windows platform.
|
||||
|
||||
|
||||
9.1.0 (2020-02-09)
|
||||
++++++++++++++++++
|
||||
|
||||
- Detect python in Windows LocalAppData install locations.
|
||||
- Upgrade wakatime-cli to v13.0.4.
|
||||
- Bundle cryptography, pyopenssl, and ipaddress packages for improved SSL
|
||||
support on Python2.
|
||||
|
||||
|
||||
9.0.2 (2019-12-04)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v13.0.3.
|
||||
- Support slashes in Mercurial and Git branch names.
|
||||
`wakatime#199 <https://github.com/wakatime/wakatime/issues/199>`_
|
||||
|
||||
|
||||
9.0.1 (2019-11-24)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v13.0.2.
|
||||
- Filter dependencies longer than 200 characters.
|
||||
- Close sqlite connection even when error raised.
|
||||
`wakatime#196 <https://github.com/wakatime/wakatime/issues/196>`_
|
||||
- Detect ColdFusion as root language instead of HTML.
|
||||
- New arguments for reading and writing ini config file.
|
||||
- Today argument shows categories when available.
|
||||
- Prevent unnecessarily debug log when syncing offline heartbeats.
|
||||
- Support for Python 3.7.
|
||||
|
||||
|
||||
9.0.0 (2019-06-23)
|
||||
++++++++++++++++++
|
||||
|
||||
- New optional config option hide_branch_names.
|
||||
`wakatime#183 <https://github.com/wakatime/wakatime/issues/183>`_
|
||||
|
||||
|
||||
8.7.0 (2019-05-29)
|
||||
++++++++++++++++++
|
||||
|
||||
- Prevent creating user sublime-settings file when api key already exists in
|
||||
common wakatime.cfg file.
|
||||
`#98 <https://github.com/wakatime/sublime-wakatime/issues/98>`_
|
||||
|
||||
|
||||
8.6.1 (2019-05-28)
|
||||
++++++++++++++++++
|
||||
|
||||
- Fix parsing common wakatime.cfg file.
|
||||
`#98 <https://github.com/wakatime/sublime-wakatime/issues/98>`_
|
||||
|
||||
|
||||
8.6.0 (2019-05-27)
|
||||
++++++++++++++++++
|
||||
|
||||
- Prevent prompting for api key when found from config file.
|
||||
`#98 <https://github.com/wakatime/sublime-wakatime/issues/98>`_
|
||||
|
||||
|
||||
8.5.0 (2019-05-10)
|
||||
++++++++++++++++++
|
||||
|
||||
- Remove clock icon from status bar.
|
||||
- Use wakatime-cli to fetch status bar coding time.
|
||||
|
||||
|
||||
8.4.2 (2019-05-07)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v11.0.0.
|
||||
- Rename argument --show-time-today to --today.
|
||||
- New argument --show-time-today for printing Today's coding time.
|
||||
|
||||
|
||||
8.4.1 (2019-05-01)
|
||||
++++++++++++++++++
|
||||
|
||||
- Use api subdomain for fetching today's coding activity.
|
||||
|
||||
|
||||
8.4.0 (2019-05-01)
|
||||
++++++++++++++++++
|
||||
|
||||
- Show today's coding time in status bar.
|
||||
|
||||
|
||||
8.3.6 (2019-04-30)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.8.4.
|
||||
- Use wakatime fork of certifi package.
|
||||
`#95 <https://github.com/wakatime/sublime-wakatime/issues/95>`_
|
||||
|
||||
|
||||
8.3.5 (2019-04-30)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.8.3.
|
||||
- Upgrade certifi to version 2019.03.09.
|
||||
|
||||
|
||||
8.3.4 (2019-03-30)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.8.2.
|
||||
- Detect go.mod files as Go language.
|
||||
`jetbrains-wakatime#119 <https://github.com/wakatime/jetbrains-wakatime/issues/119>`_
|
||||
- Detect C++ language from all C++ file extensions.
|
||||
`vscode-wakatime#87 <https://github.com/wakatime/vscode-wakatime/issues/87>`_
|
||||
- Add ssl_certs_file arg and config for custom ca bundles.
|
||||
`wakatime#164 <https://github.com/wakatime/wakatime/issues/164>`_
|
||||
- Fix bug causing random project names when hide project names enabled.
|
||||
`vscode-wakatime#162 <https://github.com/wakatime/vscode-wakatime/issues/61>`_
|
||||
- Add support for UNC network shares without drive letter mapped on Winows.
|
||||
`wakatime#162 <https://github.com/wakatime/wakatime/issues/162>`_
|
||||
|
||||
|
||||
8.3.3 (2018-12-19)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.6.1.
|
||||
- Correctly parse include_only_with_project_file when set to false.
|
||||
`wakatime#161 <https://github.com/wakatime/wakatime/issues/161>`_
|
||||
- Support language argument for non-file entity types.
|
||||
- Send 25 heartbeats per API request.
|
||||
- New category "Writing Tests".
|
||||
`wakatime#156 <https://github.com/wakatime/wakatime/issues/156>`_
|
||||
- Fix bug caused by git config section without any submodule option defined.
|
||||
`wakatime#152 <https://github.com/wakatime/wakatime/issues/152>`_
|
||||
|
||||
|
||||
8.3.2 (2018-10-06)
|
||||
++++++++++++++++++
|
||||
|
||||
- Send buffered heartbeats to API every 30 seconds.
|
||||
|
||||
|
||||
8.3.1 (2018-10-05)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.4.1.
|
||||
- Send 50 offline heartbeats to API per request with 1 second delay in between.
|
||||
|
||||
|
||||
8.3.0 (2018-10-03)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.4.0.
|
||||
- Support logging coding activity to remote network drive files on Windows
|
||||
platform by detecting UNC path from drive letter.
|
||||
`wakatime#72 <https://github.com/wakatime/wakatime/issues/72>`_
|
||||
|
||||
|
||||
8.2.0 (2018-09-30)
|
||||
++++++++++++++++++
|
||||
|
||||
- Prevent opening cmd window on Windows when running wakatime-cli.
|
||||
`#91 <https://github.com/wakatime/sublime-wakatime/issues/91>`_
|
||||
- Upgrade wakatime-cli to v10.3.0.
|
||||
- Re-enable detecting projects from Subversion folder on Windows platform.
|
||||
- Prevent opening cmd window on Windows when detecting project from Subversion.
|
||||
- Run tests on Windows using Appveyor.
|
||||
|
||||
|
||||
8.1.2 (2018-09-20)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.2.4.
|
||||
- Default --sync-offline-activity to 100 instead of 5, so offline coding is
|
||||
synced to dashboard faster.
|
||||
- Batch heartbeats in groups of 10 per api request.
|
||||
- New config hide_project_name and argument --hide-project-names for
|
||||
obfuscating project names when sending coding activity to api.
|
||||
- Fix mispelled Gosu language.
|
||||
`wakatime#137 <https://github.com/wakatime/wakatime/issues/137>`_
|
||||
- Remove metadata when hiding project or file names.
|
||||
- New --local-file argument to be used when --entity is a remote file.
|
||||
- New argument --sync-offline-activity for configuring the maximum offline
|
||||
heartbeats to sync to the WakaTime API.
|
||||
|
||||
|
||||
8.1.1 (2018-04-26)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.2.1.
|
||||
- Force forward slash for file paths.
|
||||
- New --category argument.
|
||||
- New --exclude-unknown-project argument and corresponding config setting.
|
||||
- Support for project detection from git worktree folders.
|
||||
|
||||
|
||||
8.1.0 (2018-04-03)
|
||||
++++++++++++++++++
|
||||
|
||||
- Prefer Python3 over Python2 when running wakatime-cli core.
|
||||
- Improve detection of Python3 on Ubuntu 17.10 platforms.
|
||||
|
||||
|
||||
8.0.8 (2018-03-15)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.1.3.
|
||||
- Smarter C vs C++ vs Objective-C language detection.
|
||||
|
||||
|
||||
8.0.7 (2018-03-15)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.1.2.
|
||||
- Detect dependencies from Swift, Objective-C, TypeScript and JavaScript files.
|
||||
- Categorize .mjs files as JavaScript.
|
||||
`wakatime#121 <https://github.com/wakatime/wakatime/issues/121>`_
|
||||
- Detect dependencies from Elm, Haskell, Haxe, Kotlin, Rust, and Scala files.
|
||||
- Improved Matlab vs Objective-C language detection.
|
||||
`wakatime#129 <https://github.com/wakatime/wakatime/issues/129>`_
|
||||
|
||||
|
||||
8.0.6 (2018-01-04)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.1.0.
|
||||
- Ability to only track folders containing a .wakatime-project file using new
|
||||
include_only_with_project_file argument and config option.
|
||||
|
||||
|
||||
8.0.5 (2017-11-24)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.0.5.
|
||||
- Fix bug that caused heartbeats to be cached locally instead of sent to API.
|
||||
|
||||
|
||||
8.0.4 (2017-11-23)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.0.4.
|
||||
- Improve Java dependency detection.
|
||||
- Skip null or missing heartbeats from extra heartbeats argument.
|
||||
|
||||
|
||||
8.0.3 (2017-11-22)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.0.3.
|
||||
- Support saving unicode heartbeats when working offline.
|
||||
`wakatime#112 <https://github.com/wakatime/wakatime/issues/112>`_
|
||||
|
||||
|
||||
8.0.2 (2017-11-15)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v10.0.2.
|
||||
- Limit bulk syncing to 5 heartbeats per request.
|
||||
`wakatime#109 <https://github.com/wakatime/wakatime/issues/109>`_
|
||||
|
||||
|
||||
8.0.1 (2017-11-09)
|
||||
++++++++++++++++++
|
||||
|
||||
|
3
LICENSE
3
LICENSE
@ -1,7 +1,6 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2014 by the respective authors (see AUTHORS file).
|
||||
All rights reserved.
|
||||
Copyright (c) 2014 Alan Hamlett.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
21
README.md
21
README.md
@ -1,11 +1,11 @@
|
||||
sublime-wakatime
|
||||
================
|
||||
# sublime-wakatime
|
||||
|
||||
Metrics, insights, and time tracking automatically generated from your programming activity.
|
||||
[](https://wakatime.com/badge/github/wakatime/sublime-wakatime)
|
||||
|
||||
[WakaTime][wakatime] is an open source plugin for metrics, insights, and time tracking automatically generated from your programming activity.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
## Installation
|
||||
|
||||
1. Install [Package Control](https://packagecontrol.io/installation).
|
||||
|
||||
@ -20,14 +20,12 @@ Installation
|
||||
6. Use Sublime and your coding activity will be displayed on your [WakaTime dashboard](https://wakatime.com).
|
||||
|
||||
|
||||
Screen Shots
|
||||
------------
|
||||
## Screen Shots
|
||||
|
||||

|
||||
|
||||
|
||||
Unresponsive Plugin Warning
|
||||
---------------------------
|
||||
## Unresponsive Plugin Warning
|
||||
|
||||
In Sublime Text 2, if you get a warning message:
|
||||
|
||||
@ -38,8 +36,7 @@ To fix this, go to `Preferences → Settings - User` then add the following sett
|
||||
`"detect_slow_plugins": false`
|
||||
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
## Troubleshooting
|
||||
|
||||
First, turn on debug mode in your `WakaTime.sublime-settings` file.
|
||||
|
||||
@ -53,7 +50,7 @@ Also, tail your `$HOME/.wakatime.log` file to debug wakatime cli problems.
|
||||
The [How to Debug Plugins][how to debug] guide shows how to check when coding activity was last received from your editor using the [User Agents API][user agents api].
|
||||
For more general troubleshooting info, see the [wakatime-cli Troubleshooting Section][wakatime-cli-help].
|
||||
|
||||
|
||||
[wakatime]: https://wakatime.com/sublime-text
|
||||
[wakatime-cli-help]: https://github.com/wakatime/wakatime#troubleshooting
|
||||
[how to debug]: https://wakatime.com/faq#debug-plugins
|
||||
[user agents api]: https://wakatime.com/developers#user_agents
|
||||
|
518
WakaTime.py
518
WakaTime.py
@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" ==========================================================
|
||||
File: WakaTime.py
|
||||
Description: Automatic time tracking for Sublime Text 2 and 3.
|
||||
@ -7,7 +8,7 @@ Website: https://wakatime.com/
|
||||
==========================================================="""
|
||||
|
||||
|
||||
__version__ = '8.0.1'
|
||||
__version__ = '10.0.0'
|
||||
|
||||
|
||||
import sublime
|
||||
@ -18,15 +19,18 @@ import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import ssl
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import traceback
|
||||
import urllib
|
||||
import webbrowser
|
||||
from datetime import datetime
|
||||
from subprocess import Popen, STDOUT, PIPE
|
||||
from subprocess import STDOUT, PIPE
|
||||
from zipfile import ZipFile
|
||||
|
||||
try:
|
||||
import _winreg as winreg # py2
|
||||
except ImportError:
|
||||
@ -34,14 +38,26 @@ except ImportError:
|
||||
import winreg # py3
|
||||
except ImportError:
|
||||
winreg = None
|
||||
|
||||
try:
|
||||
import Queue as queue # py2
|
||||
except ImportError:
|
||||
import queue # py3
|
||||
|
||||
try:
|
||||
import ConfigParser as configparser
|
||||
except ImportError:
|
||||
import configparser
|
||||
try:
|
||||
from urllib2 import urlretrieve
|
||||
except ImportError:
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
|
||||
is_py2 = (sys.version_info[0] == 2)
|
||||
is_py3 = (sys.version_info[0] == 3)
|
||||
is_win = platform.system() == 'Windows'
|
||||
|
||||
|
||||
if is_py2:
|
||||
def u(text):
|
||||
@ -91,11 +107,28 @@ else:
|
||||
))
|
||||
|
||||
|
||||
class Popen(subprocess.Popen):
|
||||
"""Patched Popen to prevent opening cmd window on Windows platform."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
startupinfo = kwargs.get('startupinfo')
|
||||
if is_win or True:
|
||||
try:
|
||||
startupinfo = startupinfo or subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
except AttributeError:
|
||||
pass
|
||||
kwargs['startupinfo'] = startupinfo
|
||||
super(Popen, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
# globals
|
||||
HEARTBEAT_FREQUENCY = 2
|
||||
ST_VERSION = int(sublime.version())
|
||||
PLUGIN_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
RESOURCES_FOLDER = os.path.join(os.getenv('APPDATA'), 'WakaTime') if is_win else os.path.join(os.path.expanduser('~'), '.wakatime')
|
||||
API_CLIENT = os.path.join(PLUGIN_DIR, 'packages', 'wakatime', 'cli.py')
|
||||
API_CLIENT_STANDALONE = os.path.join(RESOURCES_FOLDER, 'wakatime-cli', 'wakatime-cli' + ('.exe' if is_win else ''))
|
||||
S3_HOST = 'https://wakatime-cli.s3-us-west-2.amazonaws.com'
|
||||
SETTINGS_FILE = 'WakaTime.sublime-settings'
|
||||
SETTINGS = {}
|
||||
LAST_HEARTBEAT = {
|
||||
@ -103,8 +136,14 @@ LAST_HEARTBEAT = {
|
||||
'file': None,
|
||||
'is_write': False,
|
||||
}
|
||||
LAST_HEARTBEAT_SENT_AT = 0
|
||||
LAST_FETCH_TODAY_CODING_TIME = 0
|
||||
FETCH_TODAY_DEBOUNCE_COUNTER = 0
|
||||
FETCH_TODAY_DEBOUNCE_SECONDS = 60
|
||||
PYTHON_LOCATION = None
|
||||
HEARTBEATS = queue.Queue()
|
||||
HEARTBEAT_FREQUENCY = 2 # minutes between logging heartbeat when editing same file
|
||||
SEND_BUFFER_SECONDS = 30 # seconds between sending buffered heartbeats to API
|
||||
|
||||
|
||||
# Log Levels
|
||||
@ -117,9 +156,70 @@ ERROR = 'ERROR'
|
||||
# add wakatime package to path
|
||||
sys.path.insert(0, os.path.join(PLUGIN_DIR, 'packages'))
|
||||
try:
|
||||
from wakatime.main import parseConfigFile
|
||||
from wakatime.configs import parseConfigFile
|
||||
except ImportError:
|
||||
pass
|
||||
def _configFile(self):
|
||||
home = os.environ.get('WAKATIME_HOME')
|
||||
if home:
|
||||
return os.path.join(os.path.expanduser(home), '.wakatime.cfg')
|
||||
|
||||
return os.path.join(os.path.expanduser('~'), '.wakatime.cfg')
|
||||
|
||||
def parseConfigFile(self):
|
||||
"""Returns a configparser.SafeConfigParser instance with configs
|
||||
read from the config file. Default location of the config file is
|
||||
at ~/.wakatime.cfg.
|
||||
"""
|
||||
|
||||
configFile = _configFile()
|
||||
|
||||
configs = configparser.SafeConfigParser()
|
||||
try:
|
||||
with open(configFile, 'r', encoding='utf-8') as fh:
|
||||
try:
|
||||
configs.readfp(fh)
|
||||
return configs
|
||||
except configparser.Error:
|
||||
log(ERROR, traceback.format_exc())
|
||||
return None
|
||||
except IOError:
|
||||
log(DEBUG, "Error: Could not read from config file {0}\n".format(configFile))
|
||||
return None
|
||||
|
||||
|
||||
class ApiKey(object):
|
||||
_key = None
|
||||
|
||||
def read(self):
|
||||
if self._key:
|
||||
return self._key
|
||||
|
||||
key = SETTINGS.get('api_key')
|
||||
if key:
|
||||
self._key = key
|
||||
return self._key
|
||||
|
||||
try:
|
||||
configs = parseConfigFile()
|
||||
if configs:
|
||||
if configs.has_option('settings', 'api_key'):
|
||||
key = configs.get('settings', 'api_key')
|
||||
if key:
|
||||
self._key = key
|
||||
return self._key
|
||||
except:
|
||||
pass
|
||||
|
||||
return self._key
|
||||
|
||||
def write(self, key):
|
||||
global SETTINGS
|
||||
self._key = key
|
||||
SETTINGS.set('api_key', str(key))
|
||||
sublime.save_settings(SETTINGS_FILE)
|
||||
|
||||
|
||||
APIKEY = ApiKey()
|
||||
|
||||
|
||||
def set_timeout(callback, seconds):
|
||||
@ -153,52 +253,111 @@ def log(lvl, message, *args, **kwargs):
|
||||
set_timeout(lambda: log(lvl, message, *args, **kwargs), 0)
|
||||
|
||||
|
||||
def resources_folder():
|
||||
if platform.system() == 'Windows':
|
||||
return os.path.join(os.getenv('APPDATA'), 'WakaTime')
|
||||
else:
|
||||
return os.path.join(os.path.expanduser('~'), '.wakatime')
|
||||
|
||||
|
||||
def update_status_bar(status):
|
||||
def update_status_bar(status=None, debounced=False, msg=None):
|
||||
"""Updates the status bar."""
|
||||
global LAST_FETCH_TODAY_CODING_TIME, FETCH_TODAY_DEBOUNCE_COUNTER
|
||||
|
||||
try:
|
||||
if SETTINGS.get('status_bar_message'):
|
||||
msg = datetime.now().strftime(SETTINGS.get('status_bar_message_fmt'))
|
||||
if '{status}' in msg:
|
||||
msg = msg.format(status=status)
|
||||
if not msg and SETTINGS.get('status_bar_message') is not False and SETTINGS.get('status_bar_enabled'):
|
||||
if SETTINGS.get('status_bar_coding_activity') and status == 'OK':
|
||||
if debounced:
|
||||
FETCH_TODAY_DEBOUNCE_COUNTER -= 1
|
||||
if debounced or not LAST_FETCH_TODAY_CODING_TIME:
|
||||
now = int(time.time())
|
||||
if LAST_FETCH_TODAY_CODING_TIME and (FETCH_TODAY_DEBOUNCE_COUNTER > 0 or LAST_FETCH_TODAY_CODING_TIME > now - FETCH_TODAY_DEBOUNCE_SECONDS):
|
||||
return
|
||||
LAST_FETCH_TODAY_CODING_TIME = now
|
||||
FetchStatusBarCodingTime().start()
|
||||
return
|
||||
else:
|
||||
FETCH_TODAY_DEBOUNCE_COUNTER += 1
|
||||
set_timeout(lambda: update_status_bar(status, debounced=True), FETCH_TODAY_DEBOUNCE_SECONDS)
|
||||
return
|
||||
else:
|
||||
msg = 'WakaTime: {status}'.format(status=status)
|
||||
|
||||
if msg:
|
||||
active_window = sublime.active_window()
|
||||
if active_window:
|
||||
for view in active_window.views():
|
||||
view.set_status('wakatime', msg)
|
||||
|
||||
except RuntimeError:
|
||||
set_timeout(lambda: update_status_bar(status), 0)
|
||||
set_timeout(lambda: update_status_bar(status=status, debounced=debounced, msg=msg), 0)
|
||||
|
||||
|
||||
class FetchStatusBarCodingTime(threading.Thread):
|
||||
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
self.debug = SETTINGS.get('debug')
|
||||
self.api_key = APIKEY.read() or ''
|
||||
self.proxy = SETTINGS.get('proxy')
|
||||
self.python_binary = SETTINGS.get('python_binary')
|
||||
self.standalone = SETTINGS.get('standalone')
|
||||
|
||||
def run(self):
|
||||
if not self.api_key:
|
||||
log(DEBUG, 'Missing WakaTime API key.')
|
||||
return
|
||||
if not isCliInstalled():
|
||||
return
|
||||
|
||||
ua = 'sublime/%d sublime-wakatime/%s' % (ST_VERSION, __version__)
|
||||
|
||||
cmd = []
|
||||
if self.standalone:
|
||||
cmd.append(API_CLIENT_STANDALONE)
|
||||
else:
|
||||
python = self.python_binary
|
||||
if not python or not python.strip():
|
||||
python = python_binary()
|
||||
if not python:
|
||||
log(DEBUG, 'Missing Python.')
|
||||
return
|
||||
cmd.extend([
|
||||
python,
|
||||
API_CLIENT,
|
||||
])
|
||||
|
||||
cmd.extend([
|
||||
'--today',
|
||||
'--key', str(bytes.decode(self.api_key.encode('utf8'))),
|
||||
'--plugin', ua,
|
||||
])
|
||||
if self.debug:
|
||||
cmd.append('--verbose')
|
||||
if self.proxy:
|
||||
cmd.extend(['--proxy', self.proxy])
|
||||
|
||||
log(DEBUG, ' '.join(obfuscate_apikey(cmd)))
|
||||
try:
|
||||
process = Popen(cmd, stdout=PIPE, stderr=STDOUT)
|
||||
output, err = process.communicate()
|
||||
output = u(output)
|
||||
retcode = process.poll()
|
||||
if not retcode and output:
|
||||
msg = 'Today: {output}'.format(output=output)
|
||||
update_status_bar(msg=msg)
|
||||
else:
|
||||
log(DEBUG, 'wakatime-core today exited with status: {0}'.format(retcode))
|
||||
if output:
|
||||
log(DEBUG, u('wakatime-core today output: {0}').format(output))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def prompt_api_key():
|
||||
global SETTINGS
|
||||
|
||||
if SETTINGS.get('api_key'):
|
||||
if APIKEY.read():
|
||||
return True
|
||||
|
||||
default_key = ''
|
||||
try:
|
||||
configs = parseConfigFile()
|
||||
if configs is not None:
|
||||
if configs.has_option('settings', 'api_key'):
|
||||
default_key = configs.get('settings', 'api_key')
|
||||
except:
|
||||
pass
|
||||
|
||||
window = sublime.active_window()
|
||||
if window:
|
||||
def got_key(text):
|
||||
if text:
|
||||
SETTINGS.set('api_key', str(text))
|
||||
sublime.save_settings(SETTINGS_FILE)
|
||||
window.show_input_panel('[WakaTime] Enter your wakatime.com api key:', default_key, got_key, None, None)
|
||||
APIKEY.write(text)
|
||||
window.show_input_panel('[WakaTime] Enter your wakatime.com api key:', '', got_key, None, None)
|
||||
return True
|
||||
else:
|
||||
log(ERROR, 'Could not prompt for api key because no window found.')
|
||||
@ -211,12 +370,27 @@ def python_binary():
|
||||
|
||||
# look for python in PATH and common install locations
|
||||
paths = [
|
||||
os.path.join(resources_folder(), 'python'),
|
||||
os.path.join(RESOURCES_FOLDER, 'python'),
|
||||
None,
|
||||
'/',
|
||||
'/usr/local/bin/',
|
||||
'/usr/bin/',
|
||||
]
|
||||
|
||||
if is_win and os.getenv('LOCALAPPDATA'):
|
||||
appdata = os.getenv('LOCALAPPDATA')
|
||||
ver = 39
|
||||
while ver >= 27:
|
||||
if ver >= 30 and ver <= 33:
|
||||
ver -= 1
|
||||
continue
|
||||
paths.append('\\python{ver}\\'.format(ver=ver))
|
||||
paths.append('\\Python{ver}\\'.format(ver=ver))
|
||||
paths.append('{appdata}\\Programs\\Python{ver}\\'.format(appdata=appdata, ver=ver))
|
||||
paths.append('{appdata}\\Programs\\Python{ver}-32\\'.format(appdata=appdata, ver=ver))
|
||||
paths.append('{appdata}\\Programs\\Python{ver}-64\\'.format(appdata=appdata, ver=ver))
|
||||
ver -= 1
|
||||
|
||||
for path in paths:
|
||||
path = find_python_in_folder(path)
|
||||
if path is not None:
|
||||
@ -243,7 +417,7 @@ def set_python_binary_location(path):
|
||||
|
||||
|
||||
def find_python_from_registry(location, reg=None):
|
||||
if platform.system() != 'Windows' or winreg is None:
|
||||
if not is_win or winreg is None:
|
||||
return None
|
||||
|
||||
if reg is None:
|
||||
@ -305,13 +479,15 @@ def find_python_from_registry(location, reg=None):
|
||||
return val
|
||||
|
||||
|
||||
def find_python_in_folder(folder, headless=True):
|
||||
def find_python_in_folder(folder, python3=True, headless=True):
|
||||
pattern = re.compile(r'\d+\.\d+')
|
||||
|
||||
path = 'python'
|
||||
if folder is not None:
|
||||
if folder:
|
||||
path = os.path.realpath(os.path.join(folder, 'python'))
|
||||
if headless:
|
||||
if python3:
|
||||
path = u(path) + u('3')
|
||||
elif headless:
|
||||
path = u(path) + u('w')
|
||||
log(DEBUG, u('Looking for Python at: {0}').format(u(path)))
|
||||
try:
|
||||
@ -325,9 +501,13 @@ def find_python_in_folder(folder, headless=True):
|
||||
except:
|
||||
log(DEBUG, u(sys.exc_info()[1]))
|
||||
|
||||
if headless:
|
||||
path = find_python_in_folder(folder, headless=False)
|
||||
if path is not None:
|
||||
if python3:
|
||||
path = find_python_in_folder(folder, python3=False, headless=headless)
|
||||
if path:
|
||||
return path
|
||||
elif headless:
|
||||
path = find_python_in_folder(folder, python3=python3, headless=False)
|
||||
if path:
|
||||
return path
|
||||
|
||||
return None
|
||||
@ -427,11 +607,21 @@ def append_heartbeat(entity, timestamp, is_write, view, project, folders):
|
||||
}
|
||||
|
||||
# process the queue of heartbeats in the future
|
||||
seconds = 4
|
||||
set_timeout(process_queue, seconds)
|
||||
set_timeout(lambda: process_queue(timestamp), SEND_BUFFER_SECONDS)
|
||||
|
||||
|
||||
def process_queue():
|
||||
def process_queue(timestamp):
|
||||
global LAST_HEARTBEAT_SENT_AT
|
||||
|
||||
if not isCliInstalled():
|
||||
return
|
||||
|
||||
# Prevent sending heartbeats more often than SEND_BUFFER_SECONDS
|
||||
now = int(time.time())
|
||||
if timestamp != LAST_HEARTBEAT['time'] and LAST_HEARTBEAT_SENT_AT > now - SEND_BUFFER_SECONDS:
|
||||
return
|
||||
LAST_HEARTBEAT_SENT_AT = now
|
||||
|
||||
try:
|
||||
heartbeat = HEARTBEATS.get_nowait()
|
||||
except queue.Empty:
|
||||
@ -460,12 +650,13 @@ class SendHeartbeatsThread(threading.Thread):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
self.debug = SETTINGS.get('debug')
|
||||
self.api_key = SETTINGS.get('api_key', '')
|
||||
self.api_key = APIKEY.read() or ''
|
||||
self.ignore = SETTINGS.get('ignore', [])
|
||||
self.include = SETTINGS.get('include', [])
|
||||
self.hidefilenames = SETTINGS.get('hidefilenames')
|
||||
self.proxy = SETTINGS.get('proxy')
|
||||
self.python_binary = SETTINGS.get('python_binary')
|
||||
self.standalone = SETTINGS.get('standalone')
|
||||
|
||||
self.heartbeat = heartbeat
|
||||
self.has_extra_heartbeats = False
|
||||
@ -502,14 +693,10 @@ class SendHeartbeatsThread(threading.Thread):
|
||||
return heartbeat
|
||||
|
||||
def send_heartbeats(self):
|
||||
python = self.python_binary
|
||||
if not python or not python.strip():
|
||||
python = python_binary()
|
||||
if python:
|
||||
if self.standalone:
|
||||
heartbeat = self.build_heartbeat(**self.heartbeat)
|
||||
ua = 'sublime/%d sublime-wakatime/%s' % (ST_VERSION, __version__)
|
||||
cmd = [
|
||||
python,
|
||||
API_CLIENT,
|
||||
'--entity', heartbeat['entity'],
|
||||
'--time', str('%f' % heartbeat['timestamp']),
|
||||
@ -536,21 +723,17 @@ class SendHeartbeatsThread(threading.Thread):
|
||||
if self.has_extra_heartbeats:
|
||||
cmd.append('--extra-heartbeats')
|
||||
stdin = PIPE
|
||||
extra_heartbeats = [self.build_heartbeat(**x) for x in self.extra_heartbeats]
|
||||
extra_heartbeats = json.dumps(extra_heartbeats)
|
||||
extra_heartbeats = json.dumps([self.build_heartbeat(**x) for x in self.extra_heartbeats])
|
||||
inp = "{0}\n".format(extra_heartbeats).encode('utf-8')
|
||||
else:
|
||||
extra_heartbeats = None
|
||||
stdin = None
|
||||
inp = None
|
||||
|
||||
log(DEBUG, ' '.join(obfuscate_apikey(cmd)))
|
||||
try:
|
||||
process = Popen(cmd, stdin=stdin, stdout=PIPE, stderr=STDOUT)
|
||||
inp = None
|
||||
if self.has_extra_heartbeats:
|
||||
inp = "{0}\n".format(extra_heartbeats)
|
||||
inp = inp.encode('utf-8')
|
||||
output, err = process.communicate(input=inp)
|
||||
output = u(output)
|
||||
output, _err = process.communicate(input=inp)
|
||||
retcode = process.poll()
|
||||
if (not retcode or retcode == 102) and not output:
|
||||
self.sent()
|
||||
@ -563,10 +746,69 @@ class SendHeartbeatsThread(threading.Thread):
|
||||
except:
|
||||
log(ERROR, u(sys.exc_info()[1]))
|
||||
update_status_bar('Error')
|
||||
|
||||
else:
|
||||
log(ERROR, 'Unable to find python binary.')
|
||||
update_status_bar('Error')
|
||||
python = self.python_binary
|
||||
if not python or not python.strip():
|
||||
python = python_binary()
|
||||
if python:
|
||||
heartbeat = self.build_heartbeat(**self.heartbeat)
|
||||
ua = 'sublime/%d sublime-wakatime/%s' % (ST_VERSION, __version__)
|
||||
cmd = [
|
||||
python,
|
||||
API_CLIENT,
|
||||
'--entity', heartbeat['entity'],
|
||||
'--time', str('%f' % heartbeat['timestamp']),
|
||||
'--plugin', ua,
|
||||
]
|
||||
if self.api_key:
|
||||
cmd.extend(['--key', str(bytes.decode(self.api_key.encode('utf8')))])
|
||||
if heartbeat['is_write']:
|
||||
cmd.append('--write')
|
||||
if heartbeat.get('alternate_project'):
|
||||
cmd.extend(['--alternate-project', heartbeat['alternate_project']])
|
||||
if heartbeat.get('cursorpos') is not None:
|
||||
cmd.extend(['--cursorpos', heartbeat['cursorpos']])
|
||||
for pattern in self.ignore:
|
||||
cmd.extend(['--exclude', pattern])
|
||||
for pattern in self.include:
|
||||
cmd.extend(['--include', pattern])
|
||||
if self.debug:
|
||||
cmd.append('--verbose')
|
||||
if self.hidefilenames:
|
||||
cmd.append('--hidefilenames')
|
||||
if self.proxy:
|
||||
cmd.extend(['--proxy', self.proxy])
|
||||
if self.has_extra_heartbeats:
|
||||
cmd.append('--extra-heartbeats')
|
||||
stdin = PIPE
|
||||
extra_heartbeats = json.dumps([self.build_heartbeat(**x) for x in self.extra_heartbeats])
|
||||
inp = "{0}\n".format(extra_heartbeats).encode('utf-8')
|
||||
else:
|
||||
extra_heartbeats = None
|
||||
stdin = None
|
||||
inp = None
|
||||
|
||||
log(DEBUG, ' '.join(obfuscate_apikey(cmd)))
|
||||
try:
|
||||
process = Popen(cmd, stdin=stdin, stdout=PIPE, stderr=STDOUT)
|
||||
output, err = process.communicate(input=inp)
|
||||
output = u(output)
|
||||
retcode = process.poll()
|
||||
if (not retcode or retcode == 102) and not output:
|
||||
self.sent()
|
||||
else:
|
||||
update_status_bar('Error')
|
||||
if retcode:
|
||||
log(DEBUG if retcode == 102 else ERROR, 'wakatime-core exited with status: {0}'.format(retcode))
|
||||
if output:
|
||||
log(ERROR, u('wakatime-core output: {0}').format(output))
|
||||
except:
|
||||
log(ERROR, u(sys.exc_info()[1]))
|
||||
update_status_bar('Error')
|
||||
|
||||
else:
|
||||
log(ERROR, 'Unable to find python binary.')
|
||||
update_status_bar('Error')
|
||||
|
||||
def sent(self):
|
||||
update_status_bar('OK')
|
||||
@ -584,17 +826,17 @@ class DownloadPython(threading.Thread):
|
||||
def run(self):
|
||||
log(INFO, 'Downloading embeddable Python...')
|
||||
|
||||
ver = '3.5.2'
|
||||
ver = '3.8.1'
|
||||
arch = 'amd64' if platform.architecture()[0] == '64bit' else 'win32'
|
||||
url = 'https://www.python.org/ftp/python/{ver}/python-{ver}-embed-{arch}.zip'.format(
|
||||
ver=ver,
|
||||
arch=arch,
|
||||
)
|
||||
|
||||
if not os.path.exists(resources_folder()):
|
||||
os.makedirs(resources_folder())
|
||||
if not os.path.exists(RESOURCES_FOLDER):
|
||||
os.makedirs(RESOURCES_FOLDER)
|
||||
|
||||
zip_file = os.path.join(resources_folder(), 'python.zip')
|
||||
zip_file = os.path.join(RESOURCES_FOLDER, 'python.zip')
|
||||
try:
|
||||
urllib.urlretrieve(url, zip_file)
|
||||
except AttributeError:
|
||||
@ -602,7 +844,7 @@ class DownloadPython(threading.Thread):
|
||||
|
||||
log(INFO, 'Extracting Python...')
|
||||
with contextlib.closing(ZipFile(zip_file)) as zf:
|
||||
path = os.path.join(resources_folder(), 'python')
|
||||
path = os.path.join(RESOURCES_FOLDER, 'python')
|
||||
zf.extractall(path)
|
||||
|
||||
try:
|
||||
@ -618,15 +860,22 @@ def plugin_loaded():
|
||||
SETTINGS = sublime.load_settings(SETTINGS_FILE)
|
||||
|
||||
log(INFO, 'Initializing WakaTime plugin v%s' % __version__)
|
||||
update_status_bar('Initializing')
|
||||
update_status_bar('Initializing...')
|
||||
|
||||
if not python_binary():
|
||||
log(WARNING, 'Python binary not found.')
|
||||
if platform.system() == 'Windows':
|
||||
set_timeout(download_python, 0)
|
||||
else:
|
||||
sublime.error_message("Unable to find Python binary!\nWakaTime needs Python to work correctly.\n\nGo to https://www.python.org/downloads")
|
||||
return
|
||||
standalone = SETTINGS.get('standalone')
|
||||
|
||||
if standalone:
|
||||
if not isCliLatest():
|
||||
thread = DownloadCLI()
|
||||
thread.start()
|
||||
else:
|
||||
if not python_binary():
|
||||
log(WARNING, 'Python binary not found.')
|
||||
if is_win:
|
||||
set_timeout(download_python, 0)
|
||||
else:
|
||||
sublime.error_message("Unable to find Python binary!\nWakaTime needs Python to work correctly.\n\nGo to https://www.python.org/downloads")
|
||||
return
|
||||
|
||||
after_loaded()
|
||||
|
||||
@ -634,6 +883,7 @@ def plugin_loaded():
|
||||
def after_loaded():
|
||||
if not prompt_api_key():
|
||||
set_timeout(after_loaded, 0.5)
|
||||
update_status_bar('OK')
|
||||
|
||||
|
||||
# need to call plugin_loaded because only ST3 will auto-call it
|
||||
@ -659,3 +909,121 @@ class WakatimeDashboardCommand(sublime_plugin.ApplicationCommand):
|
||||
|
||||
def run(self):
|
||||
webbrowser.open_new_tab('https://wakatime.com/dashboard')
|
||||
|
||||
|
||||
class DownloadCLI(threading.Thread):
|
||||
"""Non-blocking thread for downloading latest wakatime-cli from GitHub.
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
log(INFO, 'Downloading wakatime-cli...')
|
||||
|
||||
if not os.path.exists(RESOURCES_FOLDER):
|
||||
os.makedirs(RESOURCES_FOLDER)
|
||||
|
||||
try:
|
||||
shutil.rmtree(os.path.join(RESOURCES_FOLDER, 'wakatime-cli'))
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
url = self._getCliUrl()
|
||||
zip_file = os.path.join(RESOURCES_FOLDER, 'wakatime-cli.zip')
|
||||
download(url, zip_file)
|
||||
|
||||
log(INFO, 'Extracting wakatime-cli...')
|
||||
with ZipFile(zip_file) as zf:
|
||||
zf.extractall(RESOURCES_FOLDER)
|
||||
|
||||
try:
|
||||
shutil.rmtree(os.path.join(RESOURCES_FOLDER, 'wakatime-cli.zip'))
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
log(DEBUG, traceback.format_exc())
|
||||
|
||||
log(INFO, 'Finished extracting wakatime-cli.')
|
||||
|
||||
def _getCliUrl(self):
|
||||
os = platform.system().lower().replace('darwin', 'mac')
|
||||
arch = '64' if sys.maxsize > 2**32 else '32'
|
||||
return '{host}/{os}-x86-{arch}/wakatime-cli.zip'.format(
|
||||
host=S3_HOST,
|
||||
os=os,
|
||||
arch=arch,
|
||||
)
|
||||
|
||||
|
||||
def isCliInstalled():
|
||||
return os.path.exists(API_CLIENT)
|
||||
|
||||
|
||||
def isCliLatest():
|
||||
if not isCliInstalled():
|
||||
return False
|
||||
|
||||
args = [API_CLIENT, '--version']
|
||||
stdout, stderr = Popen(args, stdout=PIPE, stderr=PIPE).communicate()
|
||||
stdout = (stdout or b'') + (stderr or b'')
|
||||
localVer = extractVersion(stdout.decode('utf-8'))
|
||||
if not localVer:
|
||||
return False
|
||||
|
||||
log(INFO, 'Current wakatime-cli version is %s' % localVer)
|
||||
log(INFO, 'Checking for updates to wakatime-cli...')
|
||||
|
||||
remoteVer = getLatestCliVersion()
|
||||
|
||||
if not remoteVer:
|
||||
return True
|
||||
|
||||
if remoteVer == localVer:
|
||||
log(INFO, 'wakatime-cli is up to date.')
|
||||
return True
|
||||
|
||||
log(INFO, 'Found an updated wakatime-cli v%s' % remoteVer)
|
||||
return False
|
||||
|
||||
|
||||
def getLatestCliVersion():
|
||||
url = getCliVersionUrl()
|
||||
try:
|
||||
localFile = os.path.join(RESOURCES_FOLDER, 'current_version.txt')
|
||||
download(url, localFile)
|
||||
ver = None
|
||||
with open(localFile) as fh:
|
||||
ver = extractVersion(fh.read())
|
||||
try:
|
||||
shutil.rmtree(localFile)
|
||||
except:
|
||||
pass
|
||||
return ver
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def getCliVersionUrl():
|
||||
os = platform.system().lower().replace('darwin', 'mac')
|
||||
arch = '64' if sys.maxsize > 2**32 else '32'
|
||||
return '{host}/{os}-x86-{arch}/current_version.txt'.format(
|
||||
host=S3_HOST,
|
||||
os=os,
|
||||
arch=arch,
|
||||
)
|
||||
|
||||
|
||||
def extractVersion(text):
|
||||
log(DEBUG, 'extracting version.')
|
||||
pattern = re.compile(r"([0-9]+\.[0-9]+\.[0-9]+)")
|
||||
match = pattern.search(text)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def download(url, filePath):
|
||||
try:
|
||||
urlretrieve(url, filePath)
|
||||
except IOError:
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
urlretrieve(url, filePath)
|
||||
|
@ -3,7 +3,7 @@
|
||||
// This settings file will be overwritten when upgrading.
|
||||
|
||||
{
|
||||
// Your api key from https://wakatime.com/#apikey
|
||||
// Your api key from https://wakatime.com/api-key
|
||||
// Set this in your User specific WakaTime.sublime-settings file.
|
||||
"api_key": "",
|
||||
|
||||
@ -21,16 +21,20 @@
|
||||
// POSIX regular expressions will bypass your ignore setting.
|
||||
"include": [".*"],
|
||||
|
||||
// Status bar message. Set to false to hide status bar message.
|
||||
// Defaults to true.
|
||||
"status_bar_message": true,
|
||||
// Status bar for surfacing errors and displaying today's coding time. Set
|
||||
// to false to hide. Defaults to true.
|
||||
"status_bar_enabled": true,
|
||||
|
||||
// Status bar message format.
|
||||
"status_bar_message_fmt": "WakaTime {status} %I:%M %p",
|
||||
// Show today's coding activity in WakaTime status bar item.
|
||||
// Defaults to true.
|
||||
"status_bar_coding_activity": true,
|
||||
|
||||
// Obfuscate file paths when sending to API. Your dashboard will no longer display coding activity per file.
|
||||
"hidefilenames": false,
|
||||
|
||||
// Python binary location. Uses python from your PATH by default.
|
||||
"python_binary": ""
|
||||
"python_binary": "",
|
||||
|
||||
// Use standalone compiled Python wakatime-cli (Will not work on ARM Macs)
|
||||
"standalone": false
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
__title__ = 'wakatime'
|
||||
__description__ = 'Common interface to the WakaTime api.'
|
||||
__url__ = 'https://github.com/wakatime/wakatime'
|
||||
__version_info__ = ('10', '0', '1')
|
||||
__version_info__ = ('13', '0', '7')
|
||||
__version__ = '.'.join(__version_info__)
|
||||
__author__ = 'Alan Hamlett'
|
||||
__author_email__ = 'alan@wakatime.com'
|
||||
|
@ -19,10 +19,10 @@ from .compat import u, is_py3, json
|
||||
from .constants import API_ERROR, AUTH_ERROR, SUCCESS, UNKNOWN_ERROR
|
||||
|
||||
from .offlinequeue import Queue
|
||||
from .packages.requests.exceptions import RequestException
|
||||
from .session_cache import SessionCache
|
||||
from .utils import get_hostname, get_user_agent
|
||||
from .packages import tzlocal
|
||||
from .packages import certifi
|
||||
|
||||
|
||||
log = logging.getLogger('WakaTime')
|
||||
@ -30,7 +30,7 @@ log = logging.getLogger('WakaTime')
|
||||
|
||||
try:
|
||||
from .packages import requests
|
||||
except ImportError:
|
||||
except ImportError: # pragma: nocover
|
||||
log.traceback(logging.ERROR)
|
||||
print(traceback.format_exc())
|
||||
log.error('Please upgrade Python to the latest version.')
|
||||
@ -38,6 +38,9 @@ except ImportError:
|
||||
sys.exit(UNKNOWN_ERROR)
|
||||
|
||||
|
||||
from .packages.requests.exceptions import RequestException
|
||||
|
||||
|
||||
def send_heartbeats(heartbeats, args, configs, use_ntlm_proxy=False):
|
||||
"""Send heartbeats to WakaTime API.
|
||||
|
||||
@ -104,7 +107,7 @@ def send_heartbeats(heartbeats, args, configs, use_ntlm_proxy=False):
|
||||
try:
|
||||
response = session.post(api_url, data=request_body, headers=headers,
|
||||
proxies=proxies, timeout=timeout,
|
||||
verify=not args.nosslverify)
|
||||
verify=_get_verify(args))
|
||||
except RequestException:
|
||||
if should_try_ntlm:
|
||||
return send_heartbeats(heartbeats, args, configs, use_ntlm_proxy=True)
|
||||
@ -138,50 +141,182 @@ def send_heartbeats(heartbeats, args, configs, use_ntlm_proxy=False):
|
||||
else:
|
||||
code = response.status_code if response is not None else None
|
||||
content = response.text if response is not None else None
|
||||
try:
|
||||
results = response.json() if response is not None else []
|
||||
except:
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.traceback(logging.WARNING)
|
||||
results = []
|
||||
if code == requests.codes.created or code == requests.codes.accepted:
|
||||
log.debug({
|
||||
'response_code': code,
|
||||
})
|
||||
|
||||
for i in range(len(results)):
|
||||
if len(heartbeats) <= i:
|
||||
log.debug('Results from server do not match heartbeats sent.')
|
||||
break
|
||||
|
||||
try:
|
||||
c = results[i][1]
|
||||
except:
|
||||
c = 0
|
||||
try:
|
||||
text = json.dumps(results[i][0])
|
||||
except:
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.traceback(logging.WARNING)
|
||||
text = ''
|
||||
handle_result([heartbeats[i]], c, text, args, configs)
|
||||
|
||||
if _success(code):
|
||||
results = _get_results(response)
|
||||
_process_server_results(heartbeats, code, content, results, args, configs)
|
||||
session_cache.save(session)
|
||||
return SUCCESS
|
||||
else:
|
||||
log.debug({
|
||||
'response_code': code,
|
||||
'response_text': content,
|
||||
})
|
||||
|
||||
if should_try_ntlm:
|
||||
return send_heartbeats(heartbeats, args, configs, use_ntlm_proxy=True)
|
||||
else:
|
||||
handle_result(heartbeats, code, content, args, configs)
|
||||
|
||||
_handle_unsent_heartbeats(heartbeats, code, content, args, configs)
|
||||
|
||||
session_cache.delete()
|
||||
return AUTH_ERROR if code == 401 else API_ERROR
|
||||
|
||||
|
||||
def handle_result(h, code, content, args, configs):
|
||||
if code == requests.codes.created or code == requests.codes.accepted:
|
||||
return
|
||||
def get_time_today(args, use_ntlm_proxy=False):
|
||||
"""Get coding time from WakaTime API for given time range.
|
||||
|
||||
Returns total time as string or `None` when unable to fetch summary from
|
||||
the API. When verbose output enabled, returns error message when unable to
|
||||
fetch summary.
|
||||
"""
|
||||
|
||||
url = 'https://api.wakatime.com/api/v1/users/current/summaries'
|
||||
timeout = args.timeout
|
||||
if not timeout:
|
||||
timeout = 60
|
||||
|
||||
api_key = u(base64.b64encode(str.encode(args.key) if is_py3 else args.key))
|
||||
auth = u('Basic {api_key}').format(api_key=api_key)
|
||||
headers = {
|
||||
'User-Agent': get_user_agent(args.plugin),
|
||||
'Accept': 'application/json',
|
||||
'Authorization': auth,
|
||||
}
|
||||
|
||||
session_cache = SessionCache()
|
||||
session = session_cache.get()
|
||||
|
||||
should_try_ntlm = False
|
||||
proxies = {}
|
||||
if args.proxy:
|
||||
if use_ntlm_proxy:
|
||||
from .packages.requests_ntlm import HttpNtlmAuth
|
||||
username = args.proxy.rsplit(':', 1)
|
||||
password = ''
|
||||
if len(username) == 2:
|
||||
password = username[1]
|
||||
username = username[0]
|
||||
session.auth = HttpNtlmAuth(username, password, session)
|
||||
else:
|
||||
should_try_ntlm = '\\' in args.proxy
|
||||
proxies['https'] = args.proxy
|
||||
|
||||
params = {
|
||||
'start': 'today',
|
||||
'end': 'today',
|
||||
}
|
||||
|
||||
# send request to api
|
||||
response, code = None, None
|
||||
try:
|
||||
response = session.get(url, params=params, headers=headers,
|
||||
proxies=proxies, timeout=timeout,
|
||||
verify=_get_verify(args))
|
||||
except RequestException:
|
||||
if should_try_ntlm:
|
||||
return get_time_today(args, use_ntlm_proxy=True)
|
||||
|
||||
session_cache.delete()
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
'traceback': traceback.format_exc(),
|
||||
}
|
||||
log.error(exception_data)
|
||||
return '{0}: {1}'.format(sys.exc_info()[0].__name__, u(sys.exc_info()[1])), API_ERROR
|
||||
return None, API_ERROR
|
||||
|
||||
except: # delete cached session when requests raises unknown exception
|
||||
if should_try_ntlm:
|
||||
return get_time_today(args, use_ntlm_proxy=True)
|
||||
|
||||
session_cache.delete()
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
'traceback': traceback.format_exc(),
|
||||
}
|
||||
log.error(exception_data)
|
||||
return '{0}: {1}'.format(sys.exc_info()[0].__name__, u(sys.exc_info()[1])), API_ERROR
|
||||
return None, API_ERROR
|
||||
|
||||
code = response.status_code if response is not None else None
|
||||
content = response.text if response is not None else None
|
||||
|
||||
if code == requests.codes.ok:
|
||||
try:
|
||||
summary = response.json()['data'][0]
|
||||
if len(summary['categories']) > 1:
|
||||
text = ', '.join(['{0} {1}'.format(x['text'], x['name'].lower()) for x in summary['categories']])
|
||||
else:
|
||||
text = summary['grand_total']['text']
|
||||
session_cache.save(session)
|
||||
return text, SUCCESS
|
||||
except:
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
'traceback': traceback.format_exc(),
|
||||
}
|
||||
log.error(exception_data)
|
||||
return '{0}: {1}'.format(sys.exc_info()[0].__name__, u(sys.exc_info()[1])), API_ERROR
|
||||
return None, API_ERROR
|
||||
else:
|
||||
if should_try_ntlm:
|
||||
return get_time_today(args, use_ntlm_proxy=True)
|
||||
|
||||
session_cache.delete()
|
||||
log.debug({
|
||||
'response_code': code,
|
||||
'response_text': content,
|
||||
})
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
return 'Error: {0}'.format(code), API_ERROR
|
||||
return None, API_ERROR
|
||||
|
||||
|
||||
def _get_verify(args):
|
||||
verify = not args.nosslverify
|
||||
if verify:
|
||||
if args.ssl_certs_file:
|
||||
verify = args.ssl_certs_file
|
||||
else:
|
||||
verify = certifi.where()
|
||||
return verify
|
||||
|
||||
|
||||
def _process_server_results(heartbeats, code, content, results, args, configs):
|
||||
log.debug({
|
||||
'response_code': code,
|
||||
'results': results,
|
||||
})
|
||||
|
||||
for i in range(len(results)):
|
||||
if len(heartbeats) <= i:
|
||||
log.warn('Results from api not matching heartbeats sent.')
|
||||
break
|
||||
|
||||
try:
|
||||
c = results[i][1]
|
||||
except:
|
||||
log.traceback(logging.WARNING)
|
||||
c = 0
|
||||
try:
|
||||
text = json.dumps(results[i][0])
|
||||
except:
|
||||
log.traceback(logging.WARNING)
|
||||
text = ''
|
||||
if not _success(c):
|
||||
_handle_unsent_heartbeats([heartbeats[i]], c, text, args, configs)
|
||||
|
||||
leftover = len(heartbeats) - len(results)
|
||||
if leftover > 0:
|
||||
log.warn('Missing {0} results from api.'.format(leftover))
|
||||
start = len(heartbeats) - leftover
|
||||
_handle_unsent_heartbeats(heartbeats[start:], code, content, args, configs)
|
||||
|
||||
|
||||
def _handle_unsent_heartbeats(heartbeats, code, content, args, configs):
|
||||
if args.offline:
|
||||
if code == 400:
|
||||
log.error({
|
||||
@ -195,9 +330,23 @@ def handle_result(h, code, content, args, configs):
|
||||
'response_content': content,
|
||||
})
|
||||
queue = Queue(args, configs)
|
||||
queue.push_many(h)
|
||||
queue.push_many(heartbeats)
|
||||
else:
|
||||
log.error({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
|
||||
|
||||
def _get_results(response):
|
||||
results = []
|
||||
if response is not None:
|
||||
try:
|
||||
results = response.json()['responses']
|
||||
except:
|
||||
log.traceback(logging.WARNING)
|
||||
return results
|
||||
|
||||
|
||||
def _success(code):
|
||||
return code == requests.codes.created or code == requests.codes.accepted
|
||||
|
@ -19,8 +19,8 @@ import time
|
||||
import traceback
|
||||
from .__about__ import __version__
|
||||
from .compat import basestring
|
||||
from .configs import parseConfigFile
|
||||
from .constants import AUTH_ERROR
|
||||
from .configs import getConfigFile, parseConfigFile
|
||||
from .constants import AUTH_ERROR, DEFAULT_SYNC_OFFLINE_ACTIVITY, SUCCESS
|
||||
from .packages import argparse
|
||||
|
||||
|
||||
@ -52,98 +52,214 @@ def parse_arguments():
|
||||
"""
|
||||
|
||||
# define supported command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Common interface for the WakaTime api.')
|
||||
parser = argparse.ArgumentParser(description='Common interface for the ' +
|
||||
'WakaTime api.')
|
||||
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 --entity-type is not file')
|
||||
action=FileAction,
|
||||
help='Absolute path to file for the heartbeat. Can ' +
|
||||
'also be a url, domain or app when ' +
|
||||
'--entity-type is not file.')
|
||||
parser.add_argument('--file', dest='file', action=FileAction,
|
||||
help=argparse.SUPPRESS)
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--key', dest='key', action=StoreWithoutQuotes,
|
||||
help='your wakatime api key; uses api_key from '+
|
||||
'~/.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')
|
||||
metavar='API_KEY',
|
||||
help='Your wakatime api key; uses api_key from ' +
|
||||
'~/.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.')
|
||||
parser.add_argument('--plugin', dest='plugin', action=StoreWithoutQuotes,
|
||||
help='optional text editor plugin name and version '+
|
||||
'for User-Agent header')
|
||||
help='Optional text editor plugin name and version ' +
|
||||
'for User-Agent header.')
|
||||
parser.add_argument('--time', dest='timestamp', metavar='time',
|
||||
type=float, action=StoreWithoutQuotes,
|
||||
help='optional floating-point unix epoch timestamp; '+
|
||||
'uses current time by default')
|
||||
type=float, action=StoreWithoutQuotes,
|
||||
help='Optional floating-point unix epoch timestamp. ' +
|
||||
'Uses current time by default.')
|
||||
parser.add_argument('--lineno', dest='lineno', action=StoreWithoutQuotes,
|
||||
help='optional line number; current line being edited')
|
||||
parser.add_argument('--cursorpos', dest='cursorpos', action=StoreWithoutQuotes,
|
||||
help='optional cursor position in the current file')
|
||||
parser.add_argument('--entity-type', dest='entity_type', action=StoreWithoutQuotes,
|
||||
help='entity type for this heartbeat. can be one of "file", '+
|
||||
'"domain", or "app"; defaults to file.')
|
||||
metavar='INT',
|
||||
help='Optional line number. This is the current ' +
|
||||
'line being edited.')
|
||||
parser.add_argument('--cursorpos', dest='cursorpos',
|
||||
metavar='INT', action=StoreWithoutQuotes,
|
||||
help='Optional cursor position in the current 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", "indexing", ' +
|
||||
'"debugging", "running tests", ' +
|
||||
'"writing tests", "manual testing", ' +
|
||||
'"code reviewing", "browsing", or "designing". ' +
|
||||
'Defaults to "coding".')
|
||||
parser.add_argument('--proxy', dest='proxy', action=StoreWithoutQuotes,
|
||||
help='optional proxy configuration. Supports HTTPS '+
|
||||
'and SOCKS proxies. For example: '+
|
||||
'https://user:pass@host:port or '+
|
||||
'socks5://user:pass@host:port or ' +
|
||||
'domain\\user:pass')
|
||||
help='Optional proxy configuration. Supports HTTPS '+
|
||||
'and SOCKS proxies. For example: '+
|
||||
'https://user:pass@host:port or '+
|
||||
'socks5://user:pass@host:port or ' +
|
||||
'domain\\user:pass')
|
||||
parser.add_argument('--no-ssl-verify', dest='nosslverify',
|
||||
action='store_true',
|
||||
help='disables SSL certificate verification for HTTPS '+
|
||||
'requests. By default, SSL certificates are verified.')
|
||||
help='Disables SSL certificate verification for HTTPS '+
|
||||
'requests. By default, SSL certificates are ' +
|
||||
'verified.')
|
||||
parser.add_argument('--ssl-certs-file', dest='ssl_certs_file',
|
||||
metavar='FILE', action=StoreWithoutQuotes,
|
||||
help='Override the bundled Python Requests CA certs ' +
|
||||
'file. By default, uses certifi for ca certs.')
|
||||
parser.add_argument('--project', dest='project', action=StoreWithoutQuotes,
|
||||
help='optional project name')
|
||||
parser.add_argument('--alternate-project', dest='alternate_project', action=StoreWithoutQuotes,
|
||||
help='optional alternate project name; auto-discovered project '+
|
||||
'takes priority')
|
||||
parser.add_argument('--alternate-language', dest='alternate_language', action=StoreWithoutQuotes,
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--language', dest='language', action=StoreWithoutQuotes,
|
||||
help='optional language name; if valid, takes priority over '+
|
||||
'auto-detected language')
|
||||
parser.add_argument('--hostname', dest='hostname', action=StoreWithoutQuotes, help='hostname of '+
|
||||
'current machine.')
|
||||
parser.add_argument('--disableoffline', dest='offline',
|
||||
action='store_false',
|
||||
help='disables offline time logging instead of queuing logged time')
|
||||
help='Optional project name.')
|
||||
parser.add_argument('--alternate-project', dest='alternate_project',
|
||||
metavar='PROJECT', action=StoreWithoutQuotes,
|
||||
help='Optional alternate project name. ' +
|
||||
'Auto-discovered project takes priority.')
|
||||
parser.add_argument('--alternate-language', dest='alternate_language',
|
||||
action=StoreWithoutQuotes,
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--language', dest='language',
|
||||
action=StoreWithoutQuotes,
|
||||
help='Optional language name. If valid, takes ' +
|
||||
'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',
|
||||
action=StoreWithoutQuotes,
|
||||
help='Hostname of current machine.')
|
||||
parser.add_argument('--disable-offline', dest='offline',
|
||||
action='store_false',
|
||||
help='Disables offline time logging instead of ' +
|
||||
'queuing logged time.')
|
||||
parser.add_argument('--disableoffline', dest='offline_deprecated',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--hide-file-names', dest='hide_file_names',
|
||||
action='store_true',
|
||||
help='Obfuscate filenames. Will not send file names ' +
|
||||
'to api.')
|
||||
parser.add_argument('--hide-filenames', dest='hide_filenames',
|
||||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--hidefilenames', dest='hidefilenames',
|
||||
action='store_true',
|
||||
help='obfuscate file names; will not send file names to api')
|
||||
action='store_true',
|
||||
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('--hide-branch-names', dest='hide_branch_names',
|
||||
action='store_true',
|
||||
help='Obfuscate branch names. Will not send revision ' +
|
||||
'control branch names to api.')
|
||||
parser.add_argument('--exclude', dest='exclude', action='append',
|
||||
help='filename patterns to exclude from logging; POSIX regex '+
|
||||
'syntax; can be used more than once')
|
||||
metavar='PATH',
|
||||
help='Filename patterns to exclude from logging. ' +
|
||||
'POSIX regex syntax. Can be used more than once.')
|
||||
parser.add_argument('--exclude-unknown-project',
|
||||
dest='exclude_unknown_project', action='store_true',
|
||||
help='When set, any activity where the project ' +
|
||||
'cannot be detected will be ignored.')
|
||||
parser.add_argument('--include', dest='include', action='append',
|
||||
help='filename patterns to log; when used in combination with '+
|
||||
'--exclude, files matching include will still be logged; '+
|
||||
'POSIX regex syntax; can be used more than once')
|
||||
metavar='PATH',
|
||||
help='Filename patterns to log. When used in ' +
|
||||
'combination with --exclude, files matching ' +
|
||||
'include will still be logged. POSIX regex ' +
|
||||
'syntax. Can be used more than once.')
|
||||
parser.add_argument('--include-only-with-project-file',
|
||||
dest='include_only_with_project_file',
|
||||
action='store_true',
|
||||
help='Disables tracking folders unless they contain ' +
|
||||
'a .wakatime-project file. Defaults to false.')
|
||||
parser.add_argument('--ignore', dest='ignore', action='append',
|
||||
help=argparse.SUPPRESS)
|
||||
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')
|
||||
action='store_true',
|
||||
help='Reads extra heartbeats from STDIN as a JSON ' +
|
||||
'array until EOF.')
|
||||
parser.add_argument('--log-file', dest='log_file',
|
||||
metavar='FILE', action=StoreWithoutQuotes,
|
||||
help='Defaults to ~/.wakatime.log.')
|
||||
parser.add_argument('--logfile', dest='logfile', action=StoreWithoutQuotes,
|
||||
help='defaults to ~/.wakatime.log')
|
||||
parser.add_argument('--apiurl', dest='api_url', action=StoreWithoutQuotes,
|
||||
help='heartbeats api url; for debugging with a local server')
|
||||
parser.add_argument('--timeout', dest='timeout', type=int, action=StoreWithoutQuotes,
|
||||
help='number of seconds to wait when sending heartbeats to api; '+
|
||||
'defaults to 60 seconds')
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--api-url', dest='api_url', action=StoreWithoutQuotes,
|
||||
metavar='URL',
|
||||
help='Heartbeats api url. For debugging with a ' +
|
||||
'local server.')
|
||||
parser.add_argument('--apiurl', dest='apiurl', action=StoreWithoutQuotes,
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--timeout', dest='timeout', type=int,
|
||||
metavar='SECONDS', action=StoreWithoutQuotes,
|
||||
help='Number of seconds to wait when sending ' +
|
||||
'heartbeats to api. Defaults to 60 seconds.')
|
||||
parser.add_argument('--sync-offline-activity', metavar='AMOUNT',
|
||||
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 100, meaning for every heartbeat sent ' +
|
||||
'while online, 100 offline heartbeats are ' +
|
||||
'synced. Can be used without --entity to only ' +
|
||||
'sync offline activity without generating new ' +
|
||||
'heartbeats.')
|
||||
parser.add_argument('--today', dest='today',
|
||||
action='store_true',
|
||||
help='Prints dashboard time for Today, then exits.')
|
||||
parser.add_argument('--config', dest='config', action=StoreWithoutQuotes,
|
||||
help='defaults to ~/.wakatime.cfg')
|
||||
metavar='FILE', help='Defaults to ~/.wakatime.cfg.')
|
||||
parser.add_argument('--config-section', dest='config_section',
|
||||
metavar='SECTION', action=StoreWithoutQuotes,
|
||||
help='Optional config section when reading or ' +
|
||||
'writing a config key. Defaults to [settings].')
|
||||
parser.add_argument('--config-read', dest='config_read',
|
||||
metavar='KEY', action=StoreWithoutQuotes,
|
||||
help='Prints value for the given config key, then ' +
|
||||
'exits.')
|
||||
parser.add_argument('--config-write', dest='config_write',
|
||||
nargs=2, metavar=('KEY', 'VALUE'),
|
||||
action=StoreWithoutQuotes,
|
||||
help='Writes value to a config key, then exits. ' +
|
||||
'Expects two arguments, key and value.')
|
||||
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__)
|
||||
|
||||
# parse command line arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
# parse ~/.wakatime.cfg file
|
||||
configs = parseConfigFile(args.config)
|
||||
|
||||
if args.config_read:
|
||||
section = args.config_section or 'settings'
|
||||
key = args.config_read
|
||||
print(configs.get(section, key))
|
||||
raise SystemExit(SUCCESS)
|
||||
|
||||
if args.config_write:
|
||||
section = args.config_section or 'settings'
|
||||
key = args.config_write[0]
|
||||
val = args.config_write[1]
|
||||
if not configs.has_section(section):
|
||||
configs.add_section(section)
|
||||
configs.set(section, key, val)
|
||||
with open(args.config or getConfigFile(), 'w', encoding='utf-8') as fh:
|
||||
configs.write(fh)
|
||||
raise SystemExit(SUCCESS)
|
||||
|
||||
# use current unix epoch timestamp by default
|
||||
if not args.timestamp:
|
||||
args.timestamp = time.time()
|
||||
|
||||
# parse ~/.wakatime.cfg file
|
||||
configs = parseConfigFile(args.config)
|
||||
|
||||
# update args from configs
|
||||
if not args.hostname:
|
||||
if configs.has_option('settings', 'hostname'):
|
||||
@ -158,23 +274,34 @@ def parse_arguments():
|
||||
args.key = default_key
|
||||
else:
|
||||
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:
|
||||
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)
|
||||
if not is_valid:
|
||||
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:
|
||||
raise SystemExit(AUTH_ERROR)
|
||||
|
||||
if not args.entity:
|
||||
if args.file:
|
||||
args.entity = args.file
|
||||
else:
|
||||
elif (not args.sync_offline_activity or args.sync_offline_activity == 'none') and not args.today:
|
||||
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:
|
||||
args.language = args.alternate_language
|
||||
|
||||
@ -194,6 +321,8 @@ def parse_arguments():
|
||||
args.exclude.append(pattern)
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
if not args.include_only_with_project_file and configs.has_option('settings', 'include_only_with_project_file'):
|
||||
args.include_only_with_project_file = configs.get('settings', 'include_only_with_project_file') == 'true'
|
||||
if not args.include:
|
||||
args.include = []
|
||||
if configs.has_option('settings', 'include'):
|
||||
@ -203,18 +332,13 @@ def parse_arguments():
|
||||
args.include.append(pattern)
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
if args.hidefilenames:
|
||||
args.hidefilenames = ['.*']
|
||||
else:
|
||||
args.hidefilenames = []
|
||||
if configs.has_option('settings', 'hidefilenames'):
|
||||
option = configs.get('settings', 'hidefilenames')
|
||||
if option.strip().lower() == 'true':
|
||||
args.hidefilenames = ['.*']
|
||||
elif option.strip().lower() != 'false':
|
||||
for pattern in option.split("\n"):
|
||||
if pattern.strip() != '':
|
||||
args.hidefilenames.append(pattern)
|
||||
if not args.exclude_unknown_project and configs.has_option('settings', 'exclude_unknown_project'):
|
||||
args.exclude_unknown_project = configs.getboolean('settings', 'exclude_unknown_project')
|
||||
_boolean_or_list('hide_file_names', args, configs, alternative_names=['hide_filenames', 'hidefilenames'])
|
||||
_boolean_or_list('hide_project_names', args, configs, alternative_names=['hide_projectnames', 'hideprojectnames'])
|
||||
_boolean_or_list('hide_branch_names', args, configs, alternative_names=['hide_branchnames', 'hidebranchnames'], default=None)
|
||||
if args.offline_deprecated:
|
||||
args.offline = False
|
||||
if args.offline and configs.has_option('settings', 'offline'):
|
||||
args.offline = configs.getboolean('settings', 'offline')
|
||||
if not args.proxy and configs.has_option('settings', 'proxy'):
|
||||
@ -231,15 +355,21 @@ def parse_arguments():
|
||||
'domain\\user:pass.')
|
||||
if configs.has_option('settings', 'no_ssl_verify'):
|
||||
args.nosslverify = configs.getboolean('settings', 'no_ssl_verify')
|
||||
if configs.has_option('settings', 'ssl_certs_file'):
|
||||
args.ssl_certs_file = configs.get('settings', 'ssl_certs_file')
|
||||
if not args.verbose and configs.has_option('settings', 'verbose'):
|
||||
args.verbose = configs.getboolean('settings', 'verbose')
|
||||
if not args.verbose and configs.has_option('settings', 'debug'):
|
||||
args.verbose = configs.getboolean('settings', 'debug')
|
||||
if not args.logfile and configs.has_option('settings', 'logfile'):
|
||||
args.logfile = configs.get('settings', 'logfile')
|
||||
if not args.logfile and os.environ.get('WAKATIME_HOME'):
|
||||
if not args.log_file and args.logfile:
|
||||
args.log_file = args.logfile
|
||||
if not args.log_file and configs.has_option('settings', 'log_file'):
|
||||
args.log_file = configs.get('settings', 'log_file')
|
||||
if not args.log_file and os.environ.get('WAKATIME_HOME'):
|
||||
home = os.environ.get('WAKATIME_HOME')
|
||||
args.logfile = os.path.join(os.path.expanduser(home), '.wakatime.log')
|
||||
args.log_file = os.path.join(os.path.expanduser(home), '.wakatime.log')
|
||||
if not args.api_url and args.apiurl:
|
||||
args.api_url = args.apiurl
|
||||
if not args.api_url and configs.has_option('settings', 'api_url'):
|
||||
args.api_url = configs.get('settings', 'api_url')
|
||||
if not args.timeout and configs.has_option('settings', 'timeout'):
|
||||
@ -249,3 +379,34 @@ def parse_arguments():
|
||||
print(traceback.format_exc())
|
||||
|
||||
return args, configs
|
||||
|
||||
|
||||
def _boolean_or_list(config_name, args, configs, alternative_names=[], default=[]):
|
||||
"""Get a boolean or list of regexes from args and configs."""
|
||||
|
||||
# when argument flag present, set to wildcard regex
|
||||
for key in alternative_names + [config_name]:
|
||||
if hasattr(args, key) and getattr(args, key):
|
||||
setattr(args, config_name, ['.*'])
|
||||
return
|
||||
|
||||
setattr(args, config_name, default)
|
||||
|
||||
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':
|
||||
setattr(args, config_name, [])
|
||||
else:
|
||||
for pattern in option.split("\n"):
|
||||
if pattern.strip() != '':
|
||||
if not getattr(args, config_name):
|
||||
setattr(args, config_name, [])
|
||||
getattr(args, config_name).append(pattern)
|
||||
|
@ -11,11 +11,16 @@
|
||||
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
is_py2 = (sys.version_info[0] == 2)
|
||||
is_py26 = (sys.version_info[0] == 2 and sys.version_info[1] == 6)
|
||||
is_py3 = (sys.version_info[0] == 3)
|
||||
is_win = platform.system() == 'Windows'
|
||||
|
||||
|
||||
if is_py2: # pragma: nocover
|
||||
@ -96,5 +101,23 @@ except ImportError: # pragma: nocover
|
||||
|
||||
try:
|
||||
from .packages import simplejson as json
|
||||
except (ImportError, SyntaxError):
|
||||
except (ImportError, SyntaxError): # pragma: nocover
|
||||
import json
|
||||
|
||||
|
||||
class Popen(subprocess.Popen):
|
||||
"""Patched Popen to prevent opening cmd window on Windows platform."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
startupinfo = kwargs.get('startupinfo')
|
||||
if is_win or True:
|
||||
try:
|
||||
startupinfo = startupinfo or subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
except AttributeError:
|
||||
pass
|
||||
kwargs['startupinfo'] = startupinfo
|
||||
if 'env' not in kwargs:
|
||||
kwargs['env'] = os.environ.copy()
|
||||
kwargs['env']['LANG'] = 'en-US' if is_win else 'en_US.UTF-8'
|
||||
subprocess.Popen.__init__(self, *args, **kwargs)
|
||||
|
@ -21,7 +21,7 @@ from .constants import CONFIG_FILE_PARSE_ERROR
|
||||
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
except ImportError: # pragma: nocover
|
||||
from .packages import configparser
|
||||
|
||||
|
||||
|
@ -45,3 +45,12 @@ Files larger than this in bytes will not have a line count stat for performance.
|
||||
Default is 2MB.
|
||||
"""
|
||||
MAX_FILE_SIZE_SUPPORTED = 2000000
|
||||
|
||||
""" Default limit of 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 = 25
|
||||
|
@ -106,8 +106,8 @@ class DependencyParser(object):
|
||||
self.lexer = lexer
|
||||
|
||||
if self.lexer:
|
||||
module_name = self.lexer.__module__.rsplit('.', 1)[-1]
|
||||
class_name = self.lexer.__class__.__name__.replace('Lexer', 'Parser', 1)
|
||||
module_name = self.root_lexer.__module__.rsplit('.', 1)[-1]
|
||||
class_name = self.root_lexer.__class__.__name__.replace('Lexer', 'Parser', 1)
|
||||
else:
|
||||
module_name = 'unknown'
|
||||
class_name = 'UnknownParser'
|
||||
@ -121,9 +121,19 @@ class DependencyParser(object):
|
||||
except ImportError:
|
||||
log.debug('Parsing dependencies not supported for {0}.{1}'.format(module_name, class_name))
|
||||
|
||||
@property
|
||||
def root_lexer(self):
|
||||
if hasattr(self.lexer, 'root_lexer'):
|
||||
return self.lexer.root_lexer
|
||||
return self.lexer
|
||||
|
||||
def parse(self):
|
||||
if self.parser:
|
||||
plugin = self.parser(self.source_file, lexer=self.lexer)
|
||||
dependencies = plugin.parse()
|
||||
return list(set(dependencies))
|
||||
|
||||
def filter_dependencies(dep):
|
||||
return dep and len(dep) <= 200
|
||||
|
||||
return list(filter(filter_dependencies, set(dependencies)))
|
||||
return []
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.languages.c_cpp
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
wakatime.dependencies.c_cpp
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from C++ code.
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.languages.data
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
wakatime.dependencies.data
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from data files.
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.languages.dotnet
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
wakatime.dependencies.dotnet
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from .NET code.
|
||||
|
||||
|
47
packages/wakatime/dependencies/elm.py
Normal file
47
packages/wakatime/dependencies/elm.py
Normal file
@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.dependencies.elm
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from Elm code.
|
||||
|
||||
:copyright: (c) 2018 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from . import TokenParser
|
||||
|
||||
|
||||
class ElmParser(TokenParser):
|
||||
state = None
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
self._process_token(token, content)
|
||||
return self.dependencies
|
||||
|
||||
def _process_token(self, token, content):
|
||||
if self.partial(token) == 'Namespace':
|
||||
self._process_namespace(token, content)
|
||||
elif self.partial(token) == 'Text':
|
||||
self._process_text(token, content)
|
||||
elif self.partial(token) == 'Class':
|
||||
self._process_class(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_namespace(self, token, content):
|
||||
self.state = content.strip()
|
||||
|
||||
def _process_class(self, token, content):
|
||||
if self.state == 'import':
|
||||
self.append(self._format(content))
|
||||
|
||||
def _process_text(self, token, content):
|
||||
pass
|
||||
|
||||
def _process_other(self, token, content):
|
||||
self.state = None
|
||||
|
||||
def _format(self, content):
|
||||
return content.strip().split('.')[0].strip()
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.languages.go
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
wakatime.dependencies.go
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from Go code.
|
||||
|
||||
|
53
packages/wakatime/dependencies/haskell.py
Normal file
53
packages/wakatime/dependencies/haskell.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.dependencies.haskell
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from Haskell code.
|
||||
|
||||
:copyright: (c) 2018 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from . import TokenParser
|
||||
|
||||
|
||||
class HaskellParser(TokenParser):
|
||||
state = None
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
self._process_token(token, content)
|
||||
return self.dependencies
|
||||
|
||||
def _process_token(self, token, content):
|
||||
if self.partial(token) == 'Reserved':
|
||||
self._process_reserved(token, content)
|
||||
elif self.partial(token) == 'Namespace':
|
||||
self._process_namespace(token, content)
|
||||
elif self.partial(token) == 'Keyword':
|
||||
self._process_keyword(token, content)
|
||||
elif self.partial(token) == 'Text':
|
||||
self._process_text(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_reserved(self, token, content):
|
||||
self.state = content.strip()
|
||||
|
||||
def _process_namespace(self, token, content):
|
||||
if self.state == 'import':
|
||||
self.append(self._format(content))
|
||||
|
||||
def _process_keyword(self, token, content):
|
||||
if self.state != 'import' or content.strip() != 'qualified':
|
||||
self.state = None
|
||||
|
||||
def _process_text(self, token, content):
|
||||
pass
|
||||
|
||||
def _process_other(self, token, content):
|
||||
self.state = None
|
||||
|
||||
def _format(self, content):
|
||||
return content.strip().split('.')[0].strip()
|
48
packages/wakatime/dependencies/haxe.py
Normal file
48
packages/wakatime/dependencies/haxe.py
Normal file
@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.dependencies.haxe
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from Haxe code.
|
||||
|
||||
:copyright: (c) 2018 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from . import TokenParser
|
||||
|
||||
|
||||
class HaxeParser(TokenParser):
|
||||
exclude = [
|
||||
r'^haxe$',
|
||||
]
|
||||
state = None
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
self._process_token(token, content)
|
||||
return self.dependencies
|
||||
|
||||
def _process_token(self, token, content):
|
||||
if self.partial(token) == 'Namespace':
|
||||
self._process_namespace(token, content)
|
||||
elif self.partial(token) == 'Text':
|
||||
self._process_text(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_namespace(self, token, content):
|
||||
if self.state == 'import':
|
||||
self.append(self._format(content))
|
||||
self.state = None
|
||||
else:
|
||||
self.state = content
|
||||
|
||||
def _process_text(self, token, content):
|
||||
pass
|
||||
|
||||
def _process_other(self, token, content):
|
||||
self.state = None
|
||||
|
||||
def _format(self, content):
|
||||
return content.strip()
|
@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.languages.templates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
wakatime.dependencies.html
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from Templates.
|
||||
Parse dependencies from HTML.
|
||||
|
||||
:copyright: (c) 2014 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
@ -69,7 +69,7 @@ KEYWORDS = [
|
||||
]
|
||||
|
||||
|
||||
class HtmlDjangoParser(TokenParser):
|
||||
class HtmlParser(TokenParser):
|
||||
tags = []
|
||||
opening_tag = False
|
||||
getting_attrs = False
|
||||
@ -141,63 +141,3 @@ class HtmlDjangoParser(TokenParser):
|
||||
elif content.startswith('"') or content.startswith("'"):
|
||||
if self.current_attr_value is None:
|
||||
self.current_attr_value = content
|
||||
|
||||
|
||||
class VelocityHtmlParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class MyghtyHtmlParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class MasonParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class MakoHtmlParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class CheetahHtmlParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class HtmlGenshiParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class RhtmlParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class HtmlPhpParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class HtmlSmartyParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class EvoqueHtmlParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class ColdfusionHtmlParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class LassoHtmlParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class HandlebarsHtmlParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class YamlJinjaParser(HtmlDjangoParser):
|
||||
pass
|
||||
|
||||
|
||||
class TwigHtmlParser(HtmlDjangoParser):
|
||||
pass
|
60
packages/wakatime/dependencies/javascript.py
Normal file
60
packages/wakatime/dependencies/javascript.py
Normal file
@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.dependencies.javascript
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from JavaScript code.
|
||||
|
||||
:copyright: (c) 2018 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from . import TokenParser
|
||||
|
||||
|
||||
class JavascriptParser(TokenParser):
|
||||
state = None
|
||||
extension = re.compile(r'\.\w{1,4}$')
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
self._process_token(token, content)
|
||||
return self.dependencies
|
||||
|
||||
def _process_token(self, token, content):
|
||||
if self.partial(token) == 'Reserved':
|
||||
self._process_reserved(token, content)
|
||||
elif self.partial(token) == 'Single':
|
||||
self._process_string(token, content)
|
||||
elif self.partial(token) == 'Punctuation':
|
||||
self._process_punctuation(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_reserved(self, token, content):
|
||||
if self.state is None:
|
||||
self.state = content
|
||||
|
||||
def _process_string(self, token, content):
|
||||
if self.state == 'import':
|
||||
self.append(self._format_module(content))
|
||||
self.state = None
|
||||
|
||||
def _process_punctuation(self, token, content):
|
||||
if content == ';':
|
||||
self.state = None
|
||||
|
||||
def _process_other(self, token, content):
|
||||
pass
|
||||
|
||||
def _format_module(self, content):
|
||||
content = content.strip().strip('"').strip("'").strip()
|
||||
content = content.split('/')[-1].split('\\')[-1]
|
||||
content = self.extension.sub('', content, count=1)
|
||||
return content
|
||||
|
||||
|
||||
class TypeScriptParser(JavascriptParser):
|
||||
pass
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.languages.java
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
wakatime.dependencies.java
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from Java code.
|
||||
|
||||
@ -39,11 +39,13 @@ class JavaParser(TokenParser):
|
||||
self._process_attribute(token, content)
|
||||
elif self.partial(token) == 'Operator':
|
||||
self._process_operator(token, content)
|
||||
elif self.partial(token) == 'Punctuation':
|
||||
self._process_operator(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_namespace(self, token, content):
|
||||
if u(content) == u('import'):
|
||||
if u(content).split() and u(content).split()[0] == u('import'):
|
||||
self.state = 'import'
|
||||
|
||||
elif self.state == 'import':
|
||||
@ -94,3 +96,89 @@ class JavaParser(TokenParser):
|
||||
|
||||
def _process_other(self, token, content):
|
||||
pass
|
||||
|
||||
|
||||
class KotlinParser(TokenParser):
|
||||
state = None
|
||||
exclude = [
|
||||
r'^java\.',
|
||||
]
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
self._process_token(token, content)
|
||||
return self.dependencies
|
||||
|
||||
def _process_token(self, token, content):
|
||||
if self.partial(token) == 'Keyword':
|
||||
self._process_keyword(token, content)
|
||||
elif self.partial(token) == 'Text':
|
||||
self._process_text(token, content)
|
||||
elif self.partial(token) == 'Namespace':
|
||||
self._process_namespace(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_keyword(self, token, content):
|
||||
self.state = content
|
||||
|
||||
def _process_text(self, token, content):
|
||||
pass
|
||||
|
||||
def _process_namespace(self, token, content):
|
||||
if self.state == 'import':
|
||||
self.append(self._format(content))
|
||||
self.state = None
|
||||
|
||||
def _process_other(self, token, content):
|
||||
self.state = None
|
||||
|
||||
def _format(self, content):
|
||||
content = content.split(u('.'))
|
||||
|
||||
if content[-1] == u('*'):
|
||||
content = content[:len(content) - 1]
|
||||
|
||||
if len(content) == 0:
|
||||
return None
|
||||
|
||||
if len(content) == 1:
|
||||
return content[0]
|
||||
|
||||
return u('.').join(content[:2])
|
||||
|
||||
|
||||
class ScalaParser(TokenParser):
|
||||
state = None
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
self._process_token(token, content)
|
||||
return self.dependencies
|
||||
|
||||
def _process_token(self, token, content):
|
||||
if self.partial(token) == 'Keyword':
|
||||
self._process_keyword(token, content)
|
||||
elif self.partial(token) == 'Text':
|
||||
self._process_text(token, content)
|
||||
elif self.partial(token) == 'Namespace':
|
||||
self._process_namespace(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_keyword(self, token, content):
|
||||
self.state = content
|
||||
|
||||
def _process_text(self, token, content):
|
||||
pass
|
||||
|
||||
def _process_namespace(self, token, content):
|
||||
if self.state == 'import':
|
||||
self.append(self._format(content))
|
||||
self.state = None
|
||||
|
||||
def _process_other(self, token, content):
|
||||
self.state = None
|
||||
|
||||
def _format(self, content):
|
||||
return content.strip().lstrip('__root__').strip('_').strip('.')
|
||||
|
84
packages/wakatime/dependencies/objective.py
Normal file
84
packages/wakatime/dependencies/objective.py
Normal file
@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.dependencies.objective
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from Objective-C and Swift code.
|
||||
|
||||
:copyright: (c) 2018 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from . import TokenParser
|
||||
|
||||
|
||||
class SwiftParser(TokenParser):
|
||||
state = None
|
||||
exclude = [
|
||||
r'^foundation$',
|
||||
]
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
self._process_token(token, content)
|
||||
return self.dependencies
|
||||
|
||||
def _process_token(self, token, content):
|
||||
if self.partial(token) == 'Declaration':
|
||||
self._process_declaration(token, content)
|
||||
elif self.partial(token) == 'Class':
|
||||
self._process_class(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_declaration(self, token, content):
|
||||
if self.state is None:
|
||||
self.state = content
|
||||
|
||||
def _process_class(self, token, content):
|
||||
if self.state == 'import':
|
||||
self.append(content)
|
||||
self.state = None
|
||||
|
||||
def _process_other(self, token, content):
|
||||
pass
|
||||
|
||||
|
||||
class ObjectiveCParser(TokenParser):
|
||||
state = None
|
||||
extension = re.compile(r'\.[mh]$')
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
self._process_token(token, content)
|
||||
return self.dependencies
|
||||
|
||||
def _process_token(self, token, content):
|
||||
if self.partial(token) == 'Preproc':
|
||||
self._process_preproc(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_preproc(self, token, content):
|
||||
if self.state:
|
||||
self._process_import(token, content)
|
||||
|
||||
self.state = content
|
||||
|
||||
def _process_import(self, token, content):
|
||||
if self.state == '#' and content.startswith('import '):
|
||||
self.append(self._format(content))
|
||||
self.state = None
|
||||
|
||||
def _process_other(self, token, content):
|
||||
pass
|
||||
|
||||
def _format(self, content):
|
||||
content = content.strip().lstrip('import ').strip()
|
||||
content = content.strip('"').strip("'").strip()
|
||||
content = content.strip('<').strip('>').strip()
|
||||
content = content.split('/')[0]
|
||||
content = self.extension.sub('', content, count=1)
|
||||
return content
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.languages.php
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
wakatime.dependencies.php
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from PHP code.
|
||||
|
||||
@ -16,6 +16,10 @@ from ..compat import u
|
||||
class PhpParser(TokenParser):
|
||||
state = None
|
||||
parens = 0
|
||||
exclude = [
|
||||
r'^app$',
|
||||
r'app\.php$',
|
||||
]
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.languages.python
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
wakatime.dependencies.python
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from Python code.
|
||||
|
||||
@ -9,6 +9,58 @@
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
"""
|
||||
Token.Keyword.Namespace import None
|
||||
Token.Text import
|
||||
Token.Name.Namespace os import
|
||||
Token.Operator , import-2
|
||||
Token.Text import-2
|
||||
Token.Name.Namespace sys import-2
|
||||
Token.Text
|
||||
import-2
|
||||
Token.Keyword.Namespace import None
|
||||
Token.Text import
|
||||
Token.Name.Namespace django.forms.monstertruck import
|
||||
Token.Text
|
||||
import-2
|
||||
|
||||
|
||||
|
||||
Token.Keyword.Namespace import None
|
||||
Token.Text import
|
||||
Token.Name.Namespace os import
|
||||
Token.Operator , import-2
|
||||
Token.Text import-2
|
||||
Token.Name.Namespace sys import-2
|
||||
Token.Text
|
||||
import-2
|
||||
Token.Keyword.Namespace import None
|
||||
Token.Text import
|
||||
Token.Name.Namespace django import
|
||||
Token.Name.Namespace . import-2
|
||||
Token.Name.Namespace forms import-2
|
||||
Token.Name.Namespace . import-2
|
||||
Token.Name.Namespace monstertruck import-2
|
||||
Token.Text
|
||||
import-2
|
||||
|
||||
|
||||
|
||||
None Token.Keyword.Namespace import
|
||||
import Token.Text
|
||||
import Token.Name.Namespace django
|
||||
import-2 Token.Name.Namespace .
|
||||
import-2 Token.Name.Namespace forms
|
||||
import-2 Token.Name.Namespace .
|
||||
import-2 Token.Name.Namespace monstertruck
|
||||
import-2 Token.Text
|
||||
|
||||
None Token.Keyword.Namespace from
|
||||
from Token.Text
|
||||
from Token.Name.Namespace app
|
||||
from-2 Token.Text
|
||||
"""
|
||||
|
||||
from . import TokenParser
|
||||
|
||||
|
||||
@ -32,7 +84,7 @@ class PythonParser(TokenParser):
|
||||
if self.partial(token) == 'Namespace':
|
||||
self._process_namespace(token, content)
|
||||
elif self.partial(token) == 'Operator':
|
||||
self._process_operator(token, content)
|
||||
self._process_punctuation(token, content)
|
||||
elif self.partial(token) == 'Punctuation':
|
||||
self._process_punctuation(token, content)
|
||||
elif self.partial(token) == 'Text':
|
||||
@ -43,21 +95,20 @@ class PythonParser(TokenParser):
|
||||
def _process_namespace(self, token, content):
|
||||
if self.state is None:
|
||||
self.state = content
|
||||
elif content == 'as':
|
||||
self.nonpackage = True
|
||||
else:
|
||||
if content == 'as':
|
||||
self.nonpackage = True
|
||||
else:
|
||||
self._process_import(token, content)
|
||||
|
||||
def _process_operator(self, token, content):
|
||||
pass
|
||||
self._process_import(token, content)
|
||||
|
||||
def _process_punctuation(self, token, content):
|
||||
if content == '(':
|
||||
self.parens += 1
|
||||
self.nonpackage = False
|
||||
elif content == ')':
|
||||
self.parens -= 1
|
||||
self.nonpackage = False
|
||||
self.nonpackage = False
|
||||
elif content == ',' and self.state == 'import-2':
|
||||
self.state = 'import'
|
||||
|
||||
def _process_text(self, token, content):
|
||||
if self.state is not None:
|
||||
@ -72,12 +123,12 @@ class PythonParser(TokenParser):
|
||||
if not self.nonpackage:
|
||||
if self.state == 'from':
|
||||
self.append(content, truncate=True, truncate_to=1)
|
||||
self.state = 'from-2'
|
||||
self.state = None
|
||||
elif self.state == 'import':
|
||||
self.append(content, truncate=True, truncate_to=1)
|
||||
self.state = 'import-2'
|
||||
elif self.state == 'import-2':
|
||||
self.append(content, truncate=True, truncate_to=1)
|
||||
pass
|
||||
else:
|
||||
self.state = None
|
||||
self.nonpackage = False
|
||||
|
48
packages/wakatime/dependencies/rust.py
Normal file
48
packages/wakatime/dependencies/rust.py
Normal file
@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.dependencies.rust
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from Rust code.
|
||||
|
||||
:copyright: (c) 2018 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from . import TokenParser
|
||||
|
||||
|
||||
class RustParser(TokenParser):
|
||||
state = None
|
||||
|
||||
def parse(self):
|
||||
for index, token, content in self.tokens:
|
||||
self._process_token(token, content)
|
||||
return self.dependencies
|
||||
|
||||
def _process_token(self, token, content):
|
||||
if self.partial(token) == 'Keyword':
|
||||
self._process_keyword(token, content)
|
||||
elif self.partial(token) == 'Whitespace':
|
||||
self._process_whitespace(token, content)
|
||||
elif self.partial(token) == 'Name':
|
||||
self._process_name(token, content)
|
||||
else:
|
||||
self._process_other(token, content)
|
||||
|
||||
def _process_keyword(self, token, content):
|
||||
if self.state == 'extern' and content == 'crate':
|
||||
self.state = 'extern crate'
|
||||
else:
|
||||
self.state = content
|
||||
|
||||
def _process_whitespace(self, token, content):
|
||||
pass
|
||||
|
||||
def _process_name(self, token, content):
|
||||
if self.state == 'extern crate':
|
||||
self.append(content)
|
||||
self.state = None
|
||||
|
||||
def _process_other(self, token, content):
|
||||
self.state = None
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.languages.unknown
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
wakatime.dependencies.unknown
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Parse dependencies from files of unknown language.
|
||||
|
||||
|
@ -12,3 +12,8 @@
|
||||
|
||||
class NotYetImplemented(Exception):
|
||||
"""This method needs to be implemented."""
|
||||
|
||||
|
||||
class SkipHeartbeat(Exception):
|
||||
"""Raised to prevent the current heartbeat from being sent."""
|
||||
pass
|
||||
|
@ -10,11 +10,13 @@
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
from subprocess import PIPE
|
||||
|
||||
from .compat import u, json
|
||||
from .compat import u, json, is_win, Popen
|
||||
from .exceptions import SkipHeartbeat
|
||||
from .project import get_project_info
|
||||
from .stats import get_file_stats
|
||||
from .utils import get_user_agent, should_exclude, format_file_path
|
||||
from .utils import get_user_agent, should_exclude, format_file_path, find_project_file
|
||||
|
||||
|
||||
log = logging.getLogger('WakaTime')
|
||||
@ -30,6 +32,7 @@ class Heartbeat(object):
|
||||
time = None
|
||||
entity = None
|
||||
type = None
|
||||
category = None
|
||||
is_write = None
|
||||
project = None
|
||||
branch = None
|
||||
@ -40,7 +43,21 @@ class Heartbeat(object):
|
||||
cursorpos = None
|
||||
user_agent = None
|
||||
|
||||
_sensitive_when_hiding_filename = (
|
||||
'dependencies',
|
||||
'lines',
|
||||
'lineno',
|
||||
'cursorpos',
|
||||
)
|
||||
_sensitive_when_hiding_branch = (
|
||||
'branch',
|
||||
)
|
||||
|
||||
def __init__(self, data, args, configs, _clone=None):
|
||||
if not data:
|
||||
self.skip = u('Skipping because heartbeat data is missing.')
|
||||
return
|
||||
|
||||
self.args = args
|
||||
self.configs = configs
|
||||
|
||||
@ -53,6 +70,22 @@ class Heartbeat(object):
|
||||
if self.type not in ['file', 'domain', 'app']:
|
||||
self.type = 'file'
|
||||
|
||||
self.category = data.get('category')
|
||||
allowed_categories = [
|
||||
'coding',
|
||||
'building',
|
||||
'indexing',
|
||||
'debugging',
|
||||
'running tests',
|
||||
'manual testing',
|
||||
'writing tests',
|
||||
'browsing',
|
||||
'code reviewing',
|
||||
'designing',
|
||||
]
|
||||
if self.category not in allowed_categories:
|
||||
self.category = None
|
||||
|
||||
if not _clone:
|
||||
exclude = self._excluded_by_pattern()
|
||||
if exclude:
|
||||
@ -62,20 +95,37 @@ class Heartbeat(object):
|
||||
return
|
||||
if self.type == 'file':
|
||||
self.entity = format_file_path(self.entity)
|
||||
if self.type == 'file' and not os.path.isfile(self.entity):
|
||||
self.skip = u('File does not exist; ignoring this heartbeat.')
|
||||
return
|
||||
self._format_local_file()
|
||||
if not self._file_exists():
|
||||
self.skip = u('File does not exist; ignoring this heartbeat.')
|
||||
return
|
||||
if self._excluded_by_missing_project_file():
|
||||
self.skip = u('Skipping because missing .wakatime-project file in parent path.')
|
||||
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)
|
||||
self.project = project
|
||||
self.branch = branch
|
||||
|
||||
stats = get_file_stats(self.entity,
|
||||
entity_type=self.type,
|
||||
lineno=data.get('lineno'),
|
||||
cursorpos=data.get('cursorpos'),
|
||||
plugin=args.plugin,
|
||||
language=data.get('language'))
|
||||
if self._excluded_by_unknown_project():
|
||||
self.skip = u('Skipping because project unknown.')
|
||||
return
|
||||
|
||||
try:
|
||||
stats = get_file_stats(self.entity,
|
||||
entity_type=self.type,
|
||||
lineno=data.get('lineno'),
|
||||
cursorpos=data.get('cursorpos'),
|
||||
plugin=args.plugin,
|
||||
language=data.get('language'),
|
||||
local_file=args.local_file)
|
||||
except SkipHeartbeat as ex:
|
||||
self.skip = u(ex) or 'Skipping'
|
||||
return
|
||||
|
||||
else:
|
||||
self.project = data.get('project')
|
||||
self.branch = data.get('branch')
|
||||
@ -91,7 +141,6 @@ class Heartbeat(object):
|
||||
data = self.dict()
|
||||
data.update(attrs)
|
||||
heartbeat = Heartbeat(data, self.args, self.configs, _clone=True)
|
||||
heartbeat.skip = self.skip
|
||||
return heartbeat
|
||||
|
||||
def sanitize(self):
|
||||
@ -100,38 +149,24 @@ class Heartbeat(object):
|
||||
Returns a Heartbeat.
|
||||
"""
|
||||
|
||||
if not self.args.hidefilenames:
|
||||
return self
|
||||
|
||||
if self.entity is None:
|
||||
return self
|
||||
|
||||
if self.type != 'file':
|
||||
return self
|
||||
|
||||
for pattern in self.args.hidefilenames:
|
||||
try:
|
||||
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])
|
||||
sanitized['entity'] = u('HIDDEN{0}').format(extension)
|
||||
|
||||
return self.update(sanitized)
|
||||
|
||||
except re.error as ex:
|
||||
log.warning(u('Regex error ({msg}) for include pattern: {pattern}').format(
|
||||
msg=u(ex),
|
||||
pattern=u(pattern),
|
||||
))
|
||||
if self._should_obfuscate_filename():
|
||||
self._sanitize_metadata(keys=self._sensitive_when_hiding_filename)
|
||||
if self._should_obfuscate_branch(default=True):
|
||||
self._sanitize_metadata(keys=self._sensitive_when_hiding_branch)
|
||||
extension = u(os.path.splitext(self.entity)[1])
|
||||
self.entity = u('HIDDEN{0}').format(extension)
|
||||
elif self.should_obfuscate_project():
|
||||
self._sanitize_metadata(keys=self._sensitive_when_hiding_filename)
|
||||
if self._should_obfuscate_branch(default=True):
|
||||
self._sanitize_metadata(keys=self._sensitive_when_hiding_branch)
|
||||
elif self._should_obfuscate_branch():
|
||||
self._sanitize_metadata(keys=self._sensitive_when_hiding_branch)
|
||||
|
||||
return self
|
||||
|
||||
@ -141,30 +176,205 @@ class Heartbeat(object):
|
||||
def dict(self):
|
||||
return {
|
||||
'time': self.time,
|
||||
'entity': self.entity,
|
||||
'entity': self._unicode(self.entity),
|
||||
'type': self.type,
|
||||
'category': self.category,
|
||||
'is_write': self.is_write,
|
||||
'project': self.project,
|
||||
'branch': self.branch,
|
||||
'language': self.language,
|
||||
'dependencies': self.dependencies,
|
||||
'project': self._unicode(self.project),
|
||||
'branch': self._unicode(self.branch),
|
||||
'language': self._unicode(self.language),
|
||||
'dependencies': self._unicode_list(self.dependencies),
|
||||
'lines': self.lines,
|
||||
'lineno': self.lineno,
|
||||
'cursorpos': self.cursorpos,
|
||||
'user_agent': self.user_agent,
|
||||
'user_agent': self._unicode(self.user_agent),
|
||||
}
|
||||
|
||||
def items(self):
|
||||
return self.dict().items()
|
||||
|
||||
def get_id(self):
|
||||
return u('{h.time}-{h.type}-{h.project}-{h.branch}-{h.entity}-{h.is_write}').format(
|
||||
h=self,
|
||||
return u('{time}-{type}-{category}-{project}-{branch}-{entity}-{is_write}').format(
|
||||
time=self.time,
|
||||
type=self.type,
|
||||
category=self.category,
|
||||
project=self._unicode(self.project),
|
||||
branch=self._unicode(self.branch),
|
||||
entity=self._unicode(self.entity),
|
||||
is_write=self.is_write,
|
||||
)
|
||||
|
||||
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 _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_branch(self, default=False):
|
||||
"""Returns True if hide_file_names is true or the entity file path
|
||||
matches one in the list of obfuscated file paths."""
|
||||
|
||||
# when project names or file names are hidden and hide_branch_names is
|
||||
# not set, we default to hiding branch names along with file/project.
|
||||
if default and self.args.hide_branch_names is None:
|
||||
return True
|
||||
|
||||
if not self.branch or not self.args.hide_branch_names:
|
||||
return False
|
||||
|
||||
for pattern in self.args.hide_branch_names:
|
||||
try:
|
||||
compiled = re.compile(pattern, re.IGNORECASE)
|
||||
if compiled.search(self.entity) or compiled.search(self.branch):
|
||||
return True
|
||||
except re.error as ex:
|
||||
log.warning(u('Regex error ({msg}) for hide_branch_names pattern: {pattern}').format(
|
||||
msg=u(ex),
|
||||
pattern=u(pattern),
|
||||
))
|
||||
|
||||
return False
|
||||
|
||||
def _unicode(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return u(value)
|
||||
|
||||
def _unicode_list(self, values):
|
||||
if values is None:
|
||||
return None
|
||||
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 _format_local_file(self):
|
||||
"""When args.local_file empty on Windows, tries to map args.entity to a
|
||||
unc path.
|
||||
|
||||
Updates args.local_file in-place without returning anything.
|
||||
"""
|
||||
|
||||
if self.type != 'file':
|
||||
return
|
||||
|
||||
if not self.entity:
|
||||
return
|
||||
|
||||
if not is_win:
|
||||
return
|
||||
|
||||
if self._file_exists():
|
||||
return
|
||||
|
||||
self.args.local_file = self._to_unc_path(self.entity)
|
||||
|
||||
def _to_unc_path(self, filepath):
|
||||
drive, rest = self._splitdrive(filepath)
|
||||
if not drive:
|
||||
return filepath
|
||||
|
||||
stdout = None
|
||||
try:
|
||||
stdout, stderr = Popen(['net', 'use'], stdout=PIPE, stderr=PIPE).communicate()
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
if stdout:
|
||||
cols = None
|
||||
for line in stdout.strip().splitlines()[1:]:
|
||||
line = u(line)
|
||||
if not line.strip():
|
||||
continue
|
||||
if not cols:
|
||||
cols = self._unc_columns(line)
|
||||
continue
|
||||
start, end = cols.get('local', (0, 0))
|
||||
if not start and not end:
|
||||
break
|
||||
local = line[start:end].strip().split(':')[0].upper()
|
||||
if not local.isalpha():
|
||||
continue
|
||||
if local == drive:
|
||||
start, end = cols.get('remote', (0, 0))
|
||||
if not start and not end:
|
||||
break
|
||||
remote = line[start:end].strip()
|
||||
return remote + rest
|
||||
|
||||
return filepath
|
||||
|
||||
def _unc_columns(self, line):
|
||||
cols = {}
|
||||
current_col = u('')
|
||||
newcol = False
|
||||
start, end = 0, 0
|
||||
for char in line:
|
||||
if char.isalpha():
|
||||
if newcol:
|
||||
cols[current_col.strip().lower()] = (start, end)
|
||||
current_col = u('')
|
||||
start = end
|
||||
newcol = False
|
||||
current_col += u(char)
|
||||
else:
|
||||
newcol = True
|
||||
end += 1
|
||||
if start != end and current_col:
|
||||
cols[current_col.strip().lower()] = (start, -1)
|
||||
return cols
|
||||
|
||||
def _splitdrive(self, filepath):
|
||||
if filepath[1:2] != ':' or not filepath[0].isalpha():
|
||||
return None, filepath
|
||||
return filepath[0].upper(), filepath[2:]
|
||||
|
||||
def _excluded_by_pattern(self):
|
||||
return should_exclude(self.entity, self.args.include, self.args.exclude)
|
||||
|
||||
def _excluded_by_unknown_project(self):
|
||||
if self.project:
|
||||
return False
|
||||
return self.args.exclude_unknown_project
|
||||
|
||||
def _excluded_by_missing_project_file(self):
|
||||
if not self.args.include_only_with_project_file:
|
||||
return False
|
||||
return find_project_file(self.entity) is None
|
||||
|
||||
def _sanitize_metadata(self, keys=[]):
|
||||
for key in keys:
|
||||
setattr(self, key, None)
|
||||
|
||||
def __repr__(self):
|
||||
return self.json()
|
||||
|
||||
|
@ -1,81 +1,81 @@
|
||||
{
|
||||
"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",
|
||||
"haskell": "Haskell",
|
||||
"haxe": "Haxe",
|
||||
"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",
|
||||
"objective-c": "Objective-C",
|
||||
"objective-c++": "Objective-C++",
|
||||
"objective-j": "Objective-J",
|
||||
"ocaml": "OCaml",
|
||||
"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",
|
||||
"typoscript": "TypoScript",
|
||||
"vb.net": "VB.net",
|
||||
"viml": "VimL",
|
||||
"xaml": "XAML",
|
||||
"xml": "XML",
|
||||
"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",
|
||||
"gosu": "Gosu",
|
||||
"groovy": "Groovy",
|
||||
"haml": "Haml",
|
||||
"haskell": "Haskell",
|
||||
"haxe": "Haxe",
|
||||
"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",
|
||||
"objective-c": "Objective-C",
|
||||
"objective-c++": "Objective-C++",
|
||||
"objective-j": "Objective-J",
|
||||
"ocaml": "OCaml",
|
||||
"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",
|
||||
"typoscript": "TypoScript",
|
||||
"vb.net": "VB.net",
|
||||
"viml": "VimL",
|
||||
"xaml": "XAML",
|
||||
"xml": "XML",
|
||||
"yaml": "YAML"
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ def setup_logging(args, version):
|
||||
for handler in logger.handlers:
|
||||
logger.removeHandler(handler)
|
||||
set_log_level(logger, args)
|
||||
logfile = args.logfile
|
||||
logfile = args.log_file
|
||||
if not logfile:
|
||||
logfile = '~/.wakatime.log'
|
||||
handler = logging.FileHandler(os.path.expanduser(logfile))
|
||||
|
@ -14,21 +14,31 @@ from __future__ import print_function
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
pwd = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(pwd))
|
||||
sys.path.insert(0, os.path.join(pwd, 'packages'))
|
||||
|
||||
from .compat import is_py26
|
||||
|
||||
if is_py26:
|
||||
sys.path.insert(0, os.path.join(pwd, 'packages', 'py26'))
|
||||
else:
|
||||
sys.path.insert(0, os.path.join(pwd, 'packages', 'py27'))
|
||||
|
||||
from .__about__ import __version__
|
||||
from .api import send_heartbeats
|
||||
from .api import send_heartbeats, get_time_today
|
||||
from .arguments import parse_arguments
|
||||
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
|
||||
|
||||
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
from .heartbeat import Heartbeat
|
||||
from .offlinequeue import Queue
|
||||
|
||||
@ -37,17 +47,26 @@ def execute(argv=None):
|
||||
if argv:
|
||||
sys.argv = ['wakatime'] + argv
|
||||
|
||||
args, configs = parse_arguments()
|
||||
try:
|
||||
args, configs = parse_arguments()
|
||||
except SystemExit as ex:
|
||||
return ex.code
|
||||
|
||||
setup_logging(args, __version__)
|
||||
|
||||
if args.today:
|
||||
text, retval = get_time_today(args)
|
||||
if text:
|
||||
print(text)
|
||||
return retval
|
||||
|
||||
try:
|
||||
heartbeats = []
|
||||
|
||||
hb = Heartbeat(vars(args), args, configs)
|
||||
if hb:
|
||||
heartbeats.append(hb)
|
||||
else:
|
||||
elif args.entity:
|
||||
log.debug(hb.skip)
|
||||
|
||||
if args.extra_heartbeats:
|
||||
@ -63,12 +82,23 @@ def execute(argv=None):
|
||||
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:
|
||||
queue = Queue(args, configs)
|
||||
offline_heartbeats = queue.pop_many()
|
||||
if len(offline_heartbeats) > 0:
|
||||
for offline_heartbeats in queue.pop_many(args.sync_offline_activity):
|
||||
time.sleep(1)
|
||||
retval = send_heartbeats(offline_heartbeats, args, configs)
|
||||
if retval != SUCCESS:
|
||||
break
|
||||
|
||||
return retval
|
||||
|
||||
|
@ -15,6 +15,7 @@ import os
|
||||
from time import sleep
|
||||
|
||||
from .compat import json
|
||||
from .constants import DEFAULT_SYNC_OFFLINE_ACTIVITY, HEARTBEATS_PER_REQUEST
|
||||
from .heartbeat import Heartbeat
|
||||
|
||||
|
||||
@ -59,9 +60,12 @@ class Queue(object):
|
||||
}
|
||||
c.execute('INSERT INTO {0} VALUES (:id,:heartbeat)'.format(self.table_name), data)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except sqlite3.Error:
|
||||
log.traceback()
|
||||
try:
|
||||
conn.close()
|
||||
except: # pragma: nocover
|
||||
pass
|
||||
|
||||
def pop(self):
|
||||
if not HAS_SQL:
|
||||
@ -104,19 +108,23 @@ class Queue(object):
|
||||
|
||||
def pop_many(self, limit=None):
|
||||
if limit is None:
|
||||
limit = 100
|
||||
limit = DEFAULT_SYNC_OFFLINE_ACTIVITY
|
||||
|
||||
heartbeats = []
|
||||
|
||||
count = 0
|
||||
while limit == 0 or count < limit:
|
||||
while count < limit:
|
||||
heartbeat = self.pop()
|
||||
if not heartbeat:
|
||||
break
|
||||
heartbeats.append(heartbeat)
|
||||
count += 1
|
||||
if count % HEARTBEATS_PER_REQUEST == 0:
|
||||
yield heartbeats
|
||||
heartbeats = []
|
||||
|
||||
return heartbeats
|
||||
if heartbeats:
|
||||
yield heartbeats
|
||||
|
||||
def _get_db_file(self):
|
||||
home = '~'
|
||||
|
@ -1,3 +1,3 @@
|
||||
from .core import where, old_where
|
||||
from .core import where
|
||||
|
||||
__version__ = "2017.07.27.1"
|
||||
__version__ = "2019.11.28"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@ -8,29 +7,9 @@ certifi.py
|
||||
This module returns the installation location of cacert.pem.
|
||||
"""
|
||||
import os
|
||||
import warnings
|
||||
|
||||
|
||||
class DeprecatedBundleWarning(DeprecationWarning):
|
||||
"""
|
||||
The weak security bundle is being deprecated. Please bother your service
|
||||
provider to get them to stop using cross-signed roots.
|
||||
"""
|
||||
|
||||
|
||||
def where():
|
||||
f = os.path.dirname(__file__)
|
||||
|
||||
return os.path.join(f, 'cacert.pem')
|
||||
|
||||
|
||||
def old_where():
|
||||
warnings.warn(
|
||||
"The weak security bundle is being deprecated.",
|
||||
DeprecatedBundleWarning
|
||||
)
|
||||
f = os.path.dirname(__file__)
|
||||
return os.path.join(f, 'weak.pem')
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(where())
|
||||
|
@ -1,414 +0,0 @@
|
||||
# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
|
||||
# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
|
||||
# Label: "Entrust.net Secure Server CA"
|
||||
# Serial: 927650371
|
||||
# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee
|
||||
# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39
|
||||
# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
|
||||
VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
|
||||
ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
|
||||
KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
|
||||
ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
|
||||
MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
|
||||
ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
|
||||
b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
|
||||
bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
|
||||
U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
|
||||
A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
|
||||
I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
|
||||
wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
|
||||
AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
|
||||
oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
|
||||
BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
|
||||
dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
|
||||
MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
|
||||
b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
|
||||
dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
|
||||
MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
|
||||
E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
|
||||
MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
|
||||
hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
|
||||
95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
|
||||
2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority
|
||||
# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority
|
||||
# Label: "ValiCert Class 2 VA"
|
||||
# Serial: 1
|
||||
# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87
|
||||
# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6
|
||||
# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
|
||||
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
|
||||
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
|
||||
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
|
||||
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
|
||||
NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
|
||||
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
|
||||
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
|
||||
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
|
||||
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
|
||||
dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
|
||||
WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
|
||||
v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
|
||||
UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
|
||||
IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
|
||||
W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok
|
||||
# Subject: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok
|
||||
# Label: "NetLock Express (Class C) Root"
|
||||
# Serial: 104
|
||||
# MD5 Fingerprint: 4f:eb:f1:f0:70:c2:80:63:5d:58:9f:da:12:3c:a9:c4
|
||||
# SHA1 Fingerprint: e3:92:51:2f:0a:cf:f5:05:df:f6:de:06:7f:75:37:e1:65:ea:57:4b
|
||||
# SHA256 Fingerprint: 0b:5e:ed:4e:84:64:03:cf:55:e0:65:84:84:40:ed:2a:82:75:8b:f5:b9:aa:1f:25:3d:46:13:cf:a0:80:ff:3f
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx
|
||||
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
|
||||
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD
|
||||
EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X
|
||||
DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw
|
||||
DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u
|
||||
c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr
|
||||
TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN
|
||||
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA
|
||||
OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC
|
||||
2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW
|
||||
RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P
|
||||
AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW
|
||||
ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0
|
||||
YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz
|
||||
b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO
|
||||
ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB
|
||||
IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs
|
||||
b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
|
||||
ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s
|
||||
YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg
|
||||
a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g
|
||||
SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0
|
||||
aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg
|
||||
YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg
|
||||
Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY
|
||||
ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g
|
||||
pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4
|
||||
Fp1hBWeAyNDYpQcCNJgEjTME1A==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok
|
||||
# Subject: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok
|
||||
# Label: "NetLock Business (Class B) Root"
|
||||
# Serial: 105
|
||||
# MD5 Fingerprint: 39:16:aa:b9:6a:41:e1:14:69:df:9e:6c:3b:72:dc:b6
|
||||
# SHA1 Fingerprint: 87:9f:4b:ee:05:df:98:58:3b:e3:60:d6:33:e7:0d:3f:fe:98:71:af
|
||||
# SHA256 Fingerprint: 39:df:7b:68:2b:7b:93:8f:84:71:54:81:cc:de:8d:60:d8:f2:2e:c5:98:87:7d:0a:aa:c1:2b:59:18:2b:03:12
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx
|
||||
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
|
||||
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD
|
||||
EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05
|
||||
OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G
|
||||
A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
|
||||
Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l
|
||||
dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG
|
||||
SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK
|
||||
gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX
|
||||
iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc
|
||||
Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E
|
||||
BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G
|
||||
SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu
|
||||
b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh
|
||||
bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv
|
||||
Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln
|
||||
aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0
|
||||
IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
|
||||
c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph
|
||||
biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo
|
||||
ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP
|
||||
UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj
|
||||
YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo
|
||||
dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA
|
||||
bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06
|
||||
sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa
|
||||
n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS
|
||||
NitjrFgBazMpUIaD8QFI
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority
|
||||
# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority
|
||||
# Label: "RSA Root Certificate 1"
|
||||
# Serial: 1
|
||||
# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72
|
||||
# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb
|
||||
# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
|
||||
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
|
||||
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
|
||||
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
|
||||
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
|
||||
NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
|
||||
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
|
||||
YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
|
||||
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
|
||||
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
|
||||
cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
|
||||
2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
|
||||
JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
|
||||
Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
|
||||
n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
|
||||
PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority
|
||||
# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority
|
||||
# Label: "ValiCert Class 1 VA"
|
||||
# Serial: 1
|
||||
# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb
|
||||
# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e
|
||||
# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
|
||||
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
|
||||
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
|
||||
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
|
||||
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
|
||||
NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
|
||||
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
|
||||
YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
|
||||
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
|
||||
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
|
||||
LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
|
||||
TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
|
||||
TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
|
||||
LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
|
||||
I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
|
||||
nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc.
|
||||
# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc.
|
||||
# Label: "Equifax Secure eBusiness CA 1"
|
||||
# Serial: 4
|
||||
# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d
|
||||
# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41
|
||||
# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
|
||||
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
|
||||
ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
|
||||
MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
|
||||
LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
|
||||
KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
|
||||
RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
|
||||
WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
|
||||
Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
|
||||
AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
|
||||
eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
|
||||
zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
|
||||
WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
|
||||
/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc.
|
||||
# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc.
|
||||
# Label: "Equifax Secure Global eBusiness CA"
|
||||
# Serial: 1
|
||||
# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc
|
||||
# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45
|
||||
# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
|
||||
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
|
||||
ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
|
||||
MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
|
||||
dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
|
||||
c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
|
||||
UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
|
||||
58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
|
||||
o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
|
||||
aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
|
||||
A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
|
||||
Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
|
||||
8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division
|
||||
# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division
|
||||
# Label: "Thawte Premium Server CA"
|
||||
# Serial: 1
|
||||
# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a
|
||||
# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a
|
||||
# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
|
||||
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
|
||||
biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
|
||||
dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
|
||||
MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
|
||||
MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
|
||||
A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
|
||||
b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
|
||||
cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
|
||||
bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
|
||||
VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
|
||||
ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
|
||||
uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
|
||||
9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
|
||||
hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
|
||||
pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division
|
||||
# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division
|
||||
# Label: "Thawte Server CA"
|
||||
# Serial: 1
|
||||
# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d
|
||||
# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c
|
||||
# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
|
||||
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
|
||||
biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
|
||||
MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
|
||||
MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
|
||||
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
|
||||
dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
|
||||
cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
|
||||
DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
|
||||
gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
|
||||
yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
|
||||
L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
|
||||
EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
|
||||
7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
|
||||
QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
|
||||
qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
|
||||
# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
|
||||
# Label: "Verisign Class 3 Public Primary Certification Authority"
|
||||
# Serial: 149843929435818692848040365716851702463
|
||||
# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67
|
||||
# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2
|
||||
# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
|
||||
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
|
||||
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
|
||||
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
|
||||
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
|
||||
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
|
||||
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
|
||||
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
|
||||
CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
|
||||
lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
|
||||
AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
|
||||
# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
|
||||
# Label: "Verisign Class 3 Public Primary Certification Authority"
|
||||
# Serial: 80507572722862485515306429940691309246
|
||||
# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4
|
||||
# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b
|
||||
# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
|
||||
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
|
||||
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
|
||||
MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
|
||||
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
|
||||
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
|
||||
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
|
||||
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
|
||||
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
|
||||
CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
|
||||
2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
|
||||
2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network
|
||||
# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network
|
||||
# Label: "Verisign Class 3 Public Primary Certification Authority - G2"
|
||||
# Serial: 167285380242319648451154478808036881606
|
||||
# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9
|
||||
# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f
|
||||
# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
|
||||
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
|
||||
c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
|
||||
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
|
||||
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
|
||||
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
|
||||
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
|
||||
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
|
||||
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
|
||||
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
|
||||
pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
|
||||
13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
|
||||
AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
|
||||
U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
|
||||
F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
|
||||
oJ2daZH9
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc.
|
||||
# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc.
|
||||
# Label: "GTE CyberTrust Global Root"
|
||||
# Serial: 421
|
||||
# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db
|
||||
# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74
|
||||
# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
|
||||
VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
|
||||
bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
|
||||
b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
|
||||
UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
|
||||
cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
|
||||
b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
|
||||
iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
|
||||
r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
|
||||
04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
|
||||
GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
|
||||
3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
|
||||
lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
|
||||
# Subject: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
|
||||
# Label: "Equifax Secure Certificate Authority"
|
||||
# Serial: 903804111
|
||||
# MD5 Fingerprint: 67:cb:9d:c0:13:24:8a:82:9b:b2:17:1e:d1:1b:ec:d4
|
||||
# SHA1 Fingerprint: d2:32:09:ad:23:d3:14:23:21:74:e4:0d:7f:9d:62:13:97:86:63:3a
|
||||
# SHA256 Fingerprint: 08:29:7a:40:47:db:a2:36:80:c7:31:db:6e:31:76:53:ca:78:48:e1:be:bd:3a:0b:01:79:a7:07:f9:2c:f1:78
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
|
||||
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
|
||||
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
|
||||
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
|
||||
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
|
||||
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
|
||||
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
|
||||
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
|
||||
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
|
||||
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
|
||||
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
|
||||
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
|
||||
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
|
||||
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
|
||||
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
|
||||
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
|
||||
-----END CERTIFICATE-----
|
File diff suppressed because it is too large
Load Diff
0
packages/wakatime/packages/py26/__init__.py
Normal file
0
packages/wakatime/packages/py26/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user