Skip to content

Commit

Permalink
feat: improves macro params quoting and encoding
Browse files Browse the repository at this point in the history
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
  • Loading branch information
pedrolamas committed Dec 4, 2024
1 parent 55d3802 commit 286ebcd
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 44 deletions.
62 changes: 18 additions & 44 deletions src/components/widgets/macros/MacroBtn.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
v-for="(param, i) in paramList"
:key="param"
v-model="params[param].value"
:type="isBasicGcodeCommand && !paramNameForRawGcodeCommand ? 'number' : undefined"
:label="param"
persistent-placeholder
outlined
Expand Down Expand Up @@ -89,7 +90,7 @@ import { Component, Prop, Mixins } from 'vue-property-decorator'
import StateMixin from '@/mixins/state'
import type { Macro } from '@/store/macros/types'
import gcodeMacroParams from '@/util/gcode-macro-params'
import isKeyOf from '@/util/is-key-of'
import { gcodeCommandBuilder, isBasicGcodeCommand, getParamNameForRawGcodeCommand } from '@/util/gcode-command-builder'
type MacroParameter = {
value: string | number
Expand All @@ -103,14 +104,16 @@ export default class MacroBtn extends Mixins(StateMixin) {
params: Record<string, MacroParameter> = {}
macrosWithRawParams = {
m117: 'message',
m118: 'message',
m23: 'filename'
} as const
get macroName () {
return this.macro.name.toUpperCase()
}
get isBasicGcodeCommand () {
return isBasicGcodeCommand(this.macroName)
}
get isMacroForGcodeCommand () {
return /^[gm]\d+$/i.test(this.macro.name)
get paramNameForRawGcodeCommand () {
return getParamNameForRawGcodeCommand(this.macroName)
}
get filteredListeners () {
Expand All @@ -128,38 +131,7 @@ export default class MacroBtn extends Mixins(StateMixin) {
* The formatted run command for a macro.
*/
get runCommand () {
const command = this.macro.name.toUpperCase()
const isMacroForGcodeCommand = this.isMacroForGcodeCommand
if (this.params) {
const params = isKeyOf(this.macro.name, this.macrosWithRawParams)
? this.params[this.macrosWithRawParams[this.macro.name]].value.toString()
: Object.entries(this.params)
.map(([key, param]) => {
const value = param.value.toString()
if (!value) {
return null
}
const valueDelimiter = value.includes(' ')
? '"'
: ''
const paramSeparator = isMacroForGcodeCommand && !valueDelimiter
? ''
: '='
return `${key.toUpperCase()}${paramSeparator}${valueDelimiter}${value}${valueDelimiter}`
})
.filter(x => x != null)
.join(' ')
if (params) {
return `${command} ${params}`
}
}
return command
return gcodeCommandBuilder(this.macroName, this.params)
}
get borderStyle () {
Expand All @@ -170,14 +142,16 @@ export default class MacroBtn extends Mixins(StateMixin) {
}
handleClick () {
this.$emit('click', this.macro.name.toUpperCase())
this.$emit('click', this.macroName)
}
mounted () {
if (!this.macro.config || !this.macro.config.gcode) return []
if (!this.macro.config || !this.macro.config.gcode) return
const paramNameForRawGcodeCommand = this.paramNameForRawGcodeCommand
if (isKeyOf(this.macro.name, this.macrosWithRawParams)) {
this.$set(this.params, this.macrosWithRawParams[this.macro.name], { value: '', reset: '' })
if (paramNameForRawGcodeCommand) {
this.$set(this.params, paramNameForRawGcodeCommand, { value: '', reset: '' })
} else {
for (const { name, value } of gcodeMacroParams(this.macro.config.gcode)) {
if (!name.startsWith('_') && !this.params[name]) {
Expand Down
26 changes: 26 additions & 0 deletions src/util/__tests__/gcode-command-builder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { gcodeCommandBuilder } from '../gcode-command-builder'

describe('gcodeCommandBuilder', () => {
it.each([
[{ command: 'M117', params: { Message: '' } }, 'M117'],
[{ command: 'M117', params: { Message: 'ab' } }, 'M117 ab'],
[{ command: 'M117', params: { Message: { value: 'ab' } } }, 'M117 ab'],
[{ command: 'G1', params: { } }, 'G1'],
[{ command: 'G1', params: { X: '' } }, 'G1'],
[{ command: 'G1', params: { X: 10 } }, 'G1 X10'],
[{ command: 'G1', params: { X: { value: 10 } } }, 'G1 X10'],
[{ command: 'TEST', params: { } }, 'TEST'],
[{ command: 'TEST', params: { X: '' } }, 'TEST'],
[{ command: 'TEST', params: { X: 'abc' } }, 'TEST X=abc'],
[{ command: 'TEST', params: { X: 'a b c' } }, 'TEST X="a b c"'],
[{ command: 'TEST', params: { X: ' a b c ' } }, 'TEST X=" a b c "'],
[{ command: 'TEST', params: { X: 'a\'b\'c' } }, 'TEST X="a\'b\'c"'],
[{ command: 'TEST', params: { X: 'a"b"c' } }, 'TEST X="a\\"b\\"c"'],
[{ command: 'TEST', params: { X: 'a#b#c' } }, 'TEST X="a#b#c"'],
[{ command: 'TEST', params: { X: 'a;b;c' } }, 'TEST X="a;b;c"'],
[{ command: 'TEST', params: { X: 'a=b=c' } }, 'TEST X="a=b=c"'],
[{ command: 'TEST', params: { X: 'a\\b\\c' } }, 'TEST X="a\\\\b\\\\c"'],
])('Expects params of "%s", to be %s', ({ command, params }, expected) => {
expect(gcodeCommandBuilder(command, params)).toStrictEqual(expected)
})
})
70 changes: 70 additions & 0 deletions src/util/gcode-command-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import isKeyOf from './is-key-of'

const quotableCharsRegexp = /[ '"#;=\\]/

export type ParamValue = number | string | { value: string | number }

export const rawGcodeCommands = {
M117: 'Message',
M118: 'Message',
M23: 'Filename'
}

export const getParamNameForRawGcodeCommand = (commandName: string) => {
return (
isKeyOf(commandName, rawGcodeCommands) &&
rawGcodeCommands[commandName]
)
}

export const isBasicGcodeCommand = (commandName: string) => {
return /^[gm]\d+$/i.test(commandName)
}

export const gcodeCommandBuilder = (commandName: string, params: Record<string, ParamValue>) => {
commandName = commandName.toUpperCase()

const basicGcodeCommand = isBasicGcodeCommand(commandName)
const paramNameForRawGcodeCommand = getParamNameForRawGcodeCommand(commandName)

const paramsString = paramNameForRawGcodeCommand
? getParamValue(params[paramNameForRawGcodeCommand])
: Object.entries(params)
.map(([key, param]) => {
key = key.toUpperCase()

const value = getParamValue(param)

if (!value) {
return null
}

if (basicGcodeCommand) {
return `${key}${value}`
}

if (quotableCharsRegexp.test(value)) {
const encodedValue = value
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')

return `${key}="${encodedValue}"`
}

return `${key}=${value}`
})
.filter(x => x != null)
.join(' ')

if (paramsString) {
return `${commandName} ${paramsString}`
}

return commandName
}

export const getParamValue = (param: ParamValue) => {
return typeof param === 'object'
? param.value.toString()
: param.toString()
}

0 comments on commit 286ebcd

Please sign in to comment.