bare-script 3.8.2__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 +37 -0
- bare_script/__main__.py +12 -0
- bare_script/bare.py +165 -0
- bare_script/baredoc.py +177 -0
- bare_script/data.py +477 -0
- bare_script/include/__init__.py +0 -0
- bare_script/include/args.bare +340 -0
- bare_script/include/baredoc.bare +313 -0
- bare_script/include/dataLineChart.bare +90 -0
- bare_script/include/dataTable.bare +204 -0
- bare_script/include/diff.bare +148 -0
- bare_script/include/forms.bare +72 -0
- bare_script/include/markdownUp.bare +662 -0
- bare_script/include/pager.bare +301 -0
- bare_script/include/unittest.bare +529 -0
- bare_script/include/unittestMock.bare +456 -0
- bare_script/library.py +2273 -0
- bare_script/model.py +547 -0
- bare_script/options.py +119 -0
- bare_script/parser.py +859 -0
- bare_script/runtime.py +471 -0
- bare_script/value.py +530 -0
- bare_script-3.8.2.dist-info/METADATA +201 -0
- bare_script-3.8.2.dist-info/RECORD +28 -0
- bare_script-3.8.2.dist-info/WHEEL +5 -0
- bare_script-3.8.2.dist-info/entry_points.txt +3 -0
- bare_script-3.8.2.dist-info/licenses/LICENSE +21 -0
- bare_script-3.8.2.dist-info/top_level.txt +1 -0
bare_script/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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 .data import \
|
|
9
|
+
add_calculated_field, \
|
|
10
|
+
aggregate_data, \
|
|
11
|
+
filter_data, \
|
|
12
|
+
join_data, \
|
|
13
|
+
sort_data, \
|
|
14
|
+
top_data, \
|
|
15
|
+
validate_data
|
|
16
|
+
|
|
17
|
+
from .model import \
|
|
18
|
+
lint_script, \
|
|
19
|
+
validate_expression, \
|
|
20
|
+
validate_script
|
|
21
|
+
|
|
22
|
+
from .options import \
|
|
23
|
+
fetch_http, \
|
|
24
|
+
fetch_read_only, \
|
|
25
|
+
fetch_read_write, \
|
|
26
|
+
log_stdout, \
|
|
27
|
+
url_file_relative
|
|
28
|
+
|
|
29
|
+
from .parser import \
|
|
30
|
+
BareScriptParserError, \
|
|
31
|
+
parse_expression, \
|
|
32
|
+
parse_script
|
|
33
|
+
|
|
34
|
+
from .runtime import \
|
|
35
|
+
BareScriptRuntimeError, \
|
|
36
|
+
evaluate_expression, \
|
|
37
|
+
execute_script
|
bare_script/__main__.py
ADDED
bare_script/bare.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
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)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
from functools import partial
|
|
10
|
+
import importlib.resources
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
from .library import SYSTEM_GLOBAL_INCLUDES_NAME
|
|
16
|
+
from .model import lint_script
|
|
17
|
+
from .options import fetch_read_write, log_stdout, url_file_relative
|
|
18
|
+
from .parser import parse_expression, parse_script
|
|
19
|
+
from .runtime import evaluate_expression, execute_script
|
|
20
|
+
from .value import value_boolean
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main(argv=None):
|
|
24
|
+
"""
|
|
25
|
+
BareScript command-line interface (CLI) main entry point
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Command line arguments
|
|
29
|
+
parser = argparse.ArgumentParser(prog='bare', description='The BareScript command-line interface')
|
|
30
|
+
parser.add_argument('file', nargs='*', action=_FileScriptAction, help='files to process')
|
|
31
|
+
parser.add_argument('-c', '--code', action=_InlineScriptAction, help='execute the BareScript code')
|
|
32
|
+
parser.add_argument('-d', '--debug', action='store_true', help='enable debug mode')
|
|
33
|
+
parser.add_argument('-m', '--markdown-up', action='store_true', help='run with MarkdownUp stubs')
|
|
34
|
+
parser.add_argument('-s', '--static', dest='static', action='store_const', const='s', help='perform static analysis')
|
|
35
|
+
parser.add_argument('-x', '--staticx', dest='static', action='store_const', const='x', help='perform static analysis with execution')
|
|
36
|
+
parser.add_argument('-v', '--var', nargs=2, action='append', metavar=('VAR', 'EXPR'), default = [],
|
|
37
|
+
help='set a global variable to an expression value')
|
|
38
|
+
args = parser.parse_args(args=argv)
|
|
39
|
+
if not args.scripts:
|
|
40
|
+
parser.print_help()
|
|
41
|
+
parser.exit()
|
|
42
|
+
|
|
43
|
+
status_code = 0
|
|
44
|
+
inline_count = 0
|
|
45
|
+
try:
|
|
46
|
+
# Evaluate the global variable expression arguments
|
|
47
|
+
globals_ = {}
|
|
48
|
+
for var_name, var_expr in args.var:
|
|
49
|
+
globals_[var_name] = evaluate_expression(parse_expression(var_expr))
|
|
50
|
+
|
|
51
|
+
# Get the scripts to run
|
|
52
|
+
scripts = args.scripts
|
|
53
|
+
ix_user_script = 0
|
|
54
|
+
if args.markdown_up:
|
|
55
|
+
scripts = [('code', 'include <markdownUp.bare>'), *scripts]
|
|
56
|
+
ix_user_script = 1
|
|
57
|
+
|
|
58
|
+
# Add unittest.bare argument globals
|
|
59
|
+
globals_['vUnittestReport'] = True
|
|
60
|
+
if args.static:
|
|
61
|
+
globals_['vUnittestDisabled'] = True
|
|
62
|
+
|
|
63
|
+
# Parse and execute all source files in order
|
|
64
|
+
for ix_script, [script_type, script_value] in enumerate(scripts):
|
|
65
|
+
# Get the script source
|
|
66
|
+
if script_type == 'file':
|
|
67
|
+
script_name = script_value
|
|
68
|
+
script_source = None
|
|
69
|
+
try:
|
|
70
|
+
script_source = fetch_read_write({'url': script_value})
|
|
71
|
+
except:
|
|
72
|
+
pass
|
|
73
|
+
if script_source is None:
|
|
74
|
+
raise ValueError(f'Failed to load "{script_value}"')
|
|
75
|
+
else:
|
|
76
|
+
inline_count += 1
|
|
77
|
+
inline_display = inline_count - ix_user_script
|
|
78
|
+
script_name = f'<string{inline_display if inline_display > 1 else ""}>'
|
|
79
|
+
script_source = script_value
|
|
80
|
+
|
|
81
|
+
# Parse the script source
|
|
82
|
+
script = parse_script(script_source, 1, script_name)
|
|
83
|
+
|
|
84
|
+
# Execute?
|
|
85
|
+
static_globals = None
|
|
86
|
+
if args.static != 's':
|
|
87
|
+
# Set the globals to use for static analysis (below)
|
|
88
|
+
if not args.static or ix_script < ix_user_script:
|
|
89
|
+
static_globals = globals_
|
|
90
|
+
else:
|
|
91
|
+
# Copy global to keep each script as isolated as possible
|
|
92
|
+
static_globals = dict(globals_)
|
|
93
|
+
global_includes = static_globals.get(SYSTEM_GLOBAL_INCLUDES_NAME)
|
|
94
|
+
if global_includes is not None and isinstance(global_includes, dict):
|
|
95
|
+
static_globals[SYSTEM_GLOBAL_INCLUDES_NAME] = dict(global_includes)
|
|
96
|
+
|
|
97
|
+
# Execute the script
|
|
98
|
+
time_begin = time.time()
|
|
99
|
+
result = execute_script(script, {
|
|
100
|
+
'debug': args.debug or False,
|
|
101
|
+
'fetchFn': _fetch_include,
|
|
102
|
+
'globals': static_globals,
|
|
103
|
+
'logFn': log_stdout,
|
|
104
|
+
'systemPrefix': _FETCH_INCLUDE_PREFIX,
|
|
105
|
+
'urlFn': partial(url_file_relative, script_value) if script_type == 'file' else None
|
|
106
|
+
})
|
|
107
|
+
if isinstance(result, (int, float)) and int(result) == result and 0 <= result <= 255:
|
|
108
|
+
status_code = int(result) or status_code
|
|
109
|
+
else:
|
|
110
|
+
status_code = (1 if value_boolean(result) else 0) or status_code
|
|
111
|
+
|
|
112
|
+
# Log script execution end with timing
|
|
113
|
+
if args.debug and ix_script >= ix_user_script:
|
|
114
|
+
time_end = time.time()
|
|
115
|
+
print(f'BareScript executed in {1000 * (time_end - time_begin):.1f} milliseconds')
|
|
116
|
+
|
|
117
|
+
# Run the bare-script linter?
|
|
118
|
+
if args.static and ix_script >= ix_user_script:
|
|
119
|
+
warnings = lint_script(script, static_globals)
|
|
120
|
+
if not warnings:
|
|
121
|
+
print(f'BareScript static analysis "{script_name}" ... OK')
|
|
122
|
+
else:
|
|
123
|
+
print(f'BareScript static analysis "{script_name}" ... {len(warnings)} warning{"s" if len(warnings) > 1 else ""}:')
|
|
124
|
+
for warning in warnings:
|
|
125
|
+
print(warning)
|
|
126
|
+
status_code = 1
|
|
127
|
+
|
|
128
|
+
# Stop on error status code
|
|
129
|
+
if status_code != 0 and not args.static:
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
except Exception as exc:
|
|
133
|
+
print(str(exc).strip())
|
|
134
|
+
status_code = 1
|
|
135
|
+
|
|
136
|
+
# Return the status code
|
|
137
|
+
sys.exit(status_code)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _fetch_include(request):
|
|
141
|
+
# Is this a bare system include?
|
|
142
|
+
url = request['url']
|
|
143
|
+
if url.startswith(_FETCH_INCLUDE_PREFIX):
|
|
144
|
+
path = url[len(_FETCH_INCLUDE_PREFIX):]
|
|
145
|
+
with importlib.resources.files('bare_script.include').joinpath(path).open('rb') as cm_inc:
|
|
146
|
+
return cm_inc.read().decode(encoding='utf-8')
|
|
147
|
+
|
|
148
|
+
return fetch_read_write(request)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
_FETCH_INCLUDE_PREFIX = f':bare-include:{os.sep}'
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class _InlineScriptAction(argparse.Action):
|
|
155
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
156
|
+
if 'scripts' not in namespace:
|
|
157
|
+
setattr(namespace, 'scripts', [])
|
|
158
|
+
namespace.scripts.append(('code', values))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class _FileScriptAction(argparse.Action):
|
|
162
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
163
|
+
if 'scripts' not in namespace:
|
|
164
|
+
setattr(namespace, 'scripts', [])
|
|
165
|
+
namespace.scripts.extend(('file', value) for value in values)
|
bare_script/baredoc.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Licensed under the MIT License
|
|
2
|
+
# https://github.com/craigahobbs/bare-script-py/blob/main/LICENSE
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
bare-script documentation tool
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv=None):
|
|
15
|
+
"""
|
|
16
|
+
BareScript documentation tool main entry point
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Command line arguments
|
|
20
|
+
parser = argparse.ArgumentParser(prog='baredoc', description='The BareScript documentation tool')
|
|
21
|
+
parser.add_argument('files', metavar='file', nargs='+', help='files to process')
|
|
22
|
+
parser.add_argument('-o', dest='output', metavar='file', default='-', help='write output to file (default is "-")')
|
|
23
|
+
args = parser.parse_args(args=argv)
|
|
24
|
+
|
|
25
|
+
# Parse each source file line-by-line
|
|
26
|
+
errors = []
|
|
27
|
+
funcs = {}
|
|
28
|
+
func = None
|
|
29
|
+
for file_ in args.files:
|
|
30
|
+
# Read the source file
|
|
31
|
+
source = None
|
|
32
|
+
try:
|
|
33
|
+
with open(file_, 'r', encoding='utf-8') as fh:
|
|
34
|
+
source = fh.read()
|
|
35
|
+
except:
|
|
36
|
+
pass
|
|
37
|
+
if source is None:
|
|
38
|
+
errors.append(f'Failed to load "{file_}"')
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
# Split the source lines and process documentation comments
|
|
42
|
+
lines = _R_SPLIT.split(source)
|
|
43
|
+
for ix_line, line in enumerate(lines):
|
|
44
|
+
# function/group/doc/return documentation keywords?
|
|
45
|
+
match_key = _R_KEY.match(line)
|
|
46
|
+
if match_key is not None:
|
|
47
|
+
key = match_key['key']
|
|
48
|
+
text = match_key['text']
|
|
49
|
+
text_trim = text.strip()
|
|
50
|
+
|
|
51
|
+
# Keyword used outside of function?
|
|
52
|
+
if key != 'function' and func is None:
|
|
53
|
+
errors.append(f'{file_}:{ix_line + 1}: {key} keyword outside function')
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
# Process the keyword
|
|
57
|
+
if key == 'group':
|
|
58
|
+
if text_trim == '':
|
|
59
|
+
errors.append(f'{file_}:{ix_line + 1}: Invalid function group name "{text_trim}"')
|
|
60
|
+
continue
|
|
61
|
+
if 'group' in func:
|
|
62
|
+
errors.append(f'{file_}:{ix_line + 1}: Function "{func["name"]}" group redefinition')
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# Set the function group
|
|
66
|
+
func['group'] = text_trim
|
|
67
|
+
|
|
68
|
+
elif key in ('doc', 'return'):
|
|
69
|
+
# Add the documentation line - don't add leading blank lines
|
|
70
|
+
func_doc = func.get(key)
|
|
71
|
+
if func_doc is not None or text_trim != '':
|
|
72
|
+
if func_doc is None:
|
|
73
|
+
func_doc = []
|
|
74
|
+
func[key] = func_doc
|
|
75
|
+
func_doc.append(text)
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
# key == 'function'
|
|
79
|
+
if text_trim == '':
|
|
80
|
+
errors.append(f'{file_}:{ix_line + 1}: Invalid function name "{text_trim}"')
|
|
81
|
+
continue
|
|
82
|
+
if text_trim in funcs:
|
|
83
|
+
errors.append(f'{file_}:{ix_line + 1}: Function "{text_trim}" redefinition')
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
# Add the function
|
|
87
|
+
func = {'name': text_trim}
|
|
88
|
+
funcs[text_trim] = func
|
|
89
|
+
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# arg keyword?
|
|
93
|
+
match_arg = _R_ARG.match(line)
|
|
94
|
+
if match_arg is not None:
|
|
95
|
+
name = match_arg['name']
|
|
96
|
+
text = match_arg['text']
|
|
97
|
+
text_trim = text.strip()
|
|
98
|
+
|
|
99
|
+
# Keyword used outside of function?
|
|
100
|
+
if func is None:
|
|
101
|
+
errors.append(f'{file_}:{ix_line + 1}: Function argument "{name}" outside function')
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Get the function argument model, if it exists
|
|
105
|
+
func_args = func.get('args')
|
|
106
|
+
func_arg = None
|
|
107
|
+
if func_args is not None:
|
|
108
|
+
func_arg = next((find_arg for find_arg in func_args if find_arg['name'] == name), None)
|
|
109
|
+
|
|
110
|
+
# Ignore leading argument documentation blank lines
|
|
111
|
+
if func_arg is None and text_trim == '':
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
# Add the fuction model arguments member, if necessary
|
|
115
|
+
if func_args is None:
|
|
116
|
+
func_args = []
|
|
117
|
+
func['args'] = func_args
|
|
118
|
+
|
|
119
|
+
# Add the function argument model, if necessary
|
|
120
|
+
if func_arg is None:
|
|
121
|
+
func_arg = {'name': name, 'doc': []}
|
|
122
|
+
func_args.append(func_arg)
|
|
123
|
+
|
|
124
|
+
# Add the function argument documentation line
|
|
125
|
+
func_arg['doc'].append(text)
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
# Unknown documentation comment?
|
|
129
|
+
match_unknown = _R_UNKNOWN.match(line)
|
|
130
|
+
if match_unknown is not None:
|
|
131
|
+
unknown = match_unknown.group('unknown')
|
|
132
|
+
errors.append(f'{file_}:{ix_line + 1}: Invalid documentation comment "{unknown}"')
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
# Create the library documentation model
|
|
136
|
+
library = {'functions': sorted(funcs.values(), key=lambda func: func['name'])}
|
|
137
|
+
|
|
138
|
+
# Validate the library documentation model
|
|
139
|
+
if len(library['functions']) == 0:
|
|
140
|
+
errors.append('error: No library functions')
|
|
141
|
+
for func in library['functions']:
|
|
142
|
+
func_name = func['name']
|
|
143
|
+
if 'group' not in func:
|
|
144
|
+
errors.append(f'error: Function "{func_name}" missing group')
|
|
145
|
+
if 'doc' not in func:
|
|
146
|
+
errors.append(f'error: Function "{func_name}" missing documentation')
|
|
147
|
+
|
|
148
|
+
# Errors?
|
|
149
|
+
if len(errors) != 0:
|
|
150
|
+
print('\n'.join(errors))
|
|
151
|
+
sys.exit(1)
|
|
152
|
+
|
|
153
|
+
# JSON-serialize the library documentation model
|
|
154
|
+
library_json = json.dumps(library, separators=(',', ':'), sort_keys=True)
|
|
155
|
+
|
|
156
|
+
# Output to stdout?
|
|
157
|
+
if args.output == '-':
|
|
158
|
+
print(library_json)
|
|
159
|
+
else:
|
|
160
|
+
# Output to file
|
|
161
|
+
success = False
|
|
162
|
+
try:
|
|
163
|
+
with open(args.output, 'w', encoding='utf-8') as fh:
|
|
164
|
+
fh.write(library_json)
|
|
165
|
+
success = True
|
|
166
|
+
except:
|
|
167
|
+
pass
|
|
168
|
+
if not success:
|
|
169
|
+
print(f'error: Failed to write "{args.output}"')
|
|
170
|
+
sys.exit(1)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# Library documentation regular expressions
|
|
174
|
+
_R_KEY = re.compile(r'^\s*(?:\/\/|#)\s*\$(?P<key>function|group|doc|return):\s?(?P<text>.*)$')
|
|
175
|
+
_R_ARG = re.compile(r'^\s*(?:\/\/|#)\s*\$arg\s+(?P<name>[A-Za-z_][A-Za-z0-9_]*(?:\.\.\.)?):\s?(?P<text>.*)$')
|
|
176
|
+
_R_UNKNOWN = re.compile(r'^\s*(?:\/\/|#)\s*\$(?P<unknown>[^:]+):')
|
|
177
|
+
_R_SPLIT = re.compile(r'\r?\n')
|