Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

string.split() function #1950

Closed
5 tasks done
xzyfer opened this issue Jan 5, 2016 · 15 comments
Closed
5 tasks done

string.split() function #1950

xzyfer opened this issue Jan 5, 2016 · 15 comments
Assignees
Labels
enhancement New feature or request help wanted Extra attention is needed planned We would like to add this feature at some point

Comments

@xzyfer
Copy link

xzyfer commented Jan 5, 2016


It would be really useful to have a str-split($string, $delimiter:" ") function akin to JavaScript, or Ruby.

The primary use case I run into is splitting selector strings i.e.

.foo .bar { /* code */ }

It's rather difficult to get .bar from & without boilerplate string functions or importing something like sassdash.

A another use case I run into is remove part of selector. As it stands selector-replace does not allow you to remove part of a selector (i.e. replace it with "" or null). Being able to easily split selector strings would makes this much easier.

@cimmanon
Copy link

cimmanon commented Jan 5, 2016

Selectors aren't strings. They're lists of lists. If you want to remove or capture part of a selector, you use a loop.

.foo .bar, .one .two {
    @each $sel in & {
        @debug nth($sel, -1);
    }
}

Output:

>> DEBUG: .bar
>> DEBUG: .two

@xzyfer
Copy link
Author

xzyfer commented Jan 5, 2016

Selectors aren't strings. They're lists of lists.

Yes I understand.

If you want to remove or capture part of a selector, you use a loop.

You're correct.


As it turns out, in my experimentation, where I was mistaken was that even if there is one selector,& is still a nested list. This is where I was getting tripped up.

i.e.

.foo .bar { 
  a: length(&); // 1
  b: length(nth(&, 1)); // 2
}

I was expecting a to be b. I understand why it works this way, it was just a detail I had missed.

@xzyfer
Copy link
Author

xzyfer commented Jan 5, 2016

I guess, assuming certain constraints on the input string, and only wanting " " as the $delimiter you can fake str-split with nth(parse-selector(".foo .bar"), 1). Which granted is the usecase is proposed.

@xzyfer
Copy link
Author

xzyfer commented Jan 5, 2016

@davidkpiano True. Sorry I wasn't more clear.

As it turns out, in my experimentation, where I was mistaken was that even if there is one selector,& is still a nested list.

.foo .bar {
  test: nth(nth(&, 1), 2);
}

note: selector-parse() isn't required when you're dealing with &

@xzyfer
Copy link
Author

xzyfer commented Jan 5, 2016

With regards to comment on selector-parse you can split a string on white space.

.foo .bar {
  test: nth(nth(selector-parse(".foo .bar"), 1), 2); // .bar
}

I suspect there may be limitations on the types of strings this works with i.e. valid selectors (maybe?)

@davidkpiano
Copy link

(Sorry, deleted my comment as it was redundant.)

Maybe a selector-split function would be more appropriate? Something that splits based on combinators:

$test: selector-split(".foo .bar", " ");
// => ((.foo,), (.bar,))

$test: selector-split(".foo .bar > .baz", ">");
// => ((.foo, .bar), (.baz,))

@chriseppstein chriseppstein added enhancement New feature or request planned We would like to add this feature at some point help wanted Extra attention is needed labels Jan 5, 2016
@chriseppstein
Copy link

Something that splits based on combinators

whitespace is a combinator. it is the "descendant combinator".

Anyways, a str-split function seems generally useful and is easy enough to add.

@esr360
Copy link

esr360 commented Nov 14, 2016

+1 for a str-split or selector-split function

@xzyfer
Copy link
Author

xzyfer commented May 4, 2018

@nex3 should this also come with a feature check flag?

@nex3
Copy link
Contributor

nex3 commented May 19, 2018

Generally we don't add feature flags for functions, since they can be detected with function-exists().

@xzyfer
Copy link
Author

xzyfer commented May 19, 2018 via email

@esr360
Copy link

esr360 commented Jun 19, 2018

something like this seems like a fairly elegant work around to me:

.foo .bar {
    // this spoofs the selectors of `&` into a Sass list
    $selectors: str-replace(inspect(&), ' ', ', ');
    // selector is now split - do whatever you need to do
    ...
    // when finished, rebuild selector
    $selectors: selector-parse($selectors);
}

Sorry if this was already clear from previous comments.

@nex3
Copy link
Contributor

nex3 commented Aug 29, 2022

The proposal has landed. We'll keep it open for comment for a month, then land it.

@nex3 nex3 changed the title [request] str-split function string.split() function Oct 28, 2022
nex3 added a commit that referenced this issue Oct 28, 2022
@nex3
Copy link
Contributor

nex3 commented Oct 28, 2022

Looking over this again as we start to move towards implementation, I had a thought: should this return a bracketed list?

The main reason to return an unbracketed list is historical precedent. No existing function returns an unbracketed list by default. However, I think this might be a less compelling argument than it seems at first glance. Here are all the functions that currently return lists:

  • list.append(), list.join(), and list.zip() all explicitly return a list style that matches their inputs, so that doesn't really set a precedent.

  • list.slash() is explicitly intended to return a value that's usable directly as a slash in CSS, so that has a compelling external reason to return an unbracketed list and shouldn't be considered a strong precedent.

  • map.keys() and map.values() return unbracketed, comma-separated lists from an input that isn't a list. However, maps can be interpreted as lists, in which case they are unbracketed and comma-separated so this only sort of counts as precedent.

  • selector.parse() returns an unbracketed, comma-separated list as well. However, this is specifically because we want its string representation to match the original selector, so this definitely doesn't count as precedent.

  • This leaves selector.simple-selectors() as the only built-in function that could just as easily return a bracketed list but still chooses not to do so. And even then, you could make the argument that it's that way to match the output of selector.parse().

Given that the precedent is not actually very strong for returning unbracketed lists, I think we should consider making all new functions that return lists default to returning bracketed lists unless there's a specific reason not to. Similarly to how we default to returning quoted rather than unquoted strings, more explicit delineators of values eases debugging by making it clearer to users what types they're working with.

To generalize this principle: Sass design should encourage code that's not directly emitting CSS to use more explicit syntax for constructs that CSS natively supports implicit syntax for.

@dvdherron What do you think?

nex3 added a commit that referenced this issue Oct 28, 2022
@dvdherron
Copy link
Contributor

@nex3 I think it makes perfect sense to have all functions that return lists default to returning bracketed lists. I'll make another draft of the proposal with the relevant changes. Will have a draft of tests ready soon too.

nex3 added a commit to sass/dart-sass that referenced this issue Dec 9, 2022
nex3 added a commit to sass/dart-sass that referenced this issue Dec 16, 2022
@nex3 nex3 self-assigned this Mar 2, 2023
nex3 added a commit that referenced this issue Mar 2, 2023
nex3 added a commit that referenced this issue Mar 3, 2023
@nex3 nex3 closed this as completed Mar 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed planned We would like to add this feature at some point
Projects
None yet
Development

No branches or pull requests

7 participants