Skip to content

Commit

Permalink
feat: improve parsing of variables for customizer
Browse files Browse the repository at this point in the history
  • Loading branch information
seasick committed Jan 21, 2024
1 parent eec7ecf commit c9f47f3
Show file tree
Hide file tree
Showing 3 changed files with 429 additions and 4 deletions.
34 changes: 33 additions & 1 deletion src/components/Customizer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import { Accordion, AccordionDetails, AccordionSummary } from '@mui/material';
import Accordion from '@mui/material/Accordion';
import AccordionDetails from '@mui/material/AccordionDetails';
import AccordionSummary from '@mui/material/AccordionSummary';
import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormGroup from '@mui/material/FormGroup';
import MenuItem from '@mui/material/MenuItem';
import TextField from '@mui/material/TextField';
import React, { useMemo } from 'react';

Expand Down Expand Up @@ -63,6 +66,27 @@ export default function Customizer({ parameters, onChange }: Props) {
parameter.type === 'number' ||
parameter.type === 'string'
) {
if (parameter.options) {
return (
<TextField
select
label={parameter.description || parameter.name}
fullWidth
key={parameter.name}
name={parameter.name}
onChange={handleParameterChange(false)}
value={parameter.value}
sx={{ mt: 2, p: 1 }}
>
{parameter.options.map((option, idx) => (
<MenuItem key={idx} value={option.value}>
{option.label || option.value}
</MenuItem>
))}
</TextField>
);
}

return (
<TextField
label={parameter.description || parameter.name}
Expand All @@ -72,6 +96,14 @@ export default function Customizer({ parameters, onChange }: Props) {
name={parameter.name}
onChange={handleParameterChange(false)}
value={parameter.value}
InputProps={{
inputProps: {
maxLength: parameter.range?.max,
min: parameter.range?.min,
max: parameter.range?.max,
step: parameter.range?.step,
},
}}
sx={{ mt: 2, p: 1 }}
/>
);
Expand Down
64 changes: 61 additions & 3 deletions src/lib/openSCAD/parseParameter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
type ParameterOption = {
value: string | number;
label: string;
};

type ParameterRange = {
min?: number;
max?: number;
step?: number;
};

export type Parameter = {
name: string;
type: 'string' | 'number' | 'boolean';
value: string | boolean | number;
description?: string;
group?: string;
range?: ParameterRange;
options?: ParameterOption[];
maxLength?: number;
};

export default function parseParameters(script: string): Parameter[] {
Expand All @@ -13,7 +27,8 @@ export default function parseParameters(script: string): Parameter[] {
script = script.split(/^(module |function )/m)[0];

const parameters: Record<string, Parameter> = {};
const parameterRegex = /^([a-z0-9A-Z_$]+)\s*=\s*([^;]+)/gm; // TODO: Use AST parser instead of regex
const parameterRegex =
/^([a-z0-9A-Z_$]+)\s*=\s*([^;]+);[\t\f\cK ]*(\/\/.*)?/gm; // TODO: Use AST parser instead of regex
const groupRegex = /^\/\*\s*\[([^\]]+)\]\s*\*\//gm;

const groupSections: { id: string; group: string; code: string }[] = [];
Expand Down Expand Up @@ -50,7 +65,48 @@ export default function parseParameters(script: string): Parameter[] {
while ((match = parameterRegex.exec(groupSection.code)) !== null) {
const name = match[1];
const value = match[2];
let description;
const typeAndValue = convertType(value);

let description: string;
let options: ParameterOption[];
let range: ParameterRange;

if (match[3]) {
const rawComment = match[3].replace(/^\/\/\s*/, '').trim();
const cleaned = rawComment.replace(/^\[+|\]+$/g, '');

if (!isNaN(rawComment)) {
// If the cleaned comment is a number, then we assume that it is a step
// value (or maximum length in case of a string)
if (typeAndValue.type === 'string') {
range = { max: parseFloat(cleaned) };
} else {
range = { step: parseFloat(cleaned) };
}
} else if (rawComment.startsWith('[') && cleaned.includes(',')) {
// If the options contain commas, we assume that those are options for a select element.
options = cleaned
.trim()
.split(',')
.map((option) => {
const [value, label] = option.trim().split(':');
return { value, label };
});
} else if (cleaned.match(/([0-9]+:?)+/)) {
// If the cleaned comment contains a colon, we assume that it is a range
const [min, maxOrStep, max] = cleaned.trim().split(':');

if (min && (maxOrStep || max)) {
range = { min: parseFloat(min) };
}
if (max || maxOrStep || min) {
range = { ...range, max: parseFloat(max || maxOrStep || min) };
}
if (max && maxOrStep) {
range = { ...range, step: parseFloat(maxOrStep) };
}
}
}

// Now search for the comment right above the parameter definition. This is done
// by splitting the script at the parameter definition and using the last line
Expand All @@ -71,7 +127,9 @@ export default function parseParameters(script: string): Parameter[] {
description,
group: groupSection.group,
name,
...convertType(value),
range,
options,
...typeAndValue,
};
}
});
Expand Down
Loading

0 comments on commit c9f47f3

Please sign in to comment.