zx icon indicating copy to clipboard operation
zx copied to clipboard

Add something like `$.multiline` for multiline commands?

Open zloirock opened this issue 3 years ago • 10 comments

I saw #334, but I think that it could be better to simplify it - at least, avoid extra backslashes in the tag and extra spaces in the created command.

For example, in

await $`test262-harness \
    --threads=${ os.cpus().length } \
    --host-args="--unhandled-rejections=none" \
    --preprocessor=preprocessor.js \
    --prelude=../../packages/core-js-bundle/index.js \
    --test262-dir=node_modules/test262 \
    node_modules/test262/test/built-ins/**/*.js \
`;

backslashes make the code less readable and create

test262-harness     --threads=8     --host-args="--unhandled-rejections=none"     --preprocessor=preprocessor.js     --prelude=../../packages/core-js-bundle/index.js     --test262-dir=node_modules/test262     node_modules/test262/test/built-ins/**/*.js 

command with many extra spaces that is also not good for readability.

zloirock avatar Oct 27 '22 11:10 zloirock

What about using an array? This feature is already implemented, avoid backslashes and "extra spaces", allow comments, and doesn't break the ability of running more than one command in the same $ call.

await $`test262-harness ${[
  `--threads=${ os.cpus().length }`,
  '--host-args="--unhandled-rejections=none"',
  '--preprocessor=preprocessor.js',
  '--prelude=../../packages/core-js-bundle/index.js',
  '--test262-dir=node_modules/test262',
  'node_modules/test262/test/built-ins/**/*.js',
  // This should do what you expect, doesn't it?
]}`

pizzafroide avatar Dec 08 '22 09:12 pizzafroide

@pizzafroide 12 extra quotes, 6 extra commas, 2 extra square brackets, 2 extra curly brackets, and 1 extra dollar sign? 23(!!!) extra special symbols have a great effect on the readability of the command. I don't think that it's something that I want.

zloirock avatar Dec 08 '22 09:12 zloirock

Fair enough! 😄

Although, I don't quite agree that more characters necessarily means less readability. As a matter of fact, if that was the case, $.multiline should only be used for commands using 10 backslashes or more. Actually, the backslashes clearly state that this is a multiline command which, imo, is more readable than no backslashes at all.

On the other hand, it is true that all these unnecessary spaces (when using backslashes) makes the final command output ugly. $.multiline would clearly be useful for that matter.

So what about something like this:

$.multiline = (strings, ...values) =>
  $(
    strings.map((s) => s.replace(/\s+/g, ' ')),
    ...values
  )
await $.multiline`echo \
  foo \
  bar \
`

It allows using backslashes or not using them (you decide), and the command output is prettier...

pizzafroide avatar Dec 08 '22 10:12 pizzafroide

What is the argument against replacing backslash-newline-spaces with a single space by default?

nichtich avatar Jan 25 '23 21:01 nichtich

@nichtich too many spaces, like in the example from the top?

zloirock avatar Jan 26 '23 03:01 zloirock

I thought to do it in in $ with something like

// remove linebreaks and whitespace if preceded by line continuation character \
pieces = pieces.map(p => p.replaceAll(/\s*\\(\r\n|\n|\r)\s*/gm,' '))

nichtich avatar Jan 26 '23 10:01 nichtich

What about using an array?

This interacts awkwardly with the quoting behavior. The README shows this example:

let flags = [
  '--oneline',
  '--decorate',
  '--color',
]
await $`git log ${flags}`

But users may be surprised (as I was) to discover that this doesn't work:

const curlArgs = [
  "-sLX POST",
  '--header "Content-Type: application/json"',
  '--data "{}"',
  "example.com",
];
const curlResult = await $`curl ${curlArgs}`; // fails

The reason is that each argument in the array is quoted, so curl throws the error option --header "Content-Type: application/json": is unknown. Using curlArgs.join(' ') doesn't work, either, since curl will just see one big, quoted arg.

I don't see anything in zx's docs about how to pass args to shell commands that use the common --key value convention. Am I missing something? (Happy to start a Discussion if you'd prefer.)

TrevorBurnham avatar Feb 27 '23 21:02 TrevorBurnham

--key and value should be two separated items in curlArgs. Try:

const curlArgs = [
  '-sLX', 'POST',
  '--header', 'Content-Type: application/json',
  '--data', '{}',
  'example.com',
]
const curlResult = await $`curl ${curlArgs}`;

pizzafroide avatar Feb 28 '23 08:02 pizzafroide

@pizzafroide Thanks! Somehow I didn't think of that. 😅 Though, on the topic of this ticket, that's an example where the array format isn't an ideal way of expressing multiline commands: Prettier is going to force each arg to a separate line, breaking the visual connection between key-value pairings. (Sure, you don't have to use Prettier… but being able to use Prettier is one of the great perks of using zx IMO!) A nice solution to the Prettier issue would be for zx to automatically flatten nested arrays so that they could be used to group arguments together:

const curlArgs = [
  ['-sLX', 'POST'],
  ['--header', 'Content-Type: application/json'],
  ['--data', '{}'],
  'example.com',
]
const curlResult = await $`curl ${curlArgs}`;

Also, I was a little surprised that this didn't work:

const curlArgs = [
  '--cookie', '$HOME/.cookie',
  'example.com',
]
const curlResult = await $`curl ${curlArgs}`;

The quoting docs note that globs don't work, but don't mention that env vars (like $HOME) also don't work. Since using env vars in shell commands is very common, it might be nice to include an example of this in the docs. I was able to figure out this solution:

const curlArgs = [
  '--cookie', `${$.env.HOME}/.cookie`,
  'example.com',
]
const curlResult = await $`curl ${curlArgs}`;

TrevorBurnham avatar Feb 28 '23 17:02 TrevorBurnham

Well, this is indeed a little off-topic. It is interesting, though! So I've started a discussion about an idea I had related to that kind of things! You can check it out here: #592.

pizzafroide avatar Mar 01 '23 09:03 pizzafroide

Is this completed? I don't see anything about it.

zloirock avatar Mar 29 '24 04:03 zloirock

This feature will be in v8.

antonmedv avatar Mar 29 '24 07:03 antonmedv