bare-script 0.9.0__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.
@@ -0,0 +1,33 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/bare-script-py/blob/main/LICENSE
3
+
4
+ """
5
+ bare-script package
6
+ """
7
+
8
+ from .library import \
9
+ EXPRESSION_FUNCTIONS, \
10
+ SCRIPT_FUNCTIONS
11
+
12
+ from .model import \
13
+ BARE_SCRIPT_TYPES, \
14
+ lint_script, \
15
+ validate_expression, \
16
+ validate_script
17
+
18
+ from .options import \
19
+ fetch_http, \
20
+ fetch_read_only, \
21
+ fetch_read_write, \
22
+ log_print, \
23
+ url_file_relative
24
+
25
+ from .parser import \
26
+ BareScriptParserError, \
27
+ parse_expression, \
28
+ parse_script
29
+
30
+ from .runtime import \
31
+ BareScriptRuntimeError, \
32
+ evaluate_expression, \
33
+ execute_script
@@ -0,0 +1,12 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/bare-script-py/blob/main/LICENSE
3
+
4
+ """
5
+ bare-script top-level script environment
6
+ """
7
+
8
+ from .bare import main
9
+
10
+
11
+ if __name__ == '__main__': # pragma: no cover
12
+ main()
bare_script/bare.py ADDED
@@ -0,0 +1,109 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/bare-script-py/blob/main/LICENSE
3
+
4
+ """
5
+ bare-script command-line interface (CLI) main module
6
+ """
7
+
8
+ import argparse
9
+ from functools import partial
10
+ import sys
11
+ import time
12
+
13
+ from .model import lint_script
14
+ from .options import fetch_read_write, log_print, url_file_relative
15
+ from .parser import parse_script
16
+ from .runtime import execute_script
17
+
18
+
19
+ def main(argv=None):
20
+ """
21
+ BareScript command-line interface (CLI) main entry point
22
+ """
23
+
24
+ # Command line arguments
25
+ parser = argparse.ArgumentParser(prog='bare', description='The BareScript command-line interface')
26
+ parser.add_argument('filename', nargs='*', action=_ScriptAction, help='files to process')
27
+ parser.add_argument('-c', '--code', nargs=1, action=_ScriptAction, help='execute the BareScript code')
28
+ parser.add_argument('-d', '--debug', action='store_true', help='enable debug mode')
29
+ parser.add_argument('-s', '--static', action='store_true', help='perform static analysis')
30
+ parser.add_argument('-v', '--var', nargs=2, action='append', metavar=('VAR', 'EXPR'),
31
+ help='set a global variable to an expression value')
32
+ args = parser.parse_args(args=argv)
33
+
34
+ status_code = 0
35
+ current_file = None
36
+ try:
37
+ # Parse and execute all source files in order
38
+ globals_ = dict(args.var) if args.var is not None else {}
39
+ for file_, source in args.files:
40
+ current_file = file_
41
+
42
+ # Parse the source
43
+ script = parse_script(source if source is not None else fetch_read_write({'url': file_}))
44
+
45
+ # Run the bare-script linter?
46
+ if args.static or args.debug:
47
+ warnings = lint_script(script)
48
+ warning_prefix = f'BareScript: Static analysis "{file_}" ...'
49
+ if not warnings:
50
+ print(f'{warning_prefix} OK')
51
+ else:
52
+ print(f'{warning_prefix} {len(warnings)} warning{"s" if len(warnings) > 1 else ""}:')
53
+ for warning in warnings:
54
+ print(f'BareScript: {warning}')
55
+ if args.static:
56
+ # pylint: disable=broad-exception-raised
57
+ raise Exception('Static analysis failed')
58
+ if args.static:
59
+ continue
60
+
61
+ # Execute the script
62
+ time_begin = time.time()
63
+ result = execute_script(script, {
64
+ 'debug': args.debug or False,
65
+ 'fetchFn': fetch_read_write,
66
+ 'globals': globals_,
67
+ 'logFn': log_print,
68
+ 'systemPrefix': 'https://craigahobbs.github.io/markdown-up/include/',
69
+ 'urlFn': partial(url_file_relative, file_)
70
+ })
71
+ if isinstance(result, (int, float)) and int(result) == result and 0 <= result <= 255:
72
+ status_code = int(result)
73
+ else:
74
+ status_code = 1 if result else 0
75
+
76
+ # Log script execution end with timing
77
+ if args.debug:
78
+ time_end = time.time()
79
+ print(f'BareScript: Script executed in {1000 * (time_end - time_begin):.1f} milliseconds')
80
+
81
+ # Stop on error status code
82
+ if status_code != 0:
83
+ break
84
+
85
+ except Exception as e: # pylint: disable=broad-exception-caught
86
+ if current_file is not None:
87
+ print(f'{current_file}:')
88
+ print(str(e))
89
+ status_code = 1
90
+
91
+ # Return the status code
92
+ sys.exit(status_code)
93
+
94
+
95
+ # Custom argparse action for script file arguments
96
+ class _ScriptAction(argparse.Action):
97
+
98
+ def __call__(self, parser, namespace, values, option_string=None):
99
+ files = getattr(namespace, 'files', [])
100
+ if option_string in ['-c', '--code']:
101
+ code_count = getattr(namespace, 'code_count', 0)
102
+ code_count += 1
103
+ setattr(namespace, 'code_count', code_count)
104
+ for value in values:
105
+ files.append((f'-c {code_count}', value))
106
+ elif option_string is None:
107
+ for value in values:
108
+ files.append((value, None))
109
+ setattr(namespace, 'files', files)
bare_script/baredoc.py ADDED
@@ -0,0 +1,137 @@
1
+ # Licensed under the MIT License
2
+ # https://github.com/craigahobbs/bare-script-py/blob/main/LICENSE
3
+
4
+ """
5
+ bare-script library documentation command-line interface (CLI) main module
6
+ """
7
+
8
+ import json
9
+ import re
10
+ import sys
11
+
12
+
13
+ def main():
14
+ """
15
+ BareScript library documentation command-line interface (CLI) main entry point
16
+ """
17
+
18
+ # Parse each source file line-by-line
19
+ errors = []
20
+ funcs = {}
21
+ func = None
22
+ for filename in sys.argv[1:]:
23
+ with open(filename, 'r', encoding='utf-8') as fh:
24
+ source = fh.read()
25
+ lines = R_SPLIT.split(source)
26
+ for ix_line, line in enumerate(lines):
27
+ # function/group/doc/return documentation keywords?
28
+ match_key = R_KEY.match(line)
29
+ if match_key is not None:
30
+ key = match_key['key']
31
+ text = match_key['text']
32
+ text_trim = text.strip()
33
+
34
+ # Keyword used outside of function?
35
+ if key != 'function' and func is None:
36
+ errors.append(f'{filename}:{ix_line + 1}: {key} keyword outside function')
37
+ continue
38
+
39
+ # Process the keyword
40
+ if key == 'group':
41
+ if text_trim == '':
42
+ errors.append(f'{filename}:{ix_line + 1}: Invalid function group name "{text_trim}"')
43
+ continue
44
+ if 'group' in func:
45
+ errors.append(f'{filename}:{ix_line + 1}: Function "{func["name"]}" group redefinition')
46
+ continue
47
+
48
+ # Set the function group
49
+ func['group'] = text_trim
50
+
51
+ elif key in ('doc', 'return'):
52
+ # Add the documentation line - don't add leading blank lines
53
+ func_doc = func.get('key')
54
+ if func_doc is not None or text_trim != '':
55
+ if func_doc is None:
56
+ func_doc = []
57
+ func[key] = func_doc
58
+ func_doc.append(text)
59
+
60
+ else:
61
+ # key == 'function'
62
+ if text_trim == '':
63
+ errors.append(f'{filename}:{ix_line + 1}: Invalid function name "{text_trim}"')
64
+ continue
65
+ if text_trim in funcs:
66
+ errors.append(f'{filename}:{ix_line + 1}: Function "{text_trim}" redefinition')
67
+ continue
68
+
69
+ # Add the function
70
+ func = {'name': text_trim}
71
+ funcs[text_trim] = func
72
+
73
+ continue
74
+
75
+ # arg keyword?
76
+ match_arg = R_ARG.match(line)
77
+ if match_arg is not None:
78
+ name = match_arg['name']
79
+ text = match_arg['text']
80
+ text_trim = text.strip()
81
+
82
+ # Keyword used outside of function?
83
+ if func is None:
84
+ errors.append(f'{filename}:{ix_line + 1}: Function argument "{name}" outside function')
85
+ continue
86
+
87
+ # Add the function arg documentation line - don't add leading blank lines
88
+ args = func.get('args')
89
+ arg = None
90
+ if args is not None:
91
+ arg = next((arg_find for arg_find in args if arg_find['name'] == name), None)
92
+ if arg is not None or text_trim != '':
93
+ if args is None:
94
+ args = []
95
+ func['args'] = args
96
+ if arg is None:
97
+ arg = {'name': name, 'doc': []}
98
+ args.append(arg)
99
+ arg['doc'].append(text)
100
+
101
+ continue
102
+
103
+ # Unknown documentation comment?
104
+ match_unknown = R_UNKNOWN.match(line)
105
+ if match_unknown is not None:
106
+ unknown = match_unknown.groups['unknown']
107
+ errors.append(f'{filename}:{ix_line + 1}: Invalid documentation comment "{unknown}"')
108
+ continue
109
+
110
+ # Create the library documentation model
111
+ library = {
112
+ 'functions': sorted(funcs.values(), key=lambda func: func['name'])
113
+ }
114
+
115
+ # Validate
116
+ if len(library['functions']) == 0:
117
+ errors.append('error: No library functions')
118
+ for func in library['functions']:
119
+ if 'group' not in func:
120
+ errors.append(f'error: Function "{func.name}" missing group')
121
+ if 'doc' not in func:
122
+ errors.append(f'error: Function "{func.name}" missing documentation')
123
+
124
+ # Errors?
125
+ if len(errors) != 0:
126
+ print('\n'.join(errors))
127
+ sys.exit(len(errors))
128
+
129
+ # Output the library JSON
130
+ print(json.dumps(library))
131
+
132
+
133
+ # Library documentation regular expressions
134
+ R_KEY = re.compile(r'^\s*(?:\/\/|#)\s*\$(?P<key>function|group|doc|return):\s?(?P<text>.*)$')
135
+ R_ARG = re.compile(r'^\s*(?:\/\/|#)\s*\$arg\s+(?P<name>[A-Za-z_][A-Za-z0-9_]*(?:\.\.\.)?):\s?(?P<text>.*)$')
136
+ R_UNKNOWN = re.compile(r'^\s*(?:\/\/|#)\s*\$(?P<unknown>[^:]+):')
137
+ R_SPLIT = re.compile(r'\r?\n')