mirror of
https://github.com/wakatime/sublime-wakatime.git
synced 2023-08-10 21:13:02 +03:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
a290e5d86d | |||
d5b922bb10 | |||
ec7b5e3530 | |||
aa3f2e8af6 | |||
f4e53cd682 | |||
aba72b0f1e | |||
5b9d86a57d | |||
fa40874635 | |||
6d4a4cf9eb | |||
f628b8dd11 | |||
f932ee9fc6 | |||
2f14009279 | |||
453d96bf9c | |||
9de153f156 | |||
dcc782338d | |||
9d0dba988a |
1
AUTHORS
1
AUTHORS
@ -13,3 +13,4 @@ Patches and Suggestions
|
||||
|
||||
- Jimmy Selgen Nielsen <jimmy.selgen@gmail.com>
|
||||
- Patrik Kernstock <info@pkern.at>
|
||||
- Krishna Glick <krishnaglick@gmail.com>
|
||||
|
31
HISTORY.rst
31
HISTORY.rst
@ -3,6 +3,37 @@ History
|
||||
-------
|
||||
|
||||
|
||||
7.0.23 (2017-09-14)
|
||||
++++++++++++++++++
|
||||
|
||||
- Add "include" setting to bypass ignored files. #89
|
||||
|
||||
|
||||
7.0.22 (2017-06-08)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v8.0.3.
|
||||
- Improve Matlab language detection.
|
||||
|
||||
|
||||
7.0.21 (2017-05-24)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v8.0.2.
|
||||
- Only treat proxy string as NTLM proxy after unable to connect with HTTPS and
|
||||
SOCKS proxy.
|
||||
- Support running automated tests on Linux, OS X, and Windows.
|
||||
- Ability to disable SSL cert verification. wakatime/wakatime#90
|
||||
- Disable line count stats for files larger than 2MB to improve performance.
|
||||
- Print error saying Python needs upgrading when requests can't be imported.
|
||||
|
||||
|
||||
7.0.20 (2017-04-10)
|
||||
++++++++++++++++++
|
||||
|
||||
- Fix install instructions formatting.
|
||||
|
||||
|
||||
7.0.19 (2017-04-10)
|
||||
++++++++++++++++++
|
||||
|
||||
|
24
README.md
24
README.md
@ -9,17 +9,15 @@ Installation
|
||||
|
||||
1. Install [Package Control](https://packagecontrol.io/installation).
|
||||
|
||||
2. Using [Package Control](https://packagecontrol.io/docs/usage):
|
||||
2. In Sublime, press `ctrl+shift+p`(Windows, Linux) or `cmd+shift+p`(OS X).
|
||||
|
||||
a) Inside Sublime, press `ctrl+shift+p`(Windows, Linux) or `cmd+shift+p`(OS X).
|
||||
3. Type `install`, then press `enter` with `Package Control: Install Package` selected.
|
||||
|
||||
b) Type `install`, then press `enter` with `Package Control: Install Package` selected.
|
||||
4. Type `wakatime`, then press `enter` with the `WakaTime` plugin selected.
|
||||
|
||||
c) Type `wakatime`, then press `enter` with the `WakaTime` plugin selected.
|
||||
5. Enter your [api key](https://wakatime.com/settings#apikey), then press `enter`.
|
||||
|
||||
3. Enter your [api key](https://wakatime.com/settings#apikey), then press `enter`.
|
||||
|
||||
4. Use Sublime and your coding activity will be displayed on your [WakaTime dashboard](https://wakatime.com).
|
||||
6. Use Sublime and your coding activity will be displayed on your [WakaTime dashboard](https://wakatime.com).
|
||||
|
||||
|
||||
Screen Shots
|
||||
@ -35,7 +33,7 @@ In Sublime Text 2, if you get a warning message:
|
||||
|
||||
A plugin (WakaTime) may be making Sublime Text unresponsive by taking too long (0.017332s) in its on_modified callback.
|
||||
|
||||
To fix this, go to `Preferences > Settings - User` then add the following setting:
|
||||
To fix this, go to `Preferences → Settings - User` then add the following setting:
|
||||
|
||||
`"detect_slow_plugins": false`
|
||||
|
||||
@ -49,7 +47,13 @@ First, turn on debug mode in your `WakaTime.sublime-settings` file.
|
||||
|
||||
Add the line: `"debug": true`
|
||||
|
||||
Then, open your Sublime Console with `View -> Show Console` ( CTRL + \` ) to see the plugin executing the wakatime cli process when sending a heartbeat.
|
||||
Then, open your Sublime Console with `View → Show Console` ( CTRL + \` ) to see the plugin executing the wakatime cli process when sending a heartbeat.
|
||||
Also, tail your `$HOME/.wakatime.log` file to debug wakatime cli problems.
|
||||
|
||||
For more general troubleshooting information, see [wakatime/wakatime#troubleshooting](https://github.com/wakatime/wakatime#troubleshooting).
|
||||
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-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
|
||||
|
@ -7,7 +7,7 @@ Website: https://wakatime.com/
|
||||
==========================================================="""
|
||||
|
||||
|
||||
__version__ = '7.0.19'
|
||||
__version__ = '7.0.23'
|
||||
|
||||
|
||||
import sublime
|
||||
@ -462,6 +462,7 @@ class SendHeartbeatsThread(threading.Thread):
|
||||
self.debug = SETTINGS.get('debug')
|
||||
self.api_key = SETTINGS.get('api_key', '')
|
||||
self.ignore = SETTINGS.get('ignore', [])
|
||||
self.include = SETTINGS.get('include', [])
|
||||
self.hidefilenames = SETTINGS.get('hidefilenames')
|
||||
self.proxy = SETTINGS.get('proxy')
|
||||
|
||||
@ -520,6 +521,8 @@ class SendHeartbeatsThread(threading.Thread):
|
||||
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:
|
||||
|
@ -17,6 +17,10 @@
|
||||
// POSIX regular expressions will not be logged.
|
||||
"ignore": ["^/tmp/", "^/etc/", "^/var/(?!www/).*", "COMMIT_EDITMSG$", "PULLREQ_EDITMSG$", "MERGE_MSG$", "TAG_EDITMSG$"],
|
||||
|
||||
// Include files; Files (including absolute paths) that match one of these
|
||||
// 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,
|
||||
|
@ -1,7 +1,7 @@
|
||||
__title__ = 'wakatime'
|
||||
__description__ = 'Common interface to the WakaTime api.'
|
||||
__url__ = 'https://github.com/wakatime/wakatime'
|
||||
__version_info__ = ('8', '0', '0')
|
||||
__version_info__ = ('8', '0', '3')
|
||||
__version__ = '.'.join(__version_info__)
|
||||
__author__ = 'Alan Hamlett'
|
||||
__author_email__ = 'alan@wakatime.com'
|
||||
|
@ -76,6 +76,10 @@ def parseArguments():
|
||||
'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.')
|
||||
parser.add_argument('--project', dest='project',
|
||||
help='optional project name')
|
||||
parser.add_argument('--alternate-project', dest='alternate_project',
|
||||
@ -214,6 +218,8 @@ def parseArguments():
|
||||
'https://user:pass@host:port or ' +
|
||||
'socks5://user:pass@host:port or ' +
|
||||
'domain\\user:pass.')
|
||||
if configs.has_option('settings', 'no_ssl_verify'):
|
||||
args.nosslverify = configs.getboolean('settings', 'no_ssl_verify')
|
||||
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'):
|
||||
|
@ -25,6 +25,22 @@ except ImportError:
|
||||
from .packages import configparser
|
||||
|
||||
|
||||
def getConfigFile():
|
||||
"""Returns the config file location.
|
||||
|
||||
If $WAKATIME_HOME env varialbe is defined, returns
|
||||
$WAKATIME_HOME/.wakatime.cfg, otherwise ~/.wakatime.cfg.
|
||||
"""
|
||||
|
||||
fileName = '.wakatime.cfg'
|
||||
|
||||
home = os.environ.get('WAKATIME_HOME')
|
||||
if home:
|
||||
return os.path.join(os.path.expanduser(home), fileName)
|
||||
|
||||
return os.path.join(os.path.expanduser('~'), fileName)
|
||||
|
||||
|
||||
def parseConfigFile(configFile=None):
|
||||
"""Returns a configparser.SafeConfigParser instance with configs
|
||||
read from the config file. Default location of the config file is
|
||||
@ -32,13 +48,8 @@ def parseConfigFile(configFile=None):
|
||||
"""
|
||||
|
||||
# get config file location from ENV
|
||||
home = os.environ.get('WAKATIME_HOME')
|
||||
if not configFile and home:
|
||||
configFile = os.path.join(os.path.expanduser(home), '.wakatime.cfg')
|
||||
|
||||
# use default config file location
|
||||
if not configFile:
|
||||
configFile = os.path.join(os.path.expanduser('~'), '.wakatime.cfg')
|
||||
configFile = getConfigFile()
|
||||
|
||||
configs = configparser.ConfigParser(delimiters=('='), strict=False)
|
||||
try:
|
||||
|
@ -38,3 +38,15 @@ UNKNOWN_ERROR = 105
|
||||
Exit code used when the JSON input from `--extra-heartbeats` is malformed.
|
||||
"""
|
||||
MALFORMED_HEARTBEAT_ERROR = 106
|
||||
|
||||
""" Connection Error
|
||||
Exit code used when there was proxy or other problem connecting to the WakaTime
|
||||
API servers.
|
||||
"""
|
||||
CONNECTION_ERROR = 107
|
||||
|
||||
""" Max file size supporting line number count stats.
|
||||
Files larger than this in bytes will not have a line count stat for performance.
|
||||
Default is 2MB.
|
||||
"""
|
||||
MAX_FILE_SIZE_SUPPORTED = 2000000
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
|
||||
LANGUAGES = {
|
||||
'typescript': 0.01,
|
||||
'f#': 0.01,
|
||||
'perl': 0.01,
|
||||
'perl6': 0.01,
|
||||
'f#': 0.01,
|
||||
'typescript': 0.01,
|
||||
}
|
||||
|
@ -34,8 +34,19 @@ from .constants import (
|
||||
MALFORMED_HEARTBEAT_ERROR,
|
||||
)
|
||||
from .logger import setup_logging
|
||||
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
try:
|
||||
from .packages import requests
|
||||
except ImportError:
|
||||
log.traceback(logging.ERROR)
|
||||
print(traceback.format_exc())
|
||||
log.error('Please upgrade Python to the latest version.')
|
||||
print('Please upgrade Python to the latest version.')
|
||||
sys.exit(UNKNOWN_ERROR)
|
||||
|
||||
from .offlinequeue import Queue
|
||||
from .packages import requests
|
||||
from .packages.requests.exceptions import RequestException
|
||||
from .project import get_project_info
|
||||
from .session_cache import SessionCache
|
||||
@ -48,13 +59,11 @@ except (ImportError, SyntaxError): # pragma: nocover
|
||||
from .packages import tzlocal
|
||||
|
||||
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||
entity=None, timestamp=None, is_write=None, plugin=None,
|
||||
offline=None, entity_type='file', hidefilenames=None,
|
||||
proxy=None, api_url=None, timeout=None, **kwargs):
|
||||
proxy=None, nosslverify=None, api_url=None, timeout=None,
|
||||
use_ntlm_proxy=False, **kwargs):
|
||||
"""Sends heartbeat as POST request to WakaTime api server.
|
||||
|
||||
Returns `SUCCESS` when heartbeat was sent, otherwise returns an
|
||||
@ -126,9 +135,10 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||
session_cache = SessionCache()
|
||||
session = session_cache.get()
|
||||
|
||||
should_try_ntlm = False
|
||||
proxies = {}
|
||||
if proxy:
|
||||
if '\\' in proxy:
|
||||
if use_ntlm_proxy:
|
||||
from .packages.requests_ntlm import HttpNtlmAuth
|
||||
username = proxy.rsplit(':', 1)
|
||||
password = ''
|
||||
@ -137,37 +147,80 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||
username = username[0]
|
||||
session.auth = HttpNtlmAuth(username, password, session)
|
||||
else:
|
||||
should_try_ntlm = '\\' in proxy
|
||||
proxies['https'] = proxy
|
||||
|
||||
# log time to api
|
||||
# send request to api
|
||||
response = None
|
||||
try:
|
||||
response = session.post(api_url, data=request_body, headers=headers,
|
||||
proxies=proxies, timeout=timeout)
|
||||
proxies=proxies, timeout=timeout,
|
||||
verify=not nosslverify)
|
||||
except RequestException:
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
}
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data['traceback'] = traceback.format_exc()
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.warn(exception_data)
|
||||
if should_try_ntlm:
|
||||
return send_heartbeat(
|
||||
project=project,
|
||||
entity=entity,
|
||||
timestamp=timestamp,
|
||||
branch=branch,
|
||||
hostname=hostname,
|
||||
stats=stats,
|
||||
key=key,
|
||||
is_write=is_write,
|
||||
plugin=plugin,
|
||||
offline=offline,
|
||||
hidefilenames=hidefilenames,
|
||||
entity_type=entity_type,
|
||||
proxy=proxy,
|
||||
api_url=api_url,
|
||||
timeout=timeout,
|
||||
use_ntlm_proxy=True,
|
||||
)
|
||||
else:
|
||||
log.error(exception_data)
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
}
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data['traceback'] = traceback.format_exc()
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.warn(exception_data)
|
||||
else:
|
||||
log.error(exception_data)
|
||||
|
||||
except: # delete cached session when requests raises unknown exception
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
'traceback': traceback.format_exc(),
|
||||
}
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
log.warn(exception_data)
|
||||
session_cache.delete()
|
||||
if should_try_ntlm:
|
||||
return send_heartbeat(
|
||||
project=project,
|
||||
entity=entity,
|
||||
timestamp=timestamp,
|
||||
branch=branch,
|
||||
hostname=hostname,
|
||||
stats=stats,
|
||||
key=key,
|
||||
is_write=is_write,
|
||||
plugin=plugin,
|
||||
offline=offline,
|
||||
hidefilenames=hidefilenames,
|
||||
entity_type=entity_type,
|
||||
proxy=proxy,
|
||||
api_url=api_url,
|
||||
timeout=timeout,
|
||||
use_ntlm_proxy=True,
|
||||
)
|
||||
else:
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
'traceback': traceback.format_exc(),
|
||||
}
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
log.warn(exception_data)
|
||||
session_cache.delete()
|
||||
return API_ERROR
|
||||
|
||||
else:
|
||||
code = response.status_code if response is not None else None
|
||||
@ -178,32 +231,52 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||
})
|
||||
session_cache.save(session)
|
||||
return SUCCESS
|
||||
if offline:
|
||||
if code != 400:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
if code == 401:
|
||||
if should_try_ntlm:
|
||||
return send_heartbeat(
|
||||
project=project,
|
||||
entity=entity,
|
||||
timestamp=timestamp,
|
||||
branch=branch,
|
||||
hostname=hostname,
|
||||
stats=stats,
|
||||
key=key,
|
||||
is_write=is_write,
|
||||
plugin=plugin,
|
||||
offline=offline,
|
||||
hidefilenames=hidefilenames,
|
||||
entity_type=entity_type,
|
||||
proxy=proxy,
|
||||
api_url=api_url,
|
||||
timeout=timeout,
|
||||
use_ntlm_proxy=True,
|
||||
)
|
||||
else:
|
||||
if offline:
|
||||
if code != 400:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
if code == 401:
|
||||
log.error({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
session_cache.delete()
|
||||
return AUTH_ERROR
|
||||
elif log.isEnabledFor(logging.DEBUG):
|
||||
log.warn({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
else:
|
||||
log.error({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
session_cache.delete()
|
||||
return AUTH_ERROR
|
||||
elif log.isEnabledFor(logging.DEBUG):
|
||||
log.warn({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
else:
|
||||
log.error({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
else:
|
||||
log.error({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
session_cache.delete()
|
||||
return API_ERROR
|
||||
|
||||
@ -278,6 +351,7 @@ def process_heartbeat(args, configs, hostname, heartbeat):
|
||||
heartbeat['offline'] = args.offline
|
||||
heartbeat['hidefilenames'] = args.hidefilenames
|
||||
heartbeat['proxy'] = args.proxy
|
||||
heartbeat['nosslverify'] = args.nosslverify
|
||||
heartbeat['api_url'] = args.api_url
|
||||
|
||||
return send_heartbeat(**heartbeat)
|
||||
|
@ -134,9 +134,9 @@ class MatlabLexer(RegexLexer):
|
||||
}
|
||||
|
||||
def analyse_text(text):
|
||||
if re.match('^\s*%', text, re.M): # comment
|
||||
if re.search(r'^\s*%', text, re.M): # comment
|
||||
return 0.2
|
||||
elif re.match('^!\w+', text, re.M): # system cmd
|
||||
elif re.search(r'^!\w+', text, re.M): # system cmd
|
||||
return 0.2
|
||||
|
||||
|
||||
|
@ -15,6 +15,7 @@ import re
|
||||
import sys
|
||||
|
||||
from .compat import u, open
|
||||
from .constants import MAX_FILE_SIZE_SUPPORTED
|
||||
from .dependencies import DependencyParser
|
||||
from .language_priorities import LANGUAGES
|
||||
|
||||
@ -171,10 +172,7 @@ def get_language_from_extension(file_name):
|
||||
if os.path.exists(u('{0}{1}').format(u(filepart), u('.c'))) or os.path.exists(u('{0}{1}').format(u(filepart), u('.C'))):
|
||||
return 'C'
|
||||
|
||||
directory = os.path.dirname(file_name)
|
||||
available_files = os.listdir(directory)
|
||||
available_extensions = list(zip(*map(os.path.splitext, available_files)))[1]
|
||||
available_extensions = [ext.lower() for ext in available_extensions]
|
||||
available_extensions = extensions_in_same_folder(file_name)
|
||||
if '.cpp' in available_extensions:
|
||||
return 'C++'
|
||||
if '.c' in available_extensions:
|
||||
@ -184,6 +182,11 @@ def get_language_from_extension(file_name):
|
||||
|
||||
|
||||
def number_lines_in_file(file_name):
|
||||
try:
|
||||
if os.path.getsize(file_name) > MAX_FILE_SIZE_SUPPORTED:
|
||||
return None
|
||||
except os.error:
|
||||
pass
|
||||
lines = 0
|
||||
try:
|
||||
with open(file_name, 'r', encoding='utf-8') as fh:
|
||||
@ -294,7 +297,7 @@ def custom_pygments_guess_lexer_for_filename(_fn, _text, **options):
|
||||
rv = lexer.analyse_text(_text)
|
||||
if rv == 1.0:
|
||||
return lexer(**options)
|
||||
result.append((rv, customize_priority(lexer)))
|
||||
result.append(customize_lexer_priority(_fn, rv, lexer))
|
||||
|
||||
def type_sort(t):
|
||||
# sort by:
|
||||
@ -302,16 +305,33 @@ def custom_pygments_guess_lexer_for_filename(_fn, _text, **options):
|
||||
# - is primary filename pattern?
|
||||
# - priority
|
||||
# - last resort: class name
|
||||
return (t[0], primary[t[1]], t[1].priority, t[1].__name__)
|
||||
return (t[0], primary[t[2]], t[1], t[2].__name__)
|
||||
result.sort(key=type_sort)
|
||||
|
||||
return result[-1][1](**options)
|
||||
return result[-1][2](**options)
|
||||
|
||||
|
||||
def customize_priority(lexer):
|
||||
"""Return an integer priority for the given lexer object."""
|
||||
def customize_lexer_priority(file_name, accuracy, lexer):
|
||||
"""Customize lexer priority"""
|
||||
|
||||
priority = lexer.priority
|
||||
|
||||
lexer_name = lexer.name.lower().replace('sharp', '#')
|
||||
if lexer_name in LANGUAGES:
|
||||
lexer.priority = LANGUAGES[lexer_name]
|
||||
return lexer
|
||||
priority = LANGUAGES[lexer_name]
|
||||
elif lexer_name == 'matlab':
|
||||
available_extensions = extensions_in_same_folder(file_name)
|
||||
if '.mat' in available_extensions:
|
||||
priority = 0.06
|
||||
|
||||
return (accuracy, priority, lexer)
|
||||
|
||||
|
||||
def extensions_in_same_folder(file_name):
|
||||
"""Returns a list of file extensions from the same folder as file_name."""
|
||||
|
||||
directory = os.path.dirname(file_name)
|
||||
files = os.listdir(directory)
|
||||
extensions = list(zip(*map(os.path.splitext, files)))[1]
|
||||
extensions = set([ext.lower() for ext in extensions])
|
||||
return extensions
|
||||
|
Reference in New Issue
Block a user