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.
Files changed (39) hide show
  1. skilleter_thingy/__init__.py +0 -0
  2. skilleter_thingy/addpath.py +107 -0
  3. skilleter_thingy/console_colours.py +63 -0
  4. skilleter_thingy/ffind.py +535 -0
  5. skilleter_thingy/ggit.py +88 -0
  6. skilleter_thingy/ggrep.py +155 -0
  7. skilleter_thingy/git_br.py +186 -0
  8. skilleter_thingy/git_ca.py +147 -0
  9. skilleter_thingy/git_cleanup.py +297 -0
  10. skilleter_thingy/git_co.py +227 -0
  11. skilleter_thingy/git_common.py +68 -0
  12. skilleter_thingy/git_hold.py +162 -0
  13. skilleter_thingy/git_parent.py +84 -0
  14. skilleter_thingy/git_retag.py +67 -0
  15. skilleter_thingy/git_review.py +1450 -0
  16. skilleter_thingy/git_update.py +398 -0
  17. skilleter_thingy/git_wt.py +72 -0
  18. skilleter_thingy/gitcmp_helper.py +328 -0
  19. skilleter_thingy/gitprompt.py +293 -0
  20. skilleter_thingy/linecount.py +154 -0
  21. skilleter_thingy/multigit.py +915 -0
  22. skilleter_thingy/py_audit.py +133 -0
  23. skilleter_thingy/remdir.py +127 -0
  24. skilleter_thingy/rpylint.py +98 -0
  25. skilleter_thingy/strreplace.py +82 -0
  26. skilleter_thingy/test.py +34 -0
  27. skilleter_thingy/tfm.py +948 -0
  28. skilleter_thingy/tfparse.py +101 -0
  29. skilleter_thingy/trimpath.py +82 -0
  30. skilleter_thingy/venv_create.py +47 -0
  31. skilleter_thingy/venv_template.py +47 -0
  32. skilleter_thingy/xchmod.py +124 -0
  33. skilleter_thingy/yamlcheck.py +89 -0
  34. skilleter_thingy-0.3.14.dist-info/METADATA +606 -0
  35. skilleter_thingy-0.3.14.dist-info/RECORD +39 -0
  36. skilleter_thingy-0.3.14.dist-info/WHEEL +5 -0
  37. skilleter_thingy-0.3.14.dist-info/entry_points.txt +31 -0
  38. skilleter_thingy-0.3.14.dist-info/licenses/LICENSE +619 -0
  39. skilleter_thingy-0.3.14.dist-info/top_level.txt +1 -0
@@ -0,0 +1,101 @@
1
+ #! /usr/bin/env python3
2
+
3
+ """
4
+ Read JSON Terraform output and convert back to human-readable text
5
+ This allows multiple errors and warnings to be reported as there's
6
+ no way of doing this directly from Terraform
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import json
12
+ import argparse
13
+ from collections import defaultdict
14
+
15
+ from skilleter_modules import colour
16
+
17
+ ################################################################################
18
+
19
+ def error(msg, status=1):
20
+ """Report an error and quit"""
21
+
22
+ colour.write(f'[RED:ERROR]: {msg}')
23
+ sys.exit(status)
24
+
25
+ ################################################################################
26
+
27
+ def main():
28
+ """Everything"""
29
+
30
+ # Command line is either empty or contains the input file
31
+
32
+ parser = argparse.ArgumentParser(description='Convert Terraform JSON output back into human-readable text')
33
+ parser.add_argument('--abspath', '-a', action='store_true', help='Output absolute file paths')
34
+ parser.add_argument('infile', nargs='*', help='The error file (defaults to standard input if not specified)')
35
+
36
+ args = parser.parse_args()
37
+
38
+ # Open the input file or use stdin and read the JSON
39
+
40
+ jsonfile = open(args.infile[0], 'rt') if args.infile else sys.stdin
41
+
42
+ terraform = json.loads(jsonfile.read())
43
+
44
+ # Collect each of the error/warnings
45
+
46
+ report = defaultdict(list)
47
+
48
+ if 'diagnostics' in terraform:
49
+ for diagnostics in terraform['diagnostics']:
50
+ severity = diagnostics['severity'].title()
51
+
52
+ if 'range' in diagnostics:
53
+ file_path = os.path.abspath(diagnostics['range']['filename']) if args.abspath else diagnostics['range']['filename']
54
+
55
+ category = f'{severity}: {diagnostics["summary"]} - {diagnostics["detail"]}'
56
+
57
+ message = ''
58
+ if 'range' in diagnostics:
59
+ message += f'In [BLUE:{file_path}:{diagnostics["range"]["start"]["line"]}]'
60
+
61
+ if 'address' in diagnostics:
62
+ message += f' in [BLUE:{diagnostics["address"]}]'
63
+
64
+ report[category].append(message)
65
+
66
+ for category in report:
67
+ colour.write()
68
+
69
+ # Fudge emboldening multi-line warnings
70
+
71
+ formatted_category = '[BOLD:' + category.replace('\n', ']\n[BOLD:') + ']'
72
+ colour.write(formatted_category)
73
+
74
+ for entry in sorted(report[category]):
75
+ colour.write(f' {entry}')
76
+
77
+ # Summarise the results
78
+
79
+ error_count = terraform.get('error_count', 0)
80
+ warning_count = terraform.get('warning_count', 0)
81
+
82
+ colour.write()
83
+ colour.write(f'[BOLD:Summary:] [BLUE:{error_count}] [BOLD:errors and] [BLUE:{warning_count}] [BOLD:warnings]')
84
+
85
+ ################################################################################
86
+
87
+ def tfparse():
88
+ """Entry point"""
89
+
90
+ try:
91
+ main()
92
+
93
+ except KeyboardInterrupt:
94
+ sys.exit(1)
95
+ except BrokenPipeError:
96
+ sys.exit(2)
97
+
98
+ ################################################################################
99
+
100
+ if __name__ == '__main__':
101
+ tfparse()
@@ -0,0 +1,82 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Thingy trimpath command
5
+
6
+ Copyright (C) 2017 John Skilleter
7
+
8
+ Given a path, truncate it to less than 25% of the width of the console by
9
+ replacing intermediate directories with '...' """
10
+ ################################################################################
11
+
12
+ import sys
13
+ import os
14
+ import argparse
15
+ import shutil
16
+ import logging
17
+
18
+ from skilleter_modules import path
19
+
20
+ ################################################################################
21
+
22
+ def main():
23
+ """ Trim a path to a specified width """
24
+
25
+ # Set up the command line parser
26
+
27
+ parser = argparse.ArgumentParser(description='Trim a path for display to a specified with by replacing intermediate directory names with "..."')
28
+
29
+ parser.add_argument('--width', '-w', default=None, help='Specify the width to trim to the path to. Default is 25% of the current console width')
30
+ parser.add_argument('path', nargs='?', default=None, help='The path to trim. Default is the current directory')
31
+ parser.add_argument('--debug', action='store_true', help='Enable debug output')
32
+
33
+ args = parser.parse_args()
34
+
35
+ if args.debug:
36
+ logging.basicConfig(level=logging.DEBUG)
37
+
38
+ # Set the width, defaulting to 25% of the console width
39
+
40
+ if args.width:
41
+ try:
42
+ trim_width = int(args.width)
43
+ except ValueError:
44
+ trim_width = -1
45
+
46
+ if trim_width <= 0:
47
+ logging.critical('Invalid width: "%s"', args.width)
48
+ sys.exit(1)
49
+ else:
50
+ console = shutil.get_terminal_size()
51
+ trim_width = console.columns // 4
52
+
53
+ # Set the path, defaulting to the current directory
54
+
55
+ try:
56
+ full_path = args.path or os.getcwd()
57
+ except FileNotFoundError:
58
+ logging.critical('Unable to locate directory')
59
+ sys.exit(1)
60
+
61
+ trimmed = path.trimpath(full_path, trim_width)
62
+
63
+ sys.stdout.write(trimmed)
64
+
65
+ ################################################################################
66
+
67
+ def trimpath():
68
+ """Entry point"""
69
+
70
+ try:
71
+ main()
72
+ except KeyboardInterrupt:
73
+ sys.exit(1)
74
+ except BrokenPipeError:
75
+ sys.exit(2)
76
+ except Exception:
77
+ sys.exit(3)
78
+
79
+ ################################################################################
80
+
81
+ if __name__ == '__main__':
82
+ trimpath()
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import os
5
+ import stat
6
+ import argparse
7
+
8
+ from skilleter_modules import venv_template
9
+
10
+ ################################################################################
11
+
12
+ def main():
13
+ """Create the venv script and make it executable"""
14
+
15
+ parser = argparse.ArgumentParser(description='Create a script to run Python code in a virtual environment')
16
+ parser.add_argument('name', nargs=1, help='Name of the script to create')
17
+
18
+ args = parser.parse_args()
19
+
20
+ script = args.name[0]
21
+
22
+ with open(script, 'wt') as scriptfile:
23
+ scriptfile.write(venv_template.TEMPLATE)
24
+
25
+ statinfo = os.stat(script)
26
+
27
+ os.chmod(script, statinfo.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
28
+
29
+ print(f'Created virtual environment: {script}')
30
+
31
+ ################################################################################
32
+
33
+ def venv_create():
34
+ """Entry point"""
35
+
36
+ try:
37
+ main()
38
+
39
+ except KeyboardInterrupt:
40
+ sys.exit(1)
41
+ except BrokenPipeError:
42
+ sys.exit(2)
43
+
44
+ ################################################################################
45
+
46
+ if __name__ == '__main__':
47
+ venv_create()
@@ -0,0 +1,47 @@
1
+ TEMPLATE = \
2
+ r"""#!/usr/bin/env bash
3
+
4
+ set -e
5
+
6
+ ################################################################################
7
+
8
+ VENV_NAME=$(basename "$0")
9
+ VENV_DIR=__venv__
10
+
11
+ GREEN="\e[42m"
12
+ NORMAL="\e[0m"
13
+
14
+ ################################################################################
15
+
16
+ function box()
17
+ {
18
+ echo -e "${GREEN}################################################################################${NORMAL}"
19
+ echo -e "${GREEN}# $@${NORMAL}"
20
+ echo -e "${GREEN}################################################################################${NORMAL}"
21
+ }
22
+
23
+ ################################################################################
24
+
25
+ box "Creating & activating $VENV_NAME virtual environment"
26
+
27
+ python3 -m venv $VENV_DIR
28
+
29
+ source $VENV_DIR/bin/activate
30
+
31
+ if [[ -f requirements.txt ]]
32
+ then
33
+ box "Installing/Upgrading packages"
34
+
35
+ python3 -m pip install -r requirements.txt
36
+ fi
37
+
38
+ if [[ -f ${VENV_NAME} ]]
39
+ then
40
+ box "Running ${VENV_NAME} script"
41
+
42
+ python3 ./${VENV_NAME}.py "$@"
43
+
44
+ deactivate
45
+ fi
46
+ """
47
+
@@ -0,0 +1,124 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ xchmod - Equivalent to chmod but only modifies files that do not
5
+ have the correct permissions - chmod will just set all to the
6
+ correct permissions regardless of current permissions and update
7
+ the file last-changed date whether or not the file has changed.
8
+
9
+ Initial version has MAJOR RESTRICTIONS: It only allows permissions
10
+ of 'a+rw' and applies them to all files and directories in the specified
11
+ path and must be run with -R/--recursive.
12
+
13
+ TODO: [ ] Support all file modes as per chmod
14
+ TODO: [ ] Support all command line options of chmod
15
+ TODO: [ ] Implement non-recursive mode
16
+ TODO: [ ] Options for files/directories only
17
+
18
+ Copyright (C) 2017 John Skilleter
19
+
20
+ Licence: GPL v3 or later
21
+ """
22
+ ################################################################################
23
+
24
+ import os
25
+ import sys
26
+ import stat
27
+ import argparse
28
+
29
+ ################################################################################
30
+ # Constants
31
+
32
+ # Required mode for files
33
+
34
+ MODE_ALL_RW = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH
35
+
36
+ MODE_ALL_RW_DIR = MODE_ALL_RW | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
37
+
38
+ ################################################################################
39
+
40
+ def set_mode(filepath, mode, debug, verbose):
41
+ """ Given the name of a file, the required mode and verbose
42
+ and debug flags, attempt to set the mode of the file if it does not
43
+ already have the specified mode bits set (it currently only sets
44
+ mode bits - it will not unset them)
45
+
46
+ If verbose or debug are true then the operation is reported
47
+ If debug is true the operation is not performed """
48
+
49
+ # File mode (the bits that can be changed via chmod)
50
+
51
+ filemode = stat.S_IMODE(os.stat(filepath).st_mode)
52
+
53
+ # If the current mode doesn't have all the right bits set
54
+ # then either report it or set them (leaving other bits unchanged)
55
+
56
+ if (filemode & mode) != mode:
57
+
58
+ newfilemode = filemode | mode
59
+
60
+ if verbose or debug:
61
+ print('%s (%03o -> %03o)' % (filepath, filemode, newfilemode))
62
+
63
+ if not debug:
64
+ os.chmod(filepath, newfilemode)
65
+
66
+ ################################################################################
67
+
68
+ def make_public():
69
+ """ Search and make public everything in the paths specified on the command
70
+ line """
71
+
72
+ parser = argparse.ArgumentParser(description='Locate files that are not publically writeable and make them so')
73
+
74
+ parser.add_argument('--debug', action='store_true', help='Output the list of files (if any) that need to be made publically writeable')
75
+ parser.add_argument('--verbose', action='store_true', help='List files as they are updated')
76
+ parser.add_argument('--recursive', '-R', action='store_true', help='Operate recursively')
77
+ parser.add_argument('mode', help='Mode to set')
78
+ parser.add_argument('paths', nargs='+', help='List of directory paths to search')
79
+
80
+ args = parser.parse_args()
81
+
82
+ # We only support a+rw in the first version! Future versions will allow all mode types
83
+ # and command line options supported by the chmod command.
84
+
85
+ if args.mode != 'a+rw':
86
+ sys.stderr.write('Invalid mode "%s" - currently ONLY a+rw is supported\n' % args.mode)
87
+ sys.exit(1)
88
+
89
+ if not args.recursive:
90
+ sys.stderr.write('Invalid command line - recursive option currently MUST be specified\n')
91
+ sys.exit(1)
92
+
93
+ # Make sure that we aren't doing anything reckless
94
+
95
+ for path in args.paths:
96
+ if os.path.abspath(path) == '/':
97
+ sys.stderr.write('You cannot recurse from the root directory\n')
98
+ sys.exit(1)
99
+
100
+ # Process each path and each directory and each file & directory in each directory
101
+
102
+ for path in args.paths:
103
+ for root, dirs, files in os.walk(path):
104
+ for filename in files:
105
+ set_mode(os.path.join(root, filename), MODE_ALL_RW, args.debug, args.verbose)
106
+
107
+ for dirname in dirs:
108
+ set_mode(os.path.join(root, dirname) + '/', MODE_ALL_RW_DIR, args.debug, args.verbose)
109
+
110
+ ################################################################################
111
+
112
+ def xchmod():
113
+ """Entry point"""
114
+ try:
115
+ make_public()
116
+ except KeyboardInterrupt:
117
+ sys.exit(1)
118
+ except BrokenPipeError:
119
+ sys.exit(2)
120
+
121
+ ################################################################################
122
+
123
+ if __name__ == '__main__':
124
+ xchmod()
@@ -0,0 +1,89 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Basic YML validator
5
+
6
+ Copyright (C) 2017, 2018 John Skilleter
7
+ """
8
+ ################################################################################
9
+
10
+ import sys
11
+ import argparse
12
+
13
+ import yaml
14
+
15
+ ################################################################################
16
+
17
+ def yaml_process(name, data):
18
+ """ Recursive yaml nosher """
19
+
20
+ if isinstance(data, dict):
21
+ for item in data:
22
+ yaml_process('%s::%s' % (name, item), data[item])
23
+ else:
24
+ print('%s: %s' % (name, data))
25
+
26
+ ################################################################################
27
+
28
+ def main():
29
+ """ Parse the command line - just takes one option (to dump the file after
30
+ successfully parsing it) and a list of 1 or more files to parse """
31
+
32
+ parser = argparse.ArgumentParser(description='Validate one or more YAML source files')
33
+ parser.add_argument('--dump', action='store_true', help='Dump the YAML data after parsing it')
34
+ parser.add_argument('--block', action='store_true', help='Force block style when dumping the YAML data')
35
+ parser.add_argument('--flow', action='store_true', help='Force flow style when dumping the YAML data')
36
+ parser.add_argument('--hiera', action='store_true', help='Process the file as Puppet Hiera data')
37
+ parser.add_argument('files', nargs='+', help='YAML source file')
38
+ args = parser.parse_args()
39
+
40
+ if args.block and args.flow:
41
+ sys.stderr.write('You cannot specify both block and flow style output')
42
+
43
+ if args.block:
44
+ flow_style = False
45
+ elif args.flow:
46
+ flow_style = True
47
+ else:
48
+ flow_style = None
49
+
50
+ # Try to parse each file, optionally dumping the result back to stdout
51
+ # and catching and reporting exceptions
52
+
53
+ for filename in args.files:
54
+ try:
55
+ for yaml_data in yaml.safe_load_all(open(filename)):
56
+
57
+ if args.dump:
58
+ if len(args.files) > 1:
59
+ print('File: %s' % filename)
60
+
61
+ print(yaml.dump(yaml_data, default_flow_style=flow_style))
62
+
63
+ if args.hiera:
64
+ for data in yaml_data:
65
+ yaml_process(data, yaml_data[data])
66
+
67
+ except yaml.YAMLError as exc:
68
+ sys.stderr.write('Error: %s\n' % exc)
69
+
70
+ except IOError:
71
+ sys.stderr.write('Error reading %s\n' % filename)
72
+ sys.exit(2)
73
+
74
+ ################################################################################
75
+
76
+ def yamlcheck():
77
+ """Entry point"""
78
+
79
+ try:
80
+ main()
81
+ except KeyboardInterrupt:
82
+ sys.exit(1)
83
+ except BrokenPipeError:
84
+ sys.exit(2)
85
+
86
+ ################################################################################
87
+
88
+ if __name__ == '__main__':
89
+ yamlcheck()