Skip to content

Commit

Permalink
fixed lokey and hikey to handle -1
Browse files Browse the repository at this point in the history
added option to skip pickle cache
added test suite testing script
  • Loading branch information
isomc-stone committed Mar 22, 2022
1 parent dc43f80 commit f308187
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 49 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

setup(
name='sfzlint',
version='0.1.2',
version='0.1.4',
description='parser and linter for sfz files written in python',
author='jisaacstone',
packages=['sfzlint'],
Expand Down
14 changes: 11 additions & 3 deletions sfzlint/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import sys
from pathlib import Path
from argparse import ArgumentParser
from . import spec, parser, lint, opcodes
from . import spec, parser, lint, opcodes, settings


def print_codes(search=None, filters=None, printer=print):
for o in spec.opcodes.values():
for o in spec.opcodes().values():
print_code(o, search, filters, printer)


Expand Down Expand Up @@ -47,7 +47,7 @@ def print_codes_in_path(path, search, filters, printer=print):
codes.add(str(opcode))
except Exception as e:
sys.stderr.write(f'Error checking {fp}: {e}')
op_data = spec.opcodes
op_data = spec.opcodes()

def unknown(code):
return {'name': code, 'ver': 'unknown'}
Expand Down Expand Up @@ -80,7 +80,14 @@ def eq_filter(string):
'--path', '-p',
type=Path,
help='print only opcodes found in the sfz file(s) in PATH')
parser.add_argument(
'--no-pickle',
action='store_true',
help='do not use the pickle cache (for testing)')
args = parser.parse_args()
if not args.no_pickle:
settings.pickle = True
1/0
try:
if args.path:
print_codes_in_path(args.path, args.search, args.filters, printer)
Expand All @@ -91,4 +98,5 @@ def eq_filter(string):


def sfzlint():
settings.pickle = True
return lint.main()
7 changes: 6 additions & 1 deletion sfzlint/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pathlib import Path
from lark.exceptions import UnexpectedCharacters, UnexpectedToken
from .parser import validate, SFZ, SFZValidatorConfig
from . import spec
from . import spec, settings


formats = {
Expand Down Expand Up @@ -101,7 +101,12 @@ def main():
parser.add_argument(
'--rel-path',
help='validate includes and sample paths relative to this path')
parser.add_argument(
'--no-pickle',
action='store_true',
help='do not use the pickle cache (for testing)')
args = parser.parse_args()
settings.pickle = not args.no_pickle
lint(args)


Expand Down
12 changes: 6 additions & 6 deletions sfzlint/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,17 @@ def _try_cc_subs(opcode):
for alt in cc_alts:
if alt != variation:
alternative = opcode.replace(variation, alt)
if alternative in spec.cc_opcodes:
if alternative in spec.cc_opcodes():
return alternative
return None


def validate_curvecc(raw_opcode, token, config):
'''Specializing for now, until we get this into the .yml'''
opcode, subs = OpcodeIntRepl.sub(raw_opcode)
known_op = opcode in spec.opcodes
known_op = opcode in spec.opcodes()
if known_op:
validation = spec.opcodes[opcode]
validation = spec.opcodes()[opcode]
spec_ver = config.spec_versions
if spec_ver and validation['ver'] not in spec_ver:
raise ValidationError(
Expand All @@ -126,13 +126,13 @@ def validate_curvecc(raw_opcode, token, config):


def validate_opcode_expr(raw_opcode, token, config):
if raw_opcode not in spec.opcodes:
if raw_opcode not in spec.opcodes():
opcode, subs = OpcodeIntRepl.sub(raw_opcode)
else:
opcode = raw_opcode.value
subs = {}

if opcode not in spec.opcodes:
if opcode not in spec.opcodes():
if 'cc' in opcode and 'curvecc' not in opcode:
new_opcode = _try_cc_subs(opcode)
if new_opcode:
Expand All @@ -143,7 +143,7 @@ def validate_opcode_expr(raw_opcode, token, config):
f'undocumented alias of {new_opcode} ({opcode})',
raw_opcode)
try:
op_meta = spec.opcodes[opcode]
op_meta = spec.opcodes()[opcode]
except KeyError:
raise ValidationWarning(
f'unknown opcode ({opcode})',
Expand Down
5 changes: 5 additions & 0 deletions sfzlint/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
import os


pickle = bool(os.environ.get('NOPICKLE'))
46 changes: 37 additions & 9 deletions sfzlint/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from numbers import Real # int or float
import appdirs
import yaml
from . import validators
from . import validators, settings


ver_mapping = {
Expand All @@ -31,6 +31,7 @@ def ver_code(version):
'string': str,
}


# there will be repetitive calls to listdir
@lru_cache(maxsize=32)
def listdir(path):
Expand Down Expand Up @@ -81,6 +82,12 @@ def validate(self, value, config, *args):
return 'no corresponding curve_index found'


class KeyValidator(validators.Range):
def validate(self, value, config, *args):
if value == -1 and config.spec_versions == ['v1']:
return '-1 is only valid from V2 onward'


overrides = {
('tune', 'value', 'validator'): TuneValidator(),
('sample', 'value', 'validator'): SampleValidator(),
Expand All @@ -93,6 +100,9 @@ def validate(self, value, config, *args):
('group_label', 'value', 'type'): object,
('region_label', 'value', 'type'): object,
('sw_label', 'value', 'type'): object,
# -1 prevents region being triggered by any key
('hikey', 'value', 'validator'): KeyValidator(-1, 127),
('lokey', 'value', 'validator'): KeyValidator(-1, 127),
}


Expand Down Expand Up @@ -160,9 +170,13 @@ def op_to_validator(op_data, **kwargs):
alias_meta['ver'] = valid_meta['ver']
yield alias_meta
if 'modulation' in alias:
yield from extract_modulation(alias['modulation'].items(), alias['name'])
yield from extract_modulation(
alias['modulation'].items(),
alias['name'])
if 'modulation' in op_data:
yield from extract_modulation(op_data['modulation'].items(), op_data['name'])
yield from extract_modulation(
op_data['modulation'].items(),
op_data['name'])


def extract_modulation(items, op_name):
Expand All @@ -179,9 +193,9 @@ def _extract_vdr_meta(op_data, valid_meta):
if v_key not in valid_meta:
valid_meta[v_key] = {}
valid_meta[v_key]['validator'] = _validator(op_data[v_key])
if 'type' in op_data[v_key]:
valid_meta[v_key]['type'] = type_mapping[
op_data[v_key]['type_name']]
type_name = op_data[v_key].get('type_name')
if type_name:
valid_meta[v_key]['type'] = type_mapping[type_name]


def _validator(data_value):
Expand All @@ -199,6 +213,9 @@ def _validator(data_value):


def _pickled(name, fn):
# pickling as cache cuts script time by ~400ms on my system
if not settings.pickle:
return fn()
user_cache_dir = Path(appdirs.user_cache_dir("sfzlint", "jisaacstone"))
p_file = user_cache_dir / f'{name}.pickle'
if not p_file.exists():
Expand All @@ -212,6 +229,17 @@ def _pickled(name, fn):
return data


# pickling as cache cuts script time by ~400ms on my system
opcodes = _pickled('opcodes', lambda: _override(_extract()))
cc_opcodes = {k for k in opcodes if 'cc' in k and 'curvecc' not in k}
_cache = {}


def opcodes():
if 'opcodes' not in _cache:
_cache['opcodes'] = _pickled('opcodes', lambda: _override(_extract()))
return _cache['opcodes']


def cc_opcodes():
if 'cc_opcodes' not in _cache:
_cache['cc_opcodes'] = {
k for k in opcodes() if 'cc' in k and 'curvecc' not in k}
return _cache['cc_opcodes']
2 changes: 1 addition & 1 deletion sfzlint/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __init__(self, name):
self.name = name

def validate(self, value, *args):
opc = spec.opcodes[self.name].get('value')
opc = spec.opcodes()[self.name].get('value')
if opc and 'validator' in opc:
return opc['validator'].validate(value, *args)

Expand Down
57 changes: 29 additions & 28 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pathlib import Path
from collections import namedtuple
from sfzlint.cli import sfzlint, sfzlist
from sfzlint import settings


fixture_dir = Path(__file__).parent / 'fixtures'
Expand All @@ -20,21 +21,32 @@ def __new__(cls, file, row, column, l_m):
return super().__new__(cls, file, row, column, level, message)


def patchargs(path, *args):
newargv = ['sfzlint', '--no-pickle', str(fixture_dir / path)] + list(args)

def wrapper(fn):
return patch('sys.argv', new=newargv)(fn)

return wrapper


class TestSFZLint(TestCase):
def tearDown(self):
# Ensure this does not get accidentally set
self.assertFalse(settings.pickle)

def assert_has_message(self, message, err_list):
msglen = len(message)
msgs = {e.message[:msglen] for e in err_list}
self.assertIn(message, msgs, f'{message} not in {err_list}')

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'basic/valid.sfz')])
@patchargs('basic/valid.sfz')
@patch('builtins.print')
def test_valid_file(self, print_mock):
sfzlint()
self.assertFalse(print_mock.called, print_mock.call_args_list)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'basic/bad.sfz')])
@patchargs('basic/bad.sfz')
@patch('builtins.print')
def test_invalid_file(self, print_mock):
sfzlint()
Expand All @@ -43,8 +55,7 @@ def test_invalid_file(self, print_mock):
for a in print_mock.call_args_list]
self.assert_has_message('unknown opcode', calls)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'basic')])
@patchargs('basic')
def test_lint_dir(self):
with patch('builtins.print') as print_mock:
sfzlint()
Expand All @@ -53,8 +64,7 @@ def test_lint_dir(self):
for a in print_mock.call_args_list]
self.assert_has_message('unknown opcode', calls)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'include/inbadfile.sfz')])
@patchargs('include/inbadfile.sfz')
def test_include_parse_error(self):
with patch('builtins.print') as print_mock:
sfzlint()
Expand All @@ -63,16 +73,13 @@ def test_include_parse_error(self):
for a in print_mock.call_args_list]
self.assert_has_message('error loading include', calls)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'include/hasinc.sfz')])
@patchargs('include/hasinc.sfz')
@patch('builtins.print')
def test_include_define(self, print_mock):
sfzlint()
self.assertFalse(print_mock.called, print_mock.call_args_list)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'basic/valid.sfz'),
'--spec-version', 'v1'])
@patchargs('basic/valid.sfz', '--spec-version', 'v1')
@patch('builtins.print')
def test_spec_version(self, print_mock):
sfzlint()
Expand All @@ -82,8 +89,7 @@ def test_spec_version(self, print_mock):
self.assert_has_message('header spec v2 not in', calls)
self.assert_has_message('opcode spec v2 is not', calls)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'basic/nosample.sfz')])
@patchargs('basic/nosample.sfz')
@patch('builtins.print')
def test_missing_sample(self, print_mock):
sfzlint()
Expand All @@ -92,22 +98,19 @@ def test_missing_sample(self, print_mock):
for a in print_mock.call_args_list]
self.assert_has_message('file not found', calls)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'basic/relsample.sfz')])
@patchargs('basic/relsample.sfz')
def test_relative_path(self):
with patch('builtins.print') as print_mock:
sfzlint()
self.assertFalse(print_mock.called, print_mock.call_args_list)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'basic/def_path.sfz')])
@patchargs('basic/def_path.sfz')
def test_default_path(self):
with patch('builtins.print') as print_mock:
sfzlint()
self.assertFalse(print_mock.called, print_mock.call_args_list)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'basic/badcase.sfz')])
@patchargs('basic/badcase.sfz')
def test_bad_case(self):
with patch('builtins.print') as print_mock:
sfzlint()
Expand All @@ -119,16 +122,14 @@ def test_bad_case(self):
else:
self.assert_has_message('file not found', calls)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'include/sub/relpath.sfz'),
'--rel-path', str(fixture_dir / 'include')])
@patchargs('include/sub/relpath.sfz',
'--rel-path', str(fixture_dir / 'include'))
def test_rel_path(self):
with patch('builtins.print') as print_mock:
sfzlint()
self.assertFalse(print_mock.called, print_mock.call_args_list)

@patch('sys.argv', new=[
'sfzlint', str(fixture_dir / 'aria_program.xml')])
@patchargs('aria_program.xml')
def test_xml_with_defines(self):
with patch('builtins.print') as print_mock:
sfzlint()
Expand All @@ -140,7 +141,7 @@ def test_xml_with_defines(self):


class TestSFZList(TestCase):
@patch('sys.argv', new=['sfzlist'])
@patch('sys.argv', new=['sfzlist', '--no-pickle'])
def test_valid_file(self):
print_mock = MagicMock()
sfzlist(print_mock)
Expand All @@ -151,7 +152,7 @@ def test_valid_file(self):
self.assertIn(test_opcode, opcodes)

@patch('sys.argv', new=[
'sfzlist', '--path', str(fixture_dir / 'basic')])
'sfzlist', '--no-pickle', '--path', str(fixture_dir / 'basic')])
def test_path_dir(self):
print_mock = MagicMock()
sfzlist(print_mock)
Expand Down
Loading

0 comments on commit f308187

Please sign in to comment.