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.
- bare_script/__init__.py +33 -0
- bare_script/__main__.py +12 -0
- bare_script/bare.py +109 -0
- bare_script/baredoc.py +137 -0
- bare_script/library.py +1912 -0
- bare_script/model.py +257 -0
- bare_script/options.py +108 -0
- bare_script/parser.py +680 -0
- bare_script/runtime.py +345 -0
- bare_script/value.py +199 -0
- bare_script-0.9.0.dist-info/LICENSE +21 -0
- bare_script-0.9.0.dist-info/METADATA +189 -0
- bare_script-0.9.0.dist-info/RECORD +16 -0
- bare_script-0.9.0.dist-info/WHEEL +5 -0
- bare_script-0.9.0.dist-info/entry_points.txt +3 -0
- bare_script-0.9.0.dist-info/top_level.txt +1 -0
bare_script/__init__.py
ADDED
|
@@ -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
|
bare_script/__main__.py
ADDED
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')
|