Lately I am working on an old code base and that means working with the old CSS. One of the techniques to use when refactoring CSS is to replace colors with variables. But first you need to find and catalogue all the colors in the project.
Extracting colors from a project
Let's find all colors used in a project. Regular expression I came up with finds
a pound sign
# followed by 3 or 6 numbers or letters in the range from
and stops at the word boundary.
I am using The Silver Searcher. The output is too human-readable so let's throw in some options to dumb it down:
Thats a quite a list of options but I like
ag. Now let's pipe the output
sort --uniq -f to remove the duplicates ignoring the case and count the lines with
Wow that's a large number. The problem is, right now
are treated as different colors. But I guess this is something we can fix in Ruby.
Here is what we are going to do:
With the given list of colors
- Convert colors to full hex
- Make the list unique
- Order by hue
- Print the palette so we can see what is going on
After getting some inspiration from Ruby Midwest 2013 Functional Principles for OO Development talk by Jessica Kerr I have decided to go all functional here.
Enumerable#map is the single most important method here. Just keep things mappable and you're fine.
If you are not sure about it, wrap your arguments in
Array(), use asterisk
Just make sure you have Enumerator on input.
Most Ruby developers know about the pretzel symbol-to-proc operator that converts a given symbol into a proc that calls the same method on the given object.
& here converts
:upcase to a Proc and serves as a syntax sugar.
The 'unpacked' version would look like this:
But I don't want to chain my lambdas in the ugly way, too bad there is no short syntax
for lambdas in Ruby. Or is it? Turns out there is. Given the
Even after replacing
.() it still doesn't look nice:
But there is a special syntax for lambdas that makes it all sweet:
Now that's better, and to me that was the only part missing.
Let's store filtered
ag output to the ruby file and start messing around:
Another less-known feature in Ruby is that you can treat the rest of file as data
if you add
__END__ to your program. Here is an excerpt from the Ruby Docs:
We can simply use the reset of the executable file as a
DATA enumerator. Cool, eh?
Let's get to business
For this project I will use Paleta gem.
We have the colors loaded into the
DATA enumerator. The only thing that's left is to
create some lambdas accoding to the plan.
1. Convert colors to full hex
To do that we are going to feed the color to Paleta and get hex value back. The only thing we need to add
chomp to get rid of the newlines in strings:
2. Make the list unique
.uniq and finally we can see the real number of unique colors in the project.
What?! Okay, damn it, only a few colors were not unique. White and black I guess.
3. Order by hue
That might sound tricky but Paleta comes to the rescue.
Paleta provides a
.hue method that we could use to sort colors. But since they are
strings we need to convert them back to objects.
That sounds fishy because it is. Let's use this opportunity and refactor the code slightly.
.hex from the
to_full_hex method and rename it. This refactoring
gives us even more freedom:
4. Print the palette so we can see what is going on
Now let's devise a human-viewable palette so we could judge the colors.
Make some nice-looking colored
<div>s or something.
And pipe it to a new file, because at the moment of writing this Paleta generated some warning about Rmagic.
Okay that is nice but let's improve it a bit. First of all lets get rid of the static DATA
so we could use it right from the command line. That allows us to get right of
ARGF and remove the
Then lets open the resulting file right away.
Now you can pipe the output from
ag and even sample a single css file if needed. Don't forget to
chmod +x colors.rb
Here is the final version you can use for processing your files or projects:
It makes sense to sort palettes by hue or lightness or saturation, this is simply
a matter of changing a method name in
.map(&:hue). Maybe some command-line switches
would be appropriate here as well but this was enough for my needs and I just
wanted to document the approach.