skilleter-thingy 0.3.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- skilleter_thingy/__init__.py +0 -0
- skilleter_thingy/addpath.py +107 -0
- skilleter_thingy/console_colours.py +63 -0
- skilleter_thingy/ffind.py +535 -0
- skilleter_thingy/ggit.py +88 -0
- skilleter_thingy/ggrep.py +155 -0
- skilleter_thingy/git_br.py +186 -0
- skilleter_thingy/git_ca.py +147 -0
- skilleter_thingy/git_cleanup.py +297 -0
- skilleter_thingy/git_co.py +227 -0
- skilleter_thingy/git_common.py +68 -0
- skilleter_thingy/git_hold.py +162 -0
- skilleter_thingy/git_parent.py +84 -0
- skilleter_thingy/git_retag.py +67 -0
- skilleter_thingy/git_review.py +1450 -0
- skilleter_thingy/git_update.py +398 -0
- skilleter_thingy/git_wt.py +72 -0
- skilleter_thingy/gitcmp_helper.py +328 -0
- skilleter_thingy/gitprompt.py +293 -0
- skilleter_thingy/linecount.py +154 -0
- skilleter_thingy/multigit.py +915 -0
- skilleter_thingy/py_audit.py +133 -0
- skilleter_thingy/remdir.py +127 -0
- skilleter_thingy/rpylint.py +98 -0
- skilleter_thingy/strreplace.py +82 -0
- skilleter_thingy/test.py +34 -0
- skilleter_thingy/tfm.py +948 -0
- skilleter_thingy/tfparse.py +101 -0
- skilleter_thingy/trimpath.py +82 -0
- skilleter_thingy/venv_create.py +47 -0
- skilleter_thingy/venv_template.py +47 -0
- skilleter_thingy/xchmod.py +124 -0
- skilleter_thingy/yamlcheck.py +89 -0
- skilleter_thingy-0.3.14.dist-info/METADATA +606 -0
- skilleter_thingy-0.3.14.dist-info/RECORD +39 -0
- skilleter_thingy-0.3.14.dist-info/WHEEL +5 -0
- skilleter_thingy-0.3.14.dist-info/entry_points.txt +31 -0
- skilleter_thingy-0.3.14.dist-info/licenses/LICENSE +619 -0
- skilleter_thingy-0.3.14.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
""" Query api.osv.dev to determine whether a specified version of a particular
|
|
4
|
+
Python package is subject to known security vulnerabilities """
|
|
5
|
+
|
|
6
|
+
################################################################################
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import requests
|
|
10
|
+
import subprocess
|
|
11
|
+
import tempfile
|
|
12
|
+
import re
|
|
13
|
+
import glob
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
################################################################################
|
|
17
|
+
|
|
18
|
+
PIP_PACKAGES = ('pip', 'pkg_resources', 'setuptools')
|
|
19
|
+
PIP_OPTIONS = '--no-cache-dir'
|
|
20
|
+
|
|
21
|
+
QUERY_URL = "https://api.osv.dev/v1/query"
|
|
22
|
+
QUERY_HEADERS = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
|
|
23
|
+
|
|
24
|
+
ANSI_NORMAL = '\x1b[0m'
|
|
25
|
+
ANSI_BOLD = '\x1b[1m'
|
|
26
|
+
|
|
27
|
+
################################################################################
|
|
28
|
+
|
|
29
|
+
def audit(package, version):
|
|
30
|
+
""" Main function """
|
|
31
|
+
|
|
32
|
+
# Query api.osv.dev for known vulnerabilties in this version of the package
|
|
33
|
+
|
|
34
|
+
payload = '{"version": "'+version+'", "package": {"name": "'+package+'", "ecosystem": "PyPI"}}'
|
|
35
|
+
result = requests.post(QUERY_URL, data=payload, headers=QUERY_HEADERS)
|
|
36
|
+
|
|
37
|
+
# Parse and report the results
|
|
38
|
+
|
|
39
|
+
details = result.json()
|
|
40
|
+
|
|
41
|
+
print('-' * 80)
|
|
42
|
+
if package in PIP_PACKAGES:
|
|
43
|
+
print(f'Package: {package} {version} (part of Pip)')
|
|
44
|
+
else:
|
|
45
|
+
print(f'Package: {package} {version}')
|
|
46
|
+
|
|
47
|
+
print()
|
|
48
|
+
|
|
49
|
+
if 'vulns' in details:
|
|
50
|
+
print(f'{len(details["vulns"])} known vulnerabilities')
|
|
51
|
+
|
|
52
|
+
for v in details['vulns']:
|
|
53
|
+
print()
|
|
54
|
+
print(f'{ANSI_BOLD}Vulnerability: {v["id"]}{ANSI_NORMAL}')
|
|
55
|
+
|
|
56
|
+
if 'summary' in v:
|
|
57
|
+
print(f'Summary: {v["summary"]}')
|
|
58
|
+
|
|
59
|
+
if 'aliases' in v:
|
|
60
|
+
print('Aliases: %s' % (', '.join(v['aliases'])))
|
|
61
|
+
|
|
62
|
+
if 'details' in v:
|
|
63
|
+
print()
|
|
64
|
+
print(v['details'])
|
|
65
|
+
else:
|
|
66
|
+
print('No known vulnerabilities')
|
|
67
|
+
|
|
68
|
+
################################################################################
|
|
69
|
+
|
|
70
|
+
def main():
|
|
71
|
+
""" Entry point """
|
|
72
|
+
|
|
73
|
+
parser = argparse.ArgumentParser(
|
|
74
|
+
description='Query api.osv.dev to determine whether Python packagers in a requirments.txt file are subject to known security vulnerabilities')
|
|
75
|
+
parser.add_argument('requirements', nargs='*', type=str, action='store',
|
|
76
|
+
help='The requirements file (if not specified, then the script searches for a requirements.txt file)')
|
|
77
|
+
args = parser.parse_args()
|
|
78
|
+
|
|
79
|
+
requirements = args.requirements or glob.glob('**/requirements.txt', recursive=True)
|
|
80
|
+
|
|
81
|
+
if not requirements:
|
|
82
|
+
print('No requirements.txt file(s) found')
|
|
83
|
+
sys.exit(0)
|
|
84
|
+
|
|
85
|
+
# Create a venv for each requirements file, install pip and the packages
|
|
86
|
+
# and prerequisites, get the list of installed package versions
|
|
87
|
+
# and check each one.
|
|
88
|
+
|
|
89
|
+
for requirement in requirements:
|
|
90
|
+
print('=' * 80)
|
|
91
|
+
print(f'{ANSI_BOLD}File: {requirement}{ANSI_NORMAL}')
|
|
92
|
+
print()
|
|
93
|
+
|
|
94
|
+
with tempfile.TemporaryDirectory() as env_dir:
|
|
95
|
+
with tempfile.NamedTemporaryFile() as package_list:
|
|
96
|
+
script = f'python3 -m venv {env_dir}' \
|
|
97
|
+
f' && . {env_dir}/bin/activate' \
|
|
98
|
+
f' && python3 -m pip {PIP_OPTIONS} install --upgrade pip setuptools' \
|
|
99
|
+
f' && python3 -m pip {PIP_OPTIONS} install -r {requirement}' \
|
|
100
|
+
f' && python3 -m pip {PIP_OPTIONS} list | tail -n+3 | tee {package_list.name}' \
|
|
101
|
+
' && deactivate'
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
subprocess.run(script, check=True, shell=True)
|
|
105
|
+
|
|
106
|
+
except subprocess.CalledProcessError as exc:
|
|
107
|
+
print(f'ERROR #{exc.returncode}: {exc.stdout}')
|
|
108
|
+
sys.exit(exc.returncode)
|
|
109
|
+
|
|
110
|
+
with open(package_list.name) as infile:
|
|
111
|
+
for package in infile.readlines():
|
|
112
|
+
package_info = re.split(' +', package.strip())
|
|
113
|
+
|
|
114
|
+
audit(package_info[0], package_info[1])
|
|
115
|
+
|
|
116
|
+
################################################################################
|
|
117
|
+
|
|
118
|
+
def py_audit():
|
|
119
|
+
"""Entry point"""
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
main()
|
|
123
|
+
|
|
124
|
+
except KeyboardInterrupt:
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
except BrokenPipeError:
|
|
128
|
+
sys.exit(2)
|
|
129
|
+
|
|
130
|
+
################################################################################
|
|
131
|
+
|
|
132
|
+
if __name__ == '__main__':
|
|
133
|
+
py_audit()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" remdir - remove empty directories
|
|
5
|
+
|
|
6
|
+
Given the name of a directory tree and, optionally, a list of files to ignore,
|
|
7
|
+
recursively deletes any directory in the tree that is either completely empty
|
|
8
|
+
or contains nothing but files matching the list.
|
|
9
|
+
|
|
10
|
+
For example:
|
|
11
|
+
|
|
12
|
+
remdir /backup --ignore '*.bak' --keep .stfolder
|
|
13
|
+
|
|
14
|
+
would remove any directory within the /backup tree that is empty
|
|
15
|
+
or contains only files that match the '*.bak' wildcard so long as
|
|
16
|
+
it isn't called '.stfolder'.
|
|
17
|
+
|
|
18
|
+
TODO: Using os.walk() means that, if a directory is deleted, it still shows
|
|
19
|
+
up in the list of directories in the parent directory (?)
|
|
20
|
+
"""
|
|
21
|
+
################################################################################
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
import os
|
|
25
|
+
import argparse
|
|
26
|
+
import fnmatch
|
|
27
|
+
import shutil
|
|
28
|
+
import logging
|
|
29
|
+
|
|
30
|
+
from skilleter_modules import colour
|
|
31
|
+
|
|
32
|
+
################################################################################
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
""" Entry point """
|
|
36
|
+
|
|
37
|
+
# Parse the command line
|
|
38
|
+
|
|
39
|
+
parser = argparse.ArgumentParser(description='Remove empty directories')
|
|
40
|
+
parser.add_argument('--dry-run', '-D', action='store_true', help='Dry-run - report what would be done without doing anything')
|
|
41
|
+
parser.add_argument('--debug', action='store_true', help='Output debug information')
|
|
42
|
+
parser.add_argument('--verbose', action='store_true', help='Output verbose information')
|
|
43
|
+
parser.add_argument('--ignore', '-I', action='append', help='Files to ignore when considering whether a directory is empty')
|
|
44
|
+
parser.add_argument('--keep', '-K', action='append', help='Directories that should be kept even if they are empty')
|
|
45
|
+
parser.add_argument('dirs', nargs='+', help='Directories to prune')
|
|
46
|
+
args = parser.parse_args()
|
|
47
|
+
|
|
48
|
+
if args.debug:
|
|
49
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
50
|
+
|
|
51
|
+
if not args.keep:
|
|
52
|
+
args.keep = []
|
|
53
|
+
|
|
54
|
+
# Go through each directory
|
|
55
|
+
|
|
56
|
+
for directory in args.dirs:
|
|
57
|
+
if not os.path.isdir(directory):
|
|
58
|
+
colour.write(f'"{directory}" is not a directory')
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# Walk through the directory tree in bottom-up order
|
|
62
|
+
|
|
63
|
+
for root, dirs, files in os.walk(directory, topdown=False):
|
|
64
|
+
logging.debug('')
|
|
65
|
+
logging.debug('Directory: %s', root)
|
|
66
|
+
logging.debug(' Sub-directories : %s', dirs)
|
|
67
|
+
logging.debug(' Files : %s', files)
|
|
68
|
+
|
|
69
|
+
# Only consider directories with no subdirectories
|
|
70
|
+
|
|
71
|
+
if dirs:
|
|
72
|
+
logging.debug('Ignoring directory "%s" as it has %d subdirectories', root, len(dirs))
|
|
73
|
+
else:
|
|
74
|
+
# Count of files (if any) to preserve in the directory
|
|
75
|
+
|
|
76
|
+
filecount = len(files)
|
|
77
|
+
|
|
78
|
+
# If any file matches an entry in the ignore list (if we have one) then decrement the file count
|
|
79
|
+
|
|
80
|
+
if args.ignore:
|
|
81
|
+
for file in files:
|
|
82
|
+
for ignore in args.ignore:
|
|
83
|
+
if fnmatch.fnmatch(file, ignore):
|
|
84
|
+
filecount -= 1
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
# If no non-matching files then delete the directory unless it is in the keep list
|
|
88
|
+
|
|
89
|
+
if filecount == 0:
|
|
90
|
+
keep_dir = False
|
|
91
|
+
for keep in args.keep:
|
|
92
|
+
if fnmatch.fnmatch(os.path.basename(root), keep):
|
|
93
|
+
keep_dir = True
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
if keep_dir:
|
|
97
|
+
colour.write(f'Keeping empty directory [BLUE:{root}]')
|
|
98
|
+
else:
|
|
99
|
+
logging.debug('Deleting directory: %s', root)
|
|
100
|
+
colour.write(f'Deleting "[BLUE:{root}]"')
|
|
101
|
+
|
|
102
|
+
if not args.dry_run:
|
|
103
|
+
# Delete the directory and contents
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
shutil.rmtree(root)
|
|
107
|
+
except OSError:
|
|
108
|
+
colour.error('Unable to delete "[BLUE:{root}]"')
|
|
109
|
+
else:
|
|
110
|
+
logging.debug('Ignoring directory "%s" as it has %d non-ignorable files', root, filecount)
|
|
111
|
+
|
|
112
|
+
################################################################################
|
|
113
|
+
|
|
114
|
+
def remdir():
|
|
115
|
+
"""Entry point"""
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
main()
|
|
119
|
+
except KeyboardInterrupt:
|
|
120
|
+
sys.exit(1)
|
|
121
|
+
except BrokenPipeError:
|
|
122
|
+
sys.exit(2)
|
|
123
|
+
|
|
124
|
+
################################################################################
|
|
125
|
+
|
|
126
|
+
if __name__ == '__main__':
|
|
127
|
+
remdir()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Run pylint on all the Python source files in the current tree
|
|
5
|
+
|
|
6
|
+
Copyright (C) 2017-18 John Skilleter """
|
|
7
|
+
################################################################################
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import argparse
|
|
12
|
+
import glob
|
|
13
|
+
import subprocess
|
|
14
|
+
|
|
15
|
+
################################################################################
|
|
16
|
+
|
|
17
|
+
PYLINT = 'pylint'
|
|
18
|
+
|
|
19
|
+
################################################################################
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
""" Main code. Exits directly on failure to locate source files, or returns
|
|
23
|
+
the status code from Pylint otherwise. """
|
|
24
|
+
|
|
25
|
+
# Parse the command line
|
|
26
|
+
|
|
27
|
+
parser = argparse.ArgumentParser(description='Run pylint in the current (or specified) directory/ies')
|
|
28
|
+
|
|
29
|
+
parser.add_argument('paths', nargs='*', help='List of files or paths to lint')
|
|
30
|
+
|
|
31
|
+
args = parser.parse_args()
|
|
32
|
+
|
|
33
|
+
if not args.paths:
|
|
34
|
+
args.paths = ['.']
|
|
35
|
+
|
|
36
|
+
sourcefiles = []
|
|
37
|
+
|
|
38
|
+
# Use rgrep to find source files that have a Python 3 hashbang
|
|
39
|
+
|
|
40
|
+
for entry in args.paths:
|
|
41
|
+
if os.path.isdir(entry):
|
|
42
|
+
result = subprocess.run(['rgrep', '-E', '--exclude-dir=.git', '-l', '#![[:space:]]*/usr/bin/(env[[:space:]])?python3'] + args.paths,
|
|
43
|
+
capture_output=True, check=False, text=True)
|
|
44
|
+
|
|
45
|
+
if result.returncode == 1:
|
|
46
|
+
sys.stderr.write('No Python3 source files found\n')
|
|
47
|
+
sys.exit(2)
|
|
48
|
+
|
|
49
|
+
if result.returncode:
|
|
50
|
+
sys.stderr.write(f'ERROR #{result.returncode}: {result.stderr}')
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
sourcefiles += result.stdout.split('\n')
|
|
54
|
+
|
|
55
|
+
elif os.path.isfile(entry):
|
|
56
|
+
sourcefiles.append(entry)
|
|
57
|
+
else:
|
|
58
|
+
files = glob.glob(entry)
|
|
59
|
+
|
|
60
|
+
if not files:
|
|
61
|
+
sys.stderr.write(f'No files found matching "{entry}"')
|
|
62
|
+
sys.exit(2)
|
|
63
|
+
|
|
64
|
+
sourcefiles += files
|
|
65
|
+
|
|
66
|
+
# Run pylint on all the files
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
result = subprocess.run([PYLINT, '--output-format', 'parseable'] + sourcefiles, capture_output=False, check=False, text=True)
|
|
70
|
+
|
|
71
|
+
except FileNotFoundError:
|
|
72
|
+
sys.stderr.write(f'Unable to locate {PYLINT}\n')
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
|
|
75
|
+
if result.returncode >= 64:
|
|
76
|
+
sys.stderr.write(f'Unexpected error: {result.returncode}\n')
|
|
77
|
+
sys.exit(3)
|
|
78
|
+
|
|
79
|
+
# Function return code is the status return from pylint
|
|
80
|
+
|
|
81
|
+
return result.returncode
|
|
82
|
+
|
|
83
|
+
################################################################################
|
|
84
|
+
|
|
85
|
+
def rpylint():
|
|
86
|
+
"""Entry point"""
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
sys.exit(main())
|
|
90
|
+
except KeyboardInterrupt:
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
except BrokenPipeError:
|
|
93
|
+
sys.exit(2)
|
|
94
|
+
|
|
95
|
+
################################################################################
|
|
96
|
+
|
|
97
|
+
if __name__ == '__main__':
|
|
98
|
+
rpylint()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Textual search and replace
|
|
5
|
+
|
|
6
|
+
For those occasions when you want to search and replace strings with
|
|
7
|
+
regexppy characters that upset sed.
|
|
8
|
+
|
|
9
|
+
Copyright (C) 2018 John Skilleter """
|
|
10
|
+
################################################################################
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import argparse
|
|
15
|
+
import tempfile
|
|
16
|
+
|
|
17
|
+
################################################################################
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
""" Main function """
|
|
21
|
+
|
|
22
|
+
parser = argparse.ArgumentParser(description='Textual search and replace')
|
|
23
|
+
parser.add_argument('--inplace', '-i', action='store_true', help='Do an in-place search and replace on the input file')
|
|
24
|
+
parser.add_argument('search', nargs=1, action='store', help='Search text')
|
|
25
|
+
parser.add_argument('replace', nargs=1, action='store', help='Replacment text')
|
|
26
|
+
parser.add_argument('infile', nargs='?', action='store', help='Input file')
|
|
27
|
+
parser.add_argument('outfile', nargs='?', action='store', help='Output file')
|
|
28
|
+
|
|
29
|
+
args = parser.parse_args()
|
|
30
|
+
|
|
31
|
+
# Sanity check
|
|
32
|
+
|
|
33
|
+
if args.inplace and not args.infile or args.outfile:
|
|
34
|
+
print('For in-place operations you must specify and input file and no output file')
|
|
35
|
+
|
|
36
|
+
# Open the input file
|
|
37
|
+
|
|
38
|
+
if args.infile:
|
|
39
|
+
infile = open(args.infile, 'r')
|
|
40
|
+
else:
|
|
41
|
+
infile = sys.stdin
|
|
42
|
+
|
|
43
|
+
# Open the output file, using a temporary file in the same directory as the input file
|
|
44
|
+
# if we are doing in-place operations
|
|
45
|
+
|
|
46
|
+
if args.outfile:
|
|
47
|
+
outfile = open(args.outfile, 'w')
|
|
48
|
+
elif args.inplace:
|
|
49
|
+
outfile = tempfile.NamedTemporaryFile(mode='w', delete=False, dir=os.path.dirname(args.infile))
|
|
50
|
+
else:
|
|
51
|
+
outfile = sys.stdout
|
|
52
|
+
|
|
53
|
+
# Perform the searchy-replacey-ness
|
|
54
|
+
|
|
55
|
+
for data in infile:
|
|
56
|
+
outfile.write(data.replace(args.search[0], args.replace[0]))
|
|
57
|
+
|
|
58
|
+
# If we doing in-place then juggle the temporary and input files
|
|
59
|
+
|
|
60
|
+
if args.inplace:
|
|
61
|
+
mode = os.stat(args.infile).st_mode
|
|
62
|
+
outfile.close()
|
|
63
|
+
infile.close()
|
|
64
|
+
os.rename(outfile.name, args.infile)
|
|
65
|
+
os.chmod(args.infile, mode)
|
|
66
|
+
|
|
67
|
+
################################################################################
|
|
68
|
+
|
|
69
|
+
def strreplace():
|
|
70
|
+
"""Entry point"""
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
main()
|
|
74
|
+
except KeyboardInterrupt:
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
except BrokenPipeError:
|
|
77
|
+
sys.exit(2)
|
|
78
|
+
|
|
79
|
+
################################################################################
|
|
80
|
+
|
|
81
|
+
if __name__ == '__main__':
|
|
82
|
+
strreplace()
|
skilleter_thingy/test.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
print('Importing all Skilleter-thingy modules')
|
|
4
|
+
import addpath
|
|
5
|
+
import console_colours
|
|
6
|
+
import ffind
|
|
7
|
+
import ggit
|
|
8
|
+
import ggrep
|
|
9
|
+
import git_br
|
|
10
|
+
import git_ca
|
|
11
|
+
import git_cleanup
|
|
12
|
+
import git_co
|
|
13
|
+
import git_common
|
|
14
|
+
import git_hold
|
|
15
|
+
import git_parent
|
|
16
|
+
import git_retag
|
|
17
|
+
import git_review
|
|
18
|
+
import git_update
|
|
19
|
+
import git_wt
|
|
20
|
+
import gitcmp_helper
|
|
21
|
+
import gitprompt
|
|
22
|
+
import linecount
|
|
23
|
+
import multigit
|
|
24
|
+
import py_audit
|
|
25
|
+
import remdir
|
|
26
|
+
import rpylint
|
|
27
|
+
import strreplace
|
|
28
|
+
import tfm
|
|
29
|
+
import tfparse
|
|
30
|
+
import trimpath
|
|
31
|
+
import venv_create
|
|
32
|
+
import xchmod
|
|
33
|
+
import yamlcheck
|
|
34
|
+
print('Import successful')
|