Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused by LESS replace() function behavior

Tags:

regex

css

less

I'm trying to write a LESS mixin that uses part of a string to form a class name. I expected to be able to use replace() to provide a value for a variable and then use the variable in the rule. Here's the test I wrote to verify that it would work:

.foo(@xyz) {
  @pfx: replace(@xyz, "(.).*", "$1");

  .@{pfx}-foo { margin: 0; }
}

.foo(abcdefghijklmnop);

When I run that through lessc (1.7.4) (edit - also 2.1.0), it kind-of works, generating this:

.aaa-foo {
  margin: 0;
}

So, the replace() correctly plucks the first character ("a") from the string I pass in, but instead of

.a-foo

it gives me

.aaa-foo

I've looked at the source for the replace() function and it's dirt-simple, so I'm just completely confused about what's going on. (I've tried the regex as "^(.).*$" with the same result.)

I extended the test a little:

.foo(@xyz) {
  @pfx: replace(@xyz, "(.).*", "$1");

  .@{pfx}-foo { margin: 0; content: "@{pfx}"; }
}

.foo(abcdefghijklmnop);

which gives me:

.aaa-foo {
  margin: 0;
  content: "a";
}

which means that the regex is working fine.

like image 954
Pointy Avatar asked Nov 24 '14 16:11

Pointy


3 Answers

Try this:

@pfx: e(replace(@xyz, "^(.).*$", "$1"));

Or this:

@pfx: replace(~"@{xyz}", "^(.).*$", "$1");

See documentation for the e function.

like image 190
haim770 Avatar answered Oct 10 '22 07:10

haim770


Apparently a few workarounds are provided, but how it works still a myth.

The AST after evaluation was totally correct, which looks like:

Element { combinator: { value: '', emptyOrWhitespace: true },
  value: '.',
  index: 54,
  currentFileInfo: 
   { filename: 'input',
     relativeUrls: undefined,
     rootpath: '',
     currentDirectory: '',
     entryPath: '',
     rootFilename: 'input' } },
Element { combinator: { value: '', emptyOrWhitespace: true },
  value: 
   Quoted { escaped: undefined,
     value: 'a',
     quote: 'a',
     index: undefined,
     currentFileInfo: undefined },
  index: 55,
  currentFileInfo: 
   { filename: 'input',
     relativeUrls: undefined,
     rootpath: '',
     currentDirectory: '',
     entryPath: '',
     rootFilename: 'input' } },
Element { combinator: { value: '', emptyOrWhitespace: true },
  value: '-foo',
  index: 61,
  currentFileInfo: 
   { filename: 'input',
     relativeUrls: undefined,
     rootpath: '',
     currentDirectory: '',
     entryPath: '',
     rootFilename: 'input' } }

But when generating the CSS code, the things go south quickly.

Quoted.prototype.genCSS = function (context, output) {
    if (!this.escaped) {
        output.add(this.quote, this.currentFileInfo, this.index);
    }
    output.add(this.value);
    if (!this.escaped) {
        output.add(this.quote);
    }
};

Quoted node will output the value with quote around it when it is not escaped. Whereas, somehow, the quote is the first char of the value, say 'a' in this case.

And when apply e() around replace, less will put Anonymous node in the AST instead of Quoted node, thus no quote will be outputted.

like image 33
xiaoyi Avatar answered Oct 10 '22 08:10

xiaoyi


try this

.foo(@xyz) {
  @props: ~`"@{arguments}"`;

  @pfx: replace(@props, "^(.).*$", "$1");

  .@{pfx}-foo { margin: 0; }
}

.foo(abcdefghijklmnop);

EDIT

Also answering the comment, you want to pass a string to replace, actually a simpler version would be to use .foo(~"abcdefghijklmnop") that forces less to consider the parameter as string without outputting the quotes.

like image 1
DRC Avatar answered Oct 10 '22 06:10

DRC