pre-commit-bigfix 2.0.1__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,305 @@
1
+ #!/usr/bin/env python3
2
+ """Run every pre-commit-bigfix hook over a folder, without pre-commit.
3
+
4
+ uvx pre-commit-bigfix [PATH ...]
5
+
6
+ This is the `pre-commit-bigfix` command. It finds the files under each PATH
7
+ (default: the current folder), gives each hook the ones its `files` pattern in
8
+ .pre-commit-hooks.yaml matches, with that hook's default `args`, and prints a
9
+ pass/fail line per hook. The exit code is 1 when any hook fails, and 2 for
10
+ a PATH that does not exist or a bad option.
11
+
12
+ It is read-only. Run by pre-commit, several hooks fix files in place by
13
+ default; here each one is told not to (--auto-fix=no, or --check for the
14
+ hooks that convert or rename), so fixable problems are reported instead.
15
+ To apply the fixes, run the hook itself, or use pre-commit.
16
+
17
+ Hidden folders (a name starting with `.`, or the hidden flag on macOS and
18
+ Windows) are not searched unless --include-hidden is given, and __pycache__
19
+ and node_modules never are; a PATH that names a folder is searched anyway.
20
+ Symlinked files found while searching are skipped, as pre-commit skips them.
21
+ A named file that no hook checks is reported on stderr.
22
+
23
+ bes-siterepo-validate-layout checks a whole repo from `git ls-files`, so it
24
+ runs once in each git repo that holds a PATH, and is skipped when none does.
25
+
26
+ A hook with no matching files is skipped rather than run with no file names:
27
+ run that way, most hooks would search the current folder themselves.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import importlib
34
+ import os
35
+ import re
36
+ import stat
37
+ import subprocess
38
+ import sys
39
+ import traceback
40
+ from typing import NamedTuple
41
+
42
+ from . import bes_common
43
+
44
+
45
+ class Hook(NamedTuple):
46
+ """One hook from .pre-commit-hooks.yaml, as the runner calls it."""
47
+
48
+ id: str
49
+ module: str # in this package; its main(argv) is the console script
50
+ files: str | None # the hook's `files` regex; None: it lists its own
51
+ args: tuple[str, ...] = () # the hook's default `args`
52
+ read_only: tuple[str, ...] = () # what keeps it from writing
53
+
54
+ def load(self):
55
+ """Import the hook's module (only when it runs, as some are heavy)."""
56
+ return importlib.import_module(f"{__package__}.{self.module}")
57
+
58
+
59
+ # Same order, ids, `files` and `args` as .pre-commit-hooks.yaml (minus aliases);
60
+ # tests/test_run_all.py keeps the two in step.
61
+ HOOKS = (
62
+ Hook(
63
+ "bes-schema-validate",
64
+ "bes_schema_validate",
65
+ r"\.(bes|ojo)$|(^|/)(siteConfig|site|digest)\.xml$"
66
+ r"|(^|/)Signatures/[^/]+\.xml$",
67
+ ),
68
+ Hook(
69
+ "bes-conventions-check",
70
+ "bes_conventions_check",
71
+ r"\.bes$",
72
+ read_only=("--auto-fix=no",),
73
+ ),
74
+ Hook(
75
+ "bes-actionscript-lint-schclass",
76
+ "bes_actionscript_lint_schclass",
77
+ r"\.bes$",
78
+ read_only=("--auto-fix=no",),
79
+ ),
80
+ Hook(
81
+ "bes-actionscript-validate-prefetch",
82
+ "bes_actionscript_validate_prefetch",
83
+ r"\.bes$",
84
+ # --auto-fix-network is already off by default; say so anyway
85
+ read_only=("--auto-fix=no", "--auto-fix-network=no"),
86
+ ),
87
+ Hook(
88
+ "bes-actionscript-validate-script",
89
+ "bes_actionscript_validate_script",
90
+ r"\.bes$",
91
+ read_only=("--auto-fix=no",),
92
+ ),
93
+ Hook(
94
+ "bes-relevance-lint",
95
+ "bes_relevance_lint",
96
+ r"\.(bes|bes\.xml|ojo|besrpt|beswrpt|webreport|bsr|rel)$",
97
+ args=("--disable", "W601"),
98
+ ),
99
+ Hook(
100
+ "bes-relevance-convert-group",
101
+ "bes_relevance_convert_group",
102
+ r"\.bes$",
103
+ read_only=("--check",),
104
+ ),
105
+ Hook(
106
+ "bes-siterepo-validate-layout",
107
+ "bes_siterepo_validate_layout",
108
+ None,
109
+ read_only=("--check",),
110
+ ),
111
+ )
112
+
113
+ # any file some hook checks; anything else is not worth listing
114
+ ANY_HOOK_RE = re.compile("|".join(f"(?:{h.files})" for h in HOOKS if h.files))
115
+
116
+
117
+ def _is_hidden(path, name):
118
+ """True for a dot folder, or one with the macOS or Windows hidden flag."""
119
+ if name.startswith("."):
120
+ return True
121
+ try:
122
+ st = os.lstat(path)
123
+ except OSError:
124
+ return False
125
+ # st_flags exists on BSD/macOS and st_file_attributes on Windows; each
126
+ # constant is missing (so 0) on the other platforms
127
+ if getattr(st, "st_flags", 0) & getattr(stat, "UF_HIDDEN", 0):
128
+ return True
129
+ return bool(
130
+ getattr(st, "st_file_attributes", 0) & getattr(stat, "FILE_ATTRIBUTE_HIDDEN", 0)
131
+ )
132
+
133
+
134
+ def _match_path(path):
135
+ """The string a hook's `files` regex is searched in.
136
+
137
+ pre-commit searches the repo-relative path, so `(^|/)Signatures/x.xml`
138
+ sees the folder. The path as given may not hold it (`x.xml`, run from
139
+ inside Signatures/), so the absolute path is used instead.
140
+ """
141
+ return os.path.abspath(path).replace(os.sep, "/")
142
+
143
+
144
+ def discover(paths, include_hidden=False):
145
+ """Return the files under `paths` that some hook checks, sorted, each once.
146
+
147
+ A file named in `paths` is kept even if no hook checks it. Folders are
148
+ walked, skipping symlinked files (pre-commit does not pass them to these
149
+ hooks), bes_common.DISCOVERY_SKIP_DIRS and, unless `include_hidden`, hidden
150
+ folders; a folder named in `paths` is walked even if it is hidden.
151
+ """
152
+ found = set()
153
+ for top in paths:
154
+ if not os.path.isdir(top):
155
+ found.add(os.path.normpath(top))
156
+ continue
157
+ for dirpath, dirnames, filenames in os.walk(top):
158
+ dirnames[:] = [
159
+ d
160
+ for d in dirnames
161
+ if d not in bes_common.DISCOVERY_SKIP_DIRS
162
+ and (include_hidden or not _is_hidden(os.path.join(dirpath, d), d))
163
+ ]
164
+ for name in filenames:
165
+ path = os.path.normpath(os.path.join(dirpath, name))
166
+ if not os.path.islink(path) and ANY_HOOK_RE.search(_match_path(path)):
167
+ found.add(path)
168
+ return sorted(found)
169
+
170
+
171
+ def repo_roots(paths):
172
+ """Return the top folders of the git repos holding `paths`, each once."""
173
+ roots = []
174
+ for path in paths:
175
+ folder = path if os.path.isdir(path) else os.path.dirname(path) or "."
176
+ try:
177
+ result = subprocess.run(
178
+ ["git", "rev-parse", "--show-toplevel"],
179
+ cwd=folder,
180
+ capture_output=True,
181
+ check=False,
182
+ )
183
+ except OSError: # git not installed
184
+ return []
185
+ root = os.fsdecode(result.stdout).strip()
186
+ if result.returncode == 0 and root and root not in roots:
187
+ roots.append(root)
188
+ return roots
189
+
190
+
191
+ def _call(hook, argv):
192
+ """Run `hook`'s main(argv); return its exit code, a crash counting as 1."""
193
+ try:
194
+ code = hook.load().main(argv)
195
+ except SystemExit as exc: # argparse errors, or a hook that exits
196
+ code = exc.code
197
+ except Exception: # noqa: BLE001 - one broken hook must not hide the rest
198
+ traceback.print_exc()
199
+ print(f"{hook.id}: crashed")
200
+ return 1
201
+ if code is None:
202
+ return 0
203
+ return code if isinstance(code, int) else 1
204
+
205
+
206
+ def _run_in_repos(hook, roots):
207
+ """Run a whole-repo hook once in each of `roots`; return its worst exit code."""
208
+ worst = 0
209
+ here = os.getcwd()
210
+ for root in roots:
211
+ print(f"=== {hook.id} ({root})", flush=True)
212
+ # the hook finds its repo from the current folder
213
+ os.chdir(root)
214
+ try:
215
+ worst = max(worst, _call(hook, [*hook.args, *hook.read_only]))
216
+ finally:
217
+ os.chdir(here)
218
+ return worst
219
+
220
+
221
+ def run_hook(hook, files, roots):
222
+ """Run one hook read-only; return (status, code).
223
+
224
+ A file hook gets the `files` its pattern matches; a whole-repo hook runs in
225
+ each of the git repos `roots`. status is "passed", "failed" or
226
+ "skipped: <why>".
227
+ """
228
+ if hook.files is None:
229
+ if not roots:
230
+ return "skipped: not a git repo", 0
231
+ code = _run_in_repos(hook, roots)
232
+ else:
233
+ pattern = re.compile(hook.files)
234
+ matched = [path for path in files if pattern.search(_match_path(path))]
235
+ if not matched:
236
+ return "skipped: no files", 0
237
+ print(f"=== {hook.id}", flush=True)
238
+ # `--` so a file named like an option (-h.bes) is still a file name
239
+ code = _call(hook, [*hook.args, *hook.read_only, "--", *matched])
240
+ sys.stdout.flush()
241
+ return ("failed" if code else "passed"), code
242
+
243
+
244
+ def build_parser():
245
+ parser = argparse.ArgumentParser(
246
+ prog="pre-commit-bigfix",
247
+ description=__doc__,
248
+ formatter_class=argparse.RawDescriptionHelpFormatter,
249
+ )
250
+ parser.add_argument(
251
+ "paths",
252
+ nargs="*",
253
+ default=["."],
254
+ metavar="PATH",
255
+ help="files or folders to check (default: the current folder)",
256
+ )
257
+ parser.add_argument(
258
+ "--skip",
259
+ action="append",
260
+ default=[],
261
+ choices=[hook.id for hook in HOOKS],
262
+ metavar="HOOK",
263
+ help="a hook id not to run; repeatable",
264
+ )
265
+ parser.add_argument(
266
+ "--include-hidden",
267
+ action="store_true",
268
+ help="also search hidden folders (see above)",
269
+ )
270
+ return parser
271
+
272
+
273
+ def main(argv=None):
274
+ """Execution starts here; returns 1 if any hook failed, 2 for a bad PATH."""
275
+ args = build_parser().parse_args(argv)
276
+
277
+ missing = [path for path in args.paths if not os.path.exists(path)]
278
+ if missing:
279
+ for path in missing:
280
+ print(f"pre-commit-bigfix: no such file or folder: {path}", file=sys.stderr)
281
+ return 2
282
+
283
+ files = discover(args.paths, include_hidden=args.include_hidden)
284
+ for path in args.paths:
285
+ if os.path.isfile(path) and not ANY_HOOK_RE.search(_match_path(path)):
286
+ print(f"{path}: not checked by any hook", file=sys.stderr)
287
+ roots = repo_roots(args.paths)
288
+
289
+ results = []
290
+ for hook in HOOKS:
291
+ if hook.id in args.skip:
292
+ results.append((hook.id, "skipped: --skip", 0))
293
+ continue
294
+ status, code = run_hook(hook, files, roots)
295
+ results.append((hook.id, status, code))
296
+
297
+ width = max(len(hook.id) for hook in HOOKS) + 3
298
+ print()
299
+ for hook_id, status, _code in results:
300
+ print(f"{hook_id} {'.' * (width - len(hook_id))} {status}")
301
+ return 1 if any(code for _id, _status, code in results) else 0
302
+
303
+
304
+ if __name__ == "__main__":
305
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env python3
2
+ """Generic loader for .schclass lexer-schema files.
3
+
4
+ The .schclass format is the lex-schema format of the Codejock Xtreme
5
+ ToolkitPro SyntaxEdit control, which the BigFix console embeds for its
6
+ ActionScript editor. A schema is a series of `lexClass:` blocks, each an
7
+ indented list of `key = value` lines describing one lexical class: how the
8
+ class is entered (`start:Tag` literals or `token:tag` keyword lists), how it
9
+ ends (`end:Tag` literals / `end:separators`), escape sequences consumed
10
+ without ending it (`skip:Tag`), context constraints on where it may begin
11
+ (`previous:tag`), token boundary separators, and parent/child containment.
12
+
13
+ This module only LOADS schemas; pre_commit_bigfix/schclass_tokenizer.py walks
14
+ them. Nothing here is ActionScript-specific except the convenience default
15
+ loader at the bottom.
16
+
17
+ Loading supports merging several files so a small override schema can
18
+ supplement a vendored base grammar (same-named classes union their token
19
+ lists; explicitly-set fields replace; new classes append). This matters
20
+ because .schclass files are display grammars, not validation grammars, and
21
+ need small corrections for lint use (see schclass_data/bigfix_overrides.schclass).
22
+
23
+ Format notes pinned by tests (and by the real console-generated file):
24
+ - keys are case-insensitive (`start:tag` and `start:Tag` both occur)
25
+ - values are comma-separated items: quoted strings (with backslash
26
+ escapes: \\ \' \" \n \r \t), @specials (@eol, @specs), or bare tokens
27
+ - repeated `token:tag` lines accumulate, even across blank lines and
28
+ interleaved with other keys
29
+ - `children = 0` means "no children"
30
+ - // comment lines are ignored
31
+ - files may be CRLF or LF
32
+ """
33
+
34
+ import dataclasses
35
+ import os
36
+
37
+ # @-special sentinels used in previous:tag lists (plain strings; a literal
38
+ # "@eol" token text cannot occur, so identity-by-value is safe)
39
+ AT_EOL = "@eol"
40
+ AT_SPECS = "@specs"
41
+
42
+ _ESCAPES = {"\\": "\\", "'": "'", '"': '"', "n": "\n", "r": "\r", "t": "\t"}
43
+
44
+ _DATA_DIR = os.path.join(os.path.dirname(__file__), "schclass_data")
45
+ # resolved relative to __file__ (not importlib.resources): fine for pip /
46
+ # pre-commit installs; would not survive zip-import, which none of our
47
+ # consumers use.
48
+ DEFAULT_SCHCLASS_FILES = (
49
+ os.path.join(_DATA_DIR, "ExpandedActionScript.schclass"),
50
+ os.path.join(_DATA_DIR, "bigfix_overrides.schclass"),
51
+ )
52
+
53
+
54
+ @dataclasses.dataclass
55
+ class LexClass:
56
+ """One `lexClass:` block: a single lexical class of the grammar."""
57
+
58
+ name: str = ""
59
+ parents: tuple = () # from `parent` / `parent:dyn`
60
+ parent_dyn: bool = False
61
+ parent_file: str = None # e.g. `<*.ActionScript>` on the root class
62
+ children: tuple = () # `children = 0` -> ()
63
+ start_tags: tuple = () # literals that enter this class as a state
64
+ end_tags: tuple = () # literals that exit the state (consumed)
65
+ end_at_eol: bool = False # @eol appeared in end:Tag
66
+ skip_tags: tuple = () # escape sequences consumed without ending
67
+ end_separators: tuple = () # exit the state WITHOUT consuming
68
+ end_separators_eol: bool = False
69
+ previous_tags: tuple = () # context constraint; may hold AT_EOL/AT_SPECS
70
+ token_tags: tuple = () # keyword literals (accumulated)
71
+ token_start_separators: tuple = ()
72
+ token_start_eol: bool = False
73
+ token_end_separators: tuple = ()
74
+ token_end_eol: bool = False
75
+ attrs: dict = dataclasses.field(default_factory=dict) # DisplayName, txt:*
76
+
77
+
78
+ @dataclasses.dataclass
79
+ class Schema:
80
+ """An ordered collection of LexClasses parsed from .schclass text."""
81
+
82
+ classes: dict = dataclasses.field(default_factory=dict)
83
+
84
+ def root(self):
85
+ """Return the file-level root class (`parent:file`), or best guess."""
86
+ for cls in self.classes.values():
87
+ if cls.parent_file is not None:
88
+ return cls
89
+ for cls in self.classes.values():
90
+ if not cls.parents:
91
+ return cls
92
+ return next(iter(self.classes.values()), None)
93
+
94
+ def all_token_tags(self):
95
+ """Return {token literal: class name} across every class.
96
+
97
+ The first class declaring a literal wins (schema order).
98
+ """
99
+ tags = {}
100
+ for cls in self.classes.values():
101
+ for token in cls.token_tags:
102
+ tags.setdefault(token, cls.name)
103
+ return tags
104
+
105
+
106
+ def _split_items(value):
107
+ """Split a raw value into (kind, text) items; kind is 'str' or 'bare'.
108
+
109
+ Items are comma-separated at the top level; commas INSIDE quotes (the
110
+ separator lists include a literal ',') must not split. Quoted items decode
111
+ backslash escapes; bare items are kept verbatim (`@eol`, `global`,
112
+ `0x0000FF`, `<*.ActionScript>`).
113
+ """
114
+ items = []
115
+ i, n = 0, len(value)
116
+ while i < n:
117
+ ch = value[i]
118
+ if ch in " \t,":
119
+ i += 1
120
+ continue
121
+ if ch in "'\"":
122
+ quote = ch
123
+ i += 1
124
+ buf = []
125
+ while i < n:
126
+ char = value[i]
127
+ if char == "\\" and i + 1 < n:
128
+ buf.append(_ESCAPES.get(value[i + 1], value[i + 1]))
129
+ i += 2
130
+ continue
131
+ if char == quote:
132
+ i += 1
133
+ break
134
+ buf.append(char)
135
+ i += 1
136
+ items.append(("str", "".join(buf)))
137
+ else:
138
+ j = i
139
+ while j < n and value[j] != ",":
140
+ j += 1
141
+ items.append(("bare", value[i:j].strip()))
142
+ i = j
143
+ return items
144
+
145
+
146
+ def _literals(items):
147
+ """Return the quoted-string literals from a parsed item list."""
148
+ return tuple(text for kind, text in items if kind == "str")
149
+
150
+
151
+ def _has_bare(items, token):
152
+ """True if `items` holds the bare token `token` (case-insensitive)."""
153
+ return any(kind == "bare" and text.lower() == token for kind, text in items)
154
+
155
+
156
+ def _apply_key(cls, key, items, raw_value):
157
+ """Fold one `key = value` line into the LexClass being built."""
158
+ if key == "name":
159
+ cls.name = raw_value
160
+ elif key == "parent":
161
+ cls.parents += tuple(text for _kind, text in items)
162
+ elif key == "parent:dyn":
163
+ cls.parents += tuple(text for _kind, text in items)
164
+ cls.parent_dyn = True
165
+ elif key == "parent:file":
166
+ cls.parent_file = raw_value
167
+ elif key == "children":
168
+ if not _has_bare(items, "0"):
169
+ cls.children += tuple(text for _kind, text in items)
170
+ elif key == "start:tag":
171
+ cls.start_tags += _literals(items)
172
+ elif key == "end:tag":
173
+ cls.end_tags += _literals(items)
174
+ cls.end_at_eol = cls.end_at_eol or _has_bare(items, AT_EOL)
175
+ elif key == "skip:tag":
176
+ cls.skip_tags += _literals(items)
177
+ elif key == "end:separators":
178
+ cls.end_separators += _literals(items)
179
+ cls.end_separators_eol = cls.end_separators_eol or _has_bare(items, AT_EOL)
180
+ elif key == "previous:tag":
181
+ for kind, text in items:
182
+ if kind == "bare" and text.lower() in (AT_EOL, AT_SPECS):
183
+ cls.previous_tags += (text.lower(),)
184
+ elif kind == "str":
185
+ cls.previous_tags += (text,)
186
+ elif key == "token:tag":
187
+ cls.token_tags += _literals(items)
188
+ elif key == "token:start:separators":
189
+ cls.token_start_separators += _literals(items)
190
+ cls.token_start_eol = cls.token_start_eol or _has_bare(items, AT_EOL)
191
+ elif key == "token:end:separators":
192
+ cls.token_end_separators += _literals(items)
193
+ cls.token_end_eol = cls.token_end_eol or _has_bare(items, AT_EOL)
194
+ else:
195
+ # presentation / unknown keys (DisplayName, txt:*, ParseOnScreen...)
196
+ decoded = [text for _kind, text in items]
197
+ cls.attrs[key] = ", ".join(decoded) if decoded else raw_value
198
+
199
+
200
+ def parse_schclass_text(text):
201
+ """Parse .schclass text into a Schema."""
202
+ text = text.replace("\r\n", "\n").replace("\r", "\n")
203
+ schema = Schema()
204
+ current = None
205
+ for raw_line in text.split("\n"):
206
+ stripped = raw_line.strip()
207
+ if not stripped or stripped.startswith("//"):
208
+ continue
209
+ if stripped.lower().startswith("lexclass:"):
210
+ if current is not None and current.name:
211
+ schema.classes[current.name] = current
212
+ current = LexClass()
213
+ continue
214
+ if current is None or "=" not in stripped:
215
+ continue
216
+ key, _, value = stripped.partition("=")
217
+ key = key.strip().lower()
218
+ value = value.strip()
219
+ _apply_key(current, key, _split_items(value), value)
220
+ if current is not None and current.name:
221
+ schema.classes[current.name] = current
222
+ return schema
223
+
224
+
225
+ # (list field, paired eol/dyn flag) -- a merge replaces the pair together when
226
+ # the override sets either half, so `end:Tag = '"'` (no @eol) clears the flag.
227
+ _PAIRED_FIELDS = (
228
+ ("parents", "parent_dyn"),
229
+ ("start_tags", None),
230
+ ("end_tags", "end_at_eol"),
231
+ ("skip_tags", None),
232
+ ("end_separators", "end_separators_eol"),
233
+ ("previous_tags", None),
234
+ ("token_start_separators", "token_start_eol"),
235
+ ("token_end_separators", "token_end_eol"),
236
+ ("children", None),
237
+ )
238
+
239
+
240
+ def merge_schemas(base, override):
241
+ """Merge `override` onto `base`; return a new Schema.
242
+
243
+ Same-named classes: token_tags are unioned (base order, then new ones);
244
+ any field group the override explicitly sets replaces the base's; fields
245
+ the override leaves unset are preserved. New classes append after the
246
+ base's, keeping schema order deterministic.
247
+ """
248
+ merged = Schema()
249
+ for name, cls in base.classes.items():
250
+ merged.classes[name] = dataclasses.replace(cls, attrs=dict(cls.attrs))
251
+ for name, ocls in override.classes.items():
252
+ if name not in merged.classes:
253
+ merged.classes[name] = dataclasses.replace(ocls, attrs=dict(ocls.attrs))
254
+ continue
255
+ bcls = merged.classes[name]
256
+ bcls.token_tags += tuple(
257
+ token for token in ocls.token_tags if token not in bcls.token_tags
258
+ )
259
+ for list_field, flag_field in _PAIRED_FIELDS:
260
+ flag_set = flag_field is not None and getattr(ocls, flag_field)
261
+ if getattr(ocls, list_field) or flag_set:
262
+ setattr(bcls, list_field, getattr(ocls, list_field))
263
+ if flag_field is not None:
264
+ setattr(bcls, flag_field, getattr(ocls, flag_field))
265
+ if ocls.parent_file is not None:
266
+ bcls.parent_file = ocls.parent_file
267
+ bcls.attrs.update(ocls.attrs)
268
+ return merged
269
+
270
+
271
+ def load_schclass_files(paths):
272
+ """Parse each .schclass file and merge them left to right into one Schema."""
273
+ schema = None
274
+ for path in paths:
275
+ with open(path, "rb") as handle:
276
+ text = handle.read().decode("utf-8", errors="replace")
277
+ parsed = parse_schclass_text(text)
278
+ schema = parsed if schema is None else merge_schemas(schema, parsed)
279
+ return schema if schema is not None else Schema()
280
+
281
+
282
+ def load_default_actionscript_schema():
283
+ """Load the vendored BigFix ActionScript grammar plus its overrides."""
284
+ return load_schclass_files(DEFAULT_SCHCLASS_FILES)