Skip to content

Commit 8ca077a

Browse files
authored
Merge pull request #715 from mathjax/setoptions-package
Implement \setOptions to change tex and package options
2 parents e2e10b8 + 71b40b4 commit 8ca077a

8 files changed

Lines changed: 199 additions & 2 deletions

File tree

components/src/dependencies.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const dependencies = {
2727
'[tex]/noundefined': ['input/tex-base'],
2828
'[tex]/physics': ['input/tex-base'],
2929
'[tex]/require': ['input/tex-base'],
30+
'[tex]/setoptions': ['input/tex-base'],
3031
'[tex]/tagformat': ['input/tex-base'],
3132
'[tex]/textmacros': ['input/tex-base'],
3233
'[tex]/unicode': ['input/tex-base'],
@@ -61,6 +62,7 @@ const allPackages = [
6162
'[tex]/noundefined',
6263
'[tex]/physics',
6364
'[tex]/require',
65+
'[tex]/setoptions',
6466
'[tex]/tagformat',
6567
'[tex]/textmacros',
6668
'[tex]/unicode',
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"component": "input/tex/extensions/setoptions",
3+
"targets": ["input/tex/setoptions"]
4+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './lib/setoptions.js';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const PACKAGE = require('../../../../../webpack.common.js');
2+
3+
module.exports = PACKAGE(
4+
'input/tex/extensions/setoptions', // the package to build
5+
'../../../../../../js', // location of the MathJax js library
6+
[ // packages to link to
7+
'components/src/input/tex-base/lib',
8+
'components/src/core/lib'
9+
],
10+
__dirname // our directory
11+
);

components/src/source.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const source = {
2929
'[tex]/noundefined': `${src}/input/tex/extensions/noundefined/noundefined.js`,
3030
'[tex]/physics': `${src}/input/tex/extensions/physics/physics.js`,
3131
'[tex]/require': `${src}/input/tex/extensions/require/require.js`,
32+
'[tex]/setoptions': `${src}/input/tex/extensions/setoptions/setoptions.js`,
3233
'[tex]/tagformat': `${src}/input/tex/extensions/tagformat/tagformat.js`,
3334
'[tex]/textmacros': `${src}/input/tex/extensions/textmacros/textmacros.js`,
3435
'[tex]/unicode': `${src}/input/tex/extensions/unicode/unicode.js`,

ts/input/tex/AllPackages.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import './newcommand/NewcommandConfiguration.js';
4444
import './noerrors/NoErrorsConfiguration.js';
4545
import './noundefined/NoUndefinedConfiguration.js';
4646
import './physics/PhysicsConfiguration.js';
47+
import './setoptions/SetOptionsConfiguration.js';
4748
import './tagformat/TagFormatConfiguration.js';
4849
import './textmacros/TextMacrosConfiguration.js';
4950
import './upgreek/UpgreekConfiguration.js';
@@ -79,7 +80,8 @@ if (typeof MathJax !== 'undefined' && MathJax.loader) {
7980
'[tex]/verb',
8081
'[tex]/configmacros',
8182
'[tex]/tagformat',
82-
'[tex]/textmacros'
83+
'[tex]/textmacros',
84+
'[tex]/setoptions',
8385
);
8486
}
8587

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*************************************************************
2+
*
3+
* Copyright (c) 2021 The MathJax Consortium
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
19+
/**
20+
* @fileoverview Configuration file for the setoptions package.
21+
*
22+
* @author dpvc@mathjax.org (Davide P. Cervone)
23+
*/
24+
25+
import {Configuration, ConfigurationHandler, ParserConfiguration} from '../Configuration.js';
26+
import {TeX} from '../../tex.js';
27+
import TexParser from '../TexParser.js';
28+
import {CommandMap} from '../SymbolMap.js';
29+
import TexError from '../TexError.js';
30+
import ParseUtil from '../ParseUtil.js';
31+
import {Macro} from '../Symbol.js';
32+
import BaseMethods from '../base/BaseMethods.js';
33+
import {expandable, isObject} from '../../../util/Options.js';
34+
35+
export const SetOptionsUtil = {
36+
37+
/**
38+
* Check if options can be set for a given pacakge, and error otherwise.
39+
*
40+
* @param {TexParser} parser The active tex parser.
41+
* @param {string} extension The name of the package whose option is being set.
42+
* @return {boolean} True when options can be set for this package.
43+
*/
44+
filterPackage(parser: TexParser, extension: string): boolean {
45+
if (extension !== 'tex' && !ConfigurationHandler.get(extension)) {
46+
throw new TexError('NotAPackage', 'Not a defined package: %1', extension);
47+
}
48+
const config = parser.options.setoptions;
49+
const options = config.allowOptions[extension];
50+
if ((options === undefined && !config.allowPackageDefault) || options === false) {
51+
throw new TexError('PackageNotSettable', 'Options can\'t be set for package "%1"', extension);
52+
}
53+
return true;
54+
},
55+
56+
/**
57+
* Check if an option can be set and error otherwise.
58+
*
59+
* @param {TexParser} parser The active tex parser.
60+
* @param {string} extension The name of the package whose option is being set.
61+
* @param {string} option The name of the option being set.
62+
* @return {boolean} True when the option can be set.
63+
*/
64+
filterOption(parser: TexParser, extension: string, option: string): boolean {
65+
const config = parser.options.setoptions;
66+
const options = config.allowOptions[extension] || {};
67+
const allow = (options.hasOwnProperty(option) && !isObject(options[option]) ? options[option] : null);
68+
if (allow === false || (allow === null && !config.allowOptionsDefault)) {
69+
throw new TexError('OptionNotSettable', 'Option "%1" is not allowed to be set', option);
70+
}
71+
if (!(extension === 'tex' ? parser.options : parser.options[extension])?.hasOwnProperty(option)) {
72+
if (extension === 'tex') {
73+
throw new TexError('InvalidTexOption', 'Invalid TeX option "%1"', option);
74+
} else {
75+
throw new TexError('InvalidOptionKey', 'Invalid option "%1" for package "%2"', option, extension);
76+
}
77+
}
78+
return true;
79+
},
80+
81+
/**
82+
* Verify an option's value before setting it.
83+
*
84+
* @param {TexParser} parser The active tex parser.
85+
* @param {string} extension The name of the package whose option this is.
86+
* @param {string} option The name of the option being set.
87+
* @param {string} value The value to give to the option.
88+
* @return {string} The (possibly modified) value for the option
89+
*/
90+
filterValue(_parser: TexParser, _extension: string, _option: string, value: string): string {
91+
return value;
92+
}
93+
94+
};
95+
96+
const setOptionsMap = new CommandMap('setoptions', {
97+
setOptions: 'SetOptions'
98+
}, {
99+
/**
100+
* Implements \setOptions[package]{option-values}
101+
*
102+
* @param {TexParser} parser The active tex parser.
103+
* @param {string} name The name of the macro being processed.
104+
*/
105+
SetOptions(parser: TexParser, name: string) {
106+
const extension = parser.GetBrackets(name) || 'tex';
107+
const options = ParseUtil.keyvalOptions(parser.GetArgument(name));
108+
const config = parser.options.setoptions;
109+
if (!config.filterPackage(parser, extension)) return;
110+
for (const key of Object.keys(options)) {
111+
if (config.filterOption(parser, extension, key)) {
112+
(extension === 'tex' ? parser.options : parser.options[extension])[key] =
113+
config.filterValue(parser, extension, key, options[key]);
114+
}
115+
}
116+
}
117+
});
118+
119+
/**
120+
* If the require package is available, save the original require,
121+
* and define a macro that loads the extension and sets
122+
* its options, if any.
123+
*
124+
* @param {ParserConfiguration} config The current configuration.
125+
* @param {TeX} jax The active tex input jax.
126+
*/
127+
function setoptionsConfig(_config: ParserConfiguration, jax: TeX<any, any, any>) {
128+
const require = jax.parseOptions.handlers.get('macro').lookup('require') as any;
129+
if (require) {
130+
setOptionsMap.add('Require', new Macro('Require', require._func));
131+
setOptionsMap.add('require', new Macro('require', BaseMethods.Macro,
132+
['\\Require{#2}\\setOptions[#2]{#1}', 2, '']));
133+
}
134+
}
135+
136+
export const SetOptionsConfiguration = Configuration.create(
137+
'setoptions', {
138+
handler: {macro: ['setoptions']},
139+
config: setoptionsConfig,
140+
priority: 3, // must be less than the priority of the require package (which is 5).
141+
options: {
142+
setoptions: {
143+
filterPackage: SetOptionsUtil.filterPackage, // filter for whether a package can be configured
144+
filterOption: SetOptionsUtil.filterOption, // filter for whether an option can be set
145+
filterValue: SetOptionsUtil.filterValue, // filter for the value to assign to an option
146+
allowPackageDefault: true, // default for allowing packages when not explicitly set in allowOptions
147+
allowOptionsDefault: true, // default for allowing option that isn't explicitly set in allowOptions
148+
allowOptions: expandable({ // list of packages to allow/disallow, and their options to allow/disallow
149+
//
150+
// top-level tex items can be set, but not these
151+
// (that leaves digits and the tagging options)
152+
//
153+
tex: {
154+
FindTeX: false,
155+
formatError: false,
156+
package: false,
157+
baseURL: false,
158+
tags: false,
159+
maxBuffer: false,
160+
maxMaxros: false,
161+
macros: false,
162+
environments: false
163+
},
164+
//
165+
// These packages can't be configured at all
166+
//
167+
setoptions: false,
168+
autoload: false,
169+
require: false,
170+
configmacros: false,
171+
tagformat: false
172+
})
173+
}
174+
}
175+
}
176+
);

ts/util/Options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const OBJECT = {}.constructor;
2929
/**
3030
* Check if an object is an object literal (as opposed to an instance of a class)
3131
*/
32-
function isObject(obj: any) {
32+
export function isObject(obj: any) {
3333
return typeof obj === 'object' && obj !== null &&
3434
(obj.constructor === OBJECT || obj.constructor === Expandable);
3535
}

0 commit comments

Comments
 (0)