Mixin Passing Variables to Each/For - sass

Instead of doing something like this (which is obviously inefficient):
#mixin padding($top, $right, $bottom, $left) {
$top: $top * $spacer;
$right: $right * $spacer;
$bottom: $bottom * $spacer;
$left: $left * $spacer;
$output: $top $right $bottom $left;
padding: $output;
}
Can I do something similar to this?
#mixin padding($top:"", $right:"", $bottom:"", $left:"") {
$params: $top, $right, $bottom, $left;
$output: "";
#each $var in $params {
$var: $var * $spacer;
$output: $output + $var;
}
padding: $output;
}

Yes you can =)
In this case you can also skip the first step and use $params... as the parameter (variable argument list), and then you can have padding with 1, 2, 3, or 4 values.
#mixin padding($params...) {
$output: ();
#each $var in $params {
$var: $var * $spacer;
$output: join( $output, $var );
}
padding: $output;
}
If you use the join function instead of string concatenation you won't have troubles separating the values with spaces when printing out (a list gets automatically compiled to CSS as space separated elements).
DEMO
And if you want to make sure to limit the params to 4 max, you can do something like this instead of the #each loop:
$n: length($params);
#for $i from 1 through if( $n < 4, $n , 4) {
$var: nth($params,$i) * $spacer;
$output: join( $output, $var );
}
DEMO
However, if you want to stick with strings and concatenation instead of lists, you would need to use an additional space in the concatenation inside the loop (e.g. $output + " " + $var) and then return the $output with string interpolation #{$output} or using unquote($output). But you would end up with an extra space attached to the string ... and would need to apply some additional logic in case you would want to get rid of it.

Related

How to solve string splitting issue having pharentesis on the output when working with str-split and Sass

I have been messing around with sass trying to understand how directives and at-rules work in this environment.
Very simple question:
I am trying to transform a string into a list of strings.
e.g: a, b, c into "a", "b", "c".
While trying to write this simple #function I have been seeing this odd behavior:
#function split($string, $search) {
$index: str-index($string, $search);
#while $index {
#return str-slice($string, 1, $index - 1), split(str-slice($string, $index + str-length($search)), $search);
}
#return $string;
}
#debug split("b c b", " ") //test.scss:130 DEBUG: "b", ("c", "b")
I would like to see the desired output without the parenthesis: "b", "c", "b".
How can I achieve this and where do these parenthesis come from after all?
Interestingly I ended up understanding that somehow I am getting nested lists as a output with this function so I have added the function join() at the return statement and now the result is a flattened list of strings.
#function split($string, $search) {
$index: str-index($string, $search);
#while $index {
#return join(str-slice($string, 1, $index - 1), split(str-slice($string, $index + str-length($search)), $search));
}
#return $string
}
#debug split("a b c b", " "); //test.scss:130 DEBUG: "a" "b" "c" "b"
The question I leave here is: why I am getting a nested list as an output on my question?
Please note that I am on the following environment: vscode using dart-sass in reactjs.

Parse a CSS file to to get individual declarations of selectors containing a right or left word

I need to parse a CSS file to to get individual selectors that contain a right or left word in their declarations(block). And add the selectors and their corresponding declarations(block) to an Array or Hash. For example:
.selector-one { /* This selector and its declaration will be added */
.
.
float: right;
.
.
}
#selector-two { /* This selector and its declaration will be added */
.
.
margin-left: 20%;
.
.
}
I'm trying to write it using the scan method, This way:
content.scan(/.*?\{.*?(right|left).*?\}/)
Or
content.scan(/[^}]\{[^}](right|left)[^}]\}/)
But non of them work.
I should mention that:
It dosn't matter if the name of the selector contains the word left or right, We just need to check the blocks.
The name of the selectors may start with anything other than { or }
Selectors can be grouped, So we could have something like this:
.
h1, h2, p {
text-align: right;
color: red;
}
I don't know ruby but the regexp should be :
[{]([^}]*(right|left)[^}]*)[}]
With ungreedy,insensitive and global flags
Based on your sample, and if they aren't nested.
# \.(selector-[^{}]*?)\s*\{([^{}]*:[ ]*(?:right|left)[ ]*;[^{}]*)\}
\.
( selector- [^{}]*? ) # (1), Selector
\s*
\{
( # (2 start), Block
[^{}]*
: [ ]*
(?: right | left )
[ ]* ;
[^{}]*
) # (2 end)
\}
Output:
** Grp 0 - ( pos 0 , len 105 )
.selector-one { /* This block will be added to the array */
.
.
float: right;
.
.
}
** Grp 1 - ( pos 1 , len 12 )
selector-one
** Grp 2 - ( pos 15 , len 89 )
/* This block will be added to the array */
.
.
float: right;
.
.
----------------------
** Grp 0 - ( pos 196 , len 106 )
.selector-three { /* This block will be added to the array */
.
.
float: left;
.
.
}
** Grp 1 - ( pos 197 , len 14 )
selector-three
** Grp 2 - ( pos 213 , len 88 )
/* This block will be added to the array */
.
.
float: left;
.
.
Ruby's slice_before is handy for this:
blocks = DATA.each_line
.slice_before(/^\./)
.map(&:join)
.select{ |block| block[/\b(?:left|right)\b/] }
blocks
# => [".selector-one { /* This block will be added to the array */\n .\n .\nfloat: right;\n .\n .\n}\n\n",
# ".selector-three { /* This block will be added to the array */\n .\n .\nfloat: left;\n .\n .\n}\n"]
__END__
.selector-one { /* This block will be added to the array */
.
.
float: right;
.
.
}
.selector-two { /* This block wont be added to the array */
.
.
.
}
.selector-three { /* This block will be added to the array */
.
.
float: left;
.
.
}
each_line iterates over the lines in the file.
slice_before looks through the resulting array, and creates sub-arrays when the regular expression /^\./ matches a line that starts with ..
map(&:join) converts the contents of each sub-array back into a text string.
select looks inside each string and if /\b(?:left|right)\b/ matches the word "left" or "right", then the string is passed on.
It's important to use a pattern like /\b(?:left|right)\b/, because the string being searched for could be embedded in a longer string like 'leftover' or 'bright' and you don't want to get a false-positive.
You might not have seen __END__ and DATA before, but they're handy for test code like this. __END__ marks the end of the script, and anything after it can be treated as a pseudo data file, which is accessible via DATA. So consider the code to be reading from a file.
You could also use a CSS parser like Crass and scan the parse tree.
Spitballing code now...
tree = Crass.parse(css)
nodes_with_right_or_left = tree.select do |node|
node[:children] and node[:children].detect do |child|
child[:value] == "right" or child[:value] == "left"
end
end
YMMV :-)
Thanks to all the people answered to my question. Unfortunately non of them solve the problem. Here is how I implemented it:
input.scan(/[^{}]*\{[^}]*?(?:\Wright\W|\Wleft\W)[^}]*\}/)
The key was that the parentheses in scan method make a capture group. So we need to turn it into a non-capturing group, using ?:

Inserting a hyphen when string changes from alpha to numeric

Given a random alphanumeric string (A-Z0-9) between 1 to 10 characters long, I'd like to insert a hyphen when it changes from alpha to numeric, or numeric to alpha.
I have something that works now, but I'm sure it performs as awful as it looks. I know there's a better way to handle this, but someone in the office made weak coffee this morning, or at least that's the excuse I'm going with. ;)
I have to do this ~15 million times, so the faster, the better.
Code snip:
my #letters = split //, $string;
my $type;
foreach my $letter ( #letters ) {
if (! $type) {
if ($letter =~ /^[A-Z]$/) {
$type = 'a'
}
else {
$type = 'd'
}
$string = $letter;
next;
}
else {
if ($type eq 'a') {
if ($letter =~ /^[0-9]$/) {
$string .= '-' . $letter;
$type = 'd';
next;
}
else {
$sring .= $letter;
}
}
else {
if ($letter =~ /^[A-Z]$/) {
etc, etc.
Ugh, it hurts just looking at that.
This should work:
$string =~ s/([A-Z])([0-9])/$1-$2/g;
$string =~ s/([0-9])([A-Z])/$1-$2/g;
Add the /i modifier if you want to to be case-insensitive.
Probably faster (since it avoids captures), but requires 5.10:
$string =~ s/[A-Z]\K(?=[0-9])/-/g;
$string =~ s/[0-9]\K(?=[A-Z])/-/g;

how to find substring between given characters in smarty?

I have following code in php to which I am looking in smarty
function get_string_between($string, $start, $end){
$string = " ".$string;
$ini = strpos($string,$start);
if ($ini == 0) return "";
$ini += strlen($start);
$len = strpos($string,$end,$ini) - $ini;
return substr($string,$ini,$len);
}
$fullstring = '[1]wholesale';
$parsed = get_string_between($fullstring, "[", "]");
$filename = substr(strrchr($fullstring, "]"), 1);
echo $parsed.'/'.$filename;
//Output 1/Wholesale
I want this output 1/Wholesalein two different variables (as done in php $parsed,$filename) in Smarty (Let say string is [1]wholesale).
You could achieve it this way:
{assign var="var" value="[1]wholesale"}
{$var|substr:($var|strpos:'['+1):($var|strpos:']' - $var|strpos:'['-1)}/{$var|substr:($var|strpos:']'+1)}
Output for this will be:
1/wholesale
However as you see it's rather quite illegible. You could of course move it to some function/modifier but as always I need to tell, the best option is to prepare data in PHP and then simply display it in Smarty not using advanced transformations.

perl6/rakudo: dereferencing-question

#!perl6
use v6;
my $list = 'a' .. 'f';
sub my_function( $list ) {
for ^$list.elems -> $e {
$list[$e].say;
}
}
my_function( $list );
First I tried this in perl5-style, but it didn't work:
for #$list -> $e {
$e.say;
}
# Non-declarative sigil is missing its name at line ..., near "#$list -> "
How could I do this in perl6?
You don't dereference variables like this in Perl 6. Just use for $list
But that proably won't do what you want to do. 'a'..'f' doesn't construct a list in Perl 6, but rather a built-in data type called Range. You can check that with say $list.WHAT. To turn it into a list and iterate over each element, you'd use for $list.list
These should work:
.say for #( $list );
.say for $list.list;
.say for $list.flat;
Since $listis a scalar, for $list will just iterate over a single item.
Now, Rakudo 2015.02 works it ok.
You'd better use # as twigil of variable name as array.
Perl 6 is context sensitive language, so if you want array act as 'true array', you'd better give it a suitable name.
#!perl6
use v6;
my #list = 'a' .. 'f';
for #list -> $e { $e.say };

Resources