People’s coding styles tend to evolve and change over time. One particular habit I seem to have picked up is to sprinkle the code liberally with numerous TODO
markers. I wish I could say it’s clear a sign of my ever-present dissatisfaction with imperfect solutions, but I suspect I simply adopted it while working at the current company :)
In any case, TODO
s (and FIXME
s &c.) are not actually something to scoff at, not too much at least. They are certainly better than the alternative, which is to commit shady code without explanation, rationale, or ideas for improvement. With TODO
s, you are at least making the technical debt apparent and explicit, thus increasing the likelihood you’ll eventually come around to pay it off.
When that glorious day comes, though, it would be nice to get a quick overview of the code’s shortcomings, so that you can decide what to work on first. Getting a list of TODO
s scattered over many files sounds like a great task for grep
, and a relatively simple one at that. But somehow, every time I wanted to do that I ended up spending some non-negligible time just working out the details of grep
‘s syntax and flags.
Thus, the logical course of action would be craft a simple script which would relieve me from doing that ever again. However, when I got around to writing it, I quickly realized the task it’s not actually that simple. In fact, it’s totally impossible to do it with just grep
, since it would require matching a regex against multiple subsequent lines of input . Standard GNU grep
doesn’t support that at all.
Well, at this point I should’ve probably taken the hint and realize it’s not exactly the best idea to use a shell script here. But hey, not everything has to be written in Python, right? :) So I rolled up my sleeves and after a fair amount of googling (and stack-overflowing), I unleashed a horror that I hereby present:
For best result, it is necessary to have pcregrep
installed, which is an extended version of grep
that supports the full spectrum of Perl-compatible regular expressions. On most popular Linux distros, pcregrep
is just one apt-get install
away.
Flask is one of the countless web frameworks available for Python. It’s probably my favorite, because it’s rather minimal, simple and easy to use. All the expected features are there, too, although they might not be as powerful as in some more advanced tools.
As an example, here’s how you define some simple request handler, bound to a parametrized URL pattern:
This handler responds to requests that go to /post/42 and similar paths. The syntax for those URL patterns is not very advanced: parameters can only be captured as path segments rather than arbitrary groups within a regular expression. (You can still use query string arguments, of course).
On the flip side, reversing the URL – building it from handler name and parameters – is always possible. There is a url_for
function which does just that. It can be used both from Python code and, perhaps more usefully, from HTML (Jinja) templates:
Parameters can have types, too. We’ve seen, for example, that post_id
was defined as int
in the URL pattern for blogpost
handler. These types are checked during the actual routing of HTTP requests, but also by the url_for
function:
Most of the time, this little bit of “static typing” is a nice feature. However, there are some cases where this behavior of url_for
is a bit too strict. Anytime we don’t intend to invoke the resulting URL directly, we might want a little more flexibility.
Biggest case-in-point are various client-side templates, used by JavaScript code to update small pieces of HTML without reloading the whole page. If you, for example, wanted to rewrite the template above to use Underscore templates, you would still want url_for
to format the blogpost
URL pattern:
Assuming you don’t feel dizzy from seeing two templating languages at once, you will obviously notice that '< %= post.id %>'
is not a valid int
value. But it’s a correct value for post_id
parameter, because the resulting URL (/post/< %= post.id %>
) would not be used immediately. Instead, it would be just sent to the browser, where some JS code would pick it up and replace the Underscore placeholder with an actual ID.
Unfortunately, bypassing the default strictness of url_for
is not exactly easy.
Looks like using Linux is really bound to slowly – but steadily – improve your commandline-fu. As evidence, today I wanted to share a little piece of shell acolyte’s magic that I managed to craft without very big trouble. It’s about counting lines in files – code lines in code files, to be specific.
For a single file, getting the number of text rows is very simple:
Although the name wc
comes from “word count”, the -l
switch changes its mode of operation into counting rows. The flexibility of this little program doesn’t end here; for example, it can also accept piped input (as stdin):
as well as multiple files:
or even wildcards, such as wc -l *.file
. With these we could rather easily count the number of lines of code in our project:
Unfortunately, the exact interpretation of **/*
wildcard seems to vary between shells. In zsh it works as shown above, but in bash I had it omit files from current directory. While it might make some sense here (as it would give a total without setup script and tests), I’m sure it won’t be the case all projects.
And so we need something smarter.
Napotykając problem, niektórzy ludzie myślą: “Użyję wyrażeń regularnych!”
W rezultacie mają dwa problemy.Jamie Zawinski @ alt.religion.emacs
Ten słynny cytat jest, według mnie, lekkim niedoszacowaniem. Decydując się na skorzystanie z wyrażeń regularnych, z miejsca dostajemy bowiem dwa problemy z nimi samymi :) Pierwszych z nich jest sama składnia, która dla nieprzyzwyczajonego oka jest cokolwiek nietrywialna. To głównie ona jest wskazywana jako główna trudność w sprawnym i efektywnym używaniu regexów.
Dzisiaj jednak chciałem zwrócić uwagę na ten drugi, rzadziej zauważany problem. Otóż samo wyrażenie to nie wszystko, trzeba je jeszcze odpowiednio użyć w naszym kodzie. I tutaj mogą zacząć się schody, bo w różnych językach programowania sprawa ta wygląda często odmiennie. Na szczęście jest da się tu też wskazać podobieństwa i spróbować dokonać uogólnienia.
Podstawowym elementem interfejsu programistycznego do wyrażeń regularnych jest zwykle obiekt wzorca (pattern), czyli samego wyrażenia. Zawiera on jego postać skompilowaną, którym jest mniej lub bardziej skomplikowana (w zależności od składni) konstrukcja przypominająca automat stanów. Zbudowanie tej wewnętrznej reprezentacji jest konieczne, aby przeprowadzić jakąkolwiek operację (np. wyszukiwania czy dopasowania). Jeśli więc planujemy skorzystać z jednego wyrażenia w celu przetworzenia większej liczby tekstów, dobrze jest posługiwać się gotowym, skompilowanym obiektem.
Ten ogólny opis dobrze przenosi się na rzeczywiste języki programowania, w których możemy znaleźć takie klasy jak:
boost::regex
/wregex
w C++ z biblioteką Boost.RegexSystem.Text.RegularExpressions.Regex
w C#/.NETjava.util.regex.Pattern
w Javiere.RegexObject
w PythonieTekstową postać wyrażeń regularnych podajemy zwykle do konstruktorów wyżej wymienionych klas, względnie używamy jakichś statycznych lub globalnych funkcji z odpowiednich pakietów. Przy okazji warto też wspomnieć o problemie escape‘owania znaków specjalnych w wyrażeniach, który w mocno niepożądany sposób interferuje z analogicznym mechanizmem w samych językach programowania. Ponieważ w obu przypadkach używa się do tego znaku backslash (\), w wyrażeniach wpisywanych do kodu należy go podwoić:
W C# i Pythonie można tego uniknąć, stosując mechanizm surowych napisów (raw strings). Programiści C++ i Javy nie mają niestety tego szczęścia ;)
Gdy mamy już obiekt skompilowanego wyrażenia, możemy użyć go do jakichś pożytecznych celów. Jeśli są one proste – jak choćby sprawdzenie, czy jakiś ciąg ma formę określoną regeksem – to możemy zazwyczaj obejść się jednym prostym wywołaniem:
Bardziej skomplikowane jest wyszukiwanie wszystkich dopasowań wyrażenia w danym tekście, zwłaszcza jeśli przy okazji chcemy dobrać się do fragmentów znalezionych podciągów. Tutaj zaczynają objawiać się pewne różnice między poszczególnymi językami, ale ogólny schemat pozostaje ten sam. Opiera się on na skonstruowaniu odpowiedniej pętli przelatującej po kolejnych dopasowaniach i operowaniu na obiekcie, który takie dopasowanie (match) reprezentuje:
boost::match_results
w C++ z Boost.RegexSystem.Text.RegularExpressions.Match
w C#/.NETjava.util.regex.Matcher
w Javie (klasa ta kontroluje też iterację po kolejnych dopasowaniach)re.MatchObject
w PythonieObiekt dopasowania udostępnia zazwyczaj kilka przydatnych metod i właściwości, jak choćby zakres indeksów znalezionego ciągu. Są też tam fragmenty, które “wpadły” w podgrupy strukturalne (subsequences, subgroups, capture groups, itp.), na które nasze wyrażenie było podzielone. Chodzi tu o jego części ujęte w nawiasy okrągłe; to, jakie konkretne znaki zostały dopasowane do każdego z nich zostaje bowiem zapamiętane w obiekcie match.
Między innymi dzięki temu faktowi możliwe jest określanie bardzo ogólnych wzorców do wyszukania w tekście, a następnie przeglądanie tego, co udało nam się znaleźć i podejmowanie decyzji na podstawie jakichś znaczących elementów dopasowania. W ten sposób możemy przetwarzać teksty o stopniu skomplikowania znacznie przekraczającym to, co w teorii daje się opisać wyrażeniami regularnymi. Żeby nie pozostać gołosłownym, zaprezentuję na przykład prosty sposób na konwersję tekstu zawierającego często spotykane na forach znaczniki BBCode (takie jak [url]
czy [img]
) na jego odpowiednik HTML-owy, gotowy do wyświetlenia.
Najważniejsza jego część to wykonywane w funkcji _bbtag_to_html
przetwarzanie obiektu typu re.MatchObject
zawierającego dane o znalezionym, pojedynczym tagu. Pobieramy tam jego nazwę i zawartość, które zostały dopasowane jako odpowiednio: pierwsza i druga podgrupa wyrażenia. Samo przeglądanie tekstu w poszukiwaniu tagów i ich zastępowanie jest wykonywane wbudowaną funkcją re.RegexObject.sub
, która ukrywa szczegóły wspomnianej wcześniej pętli.
Mam nadzieję, że powyższy przykład dowodzi, że możliwe jest zastosowanie wyrażeń regularnych bez znaczącego wzrostu liczby problemów do rozwiązania :) Jakkolwiek dziwnie to zabrzmi, korzystanie z regeksów może bowiem niekiedy przyczynić się do wzrostu czytelności wynikowego kodu, przynajmniej dla bardziej doświadczonych programistów. Jest tak ze względu na duże podobieństwa nie tylko między różnymi wariantami składni wyrażeń, ale też między bibliotekami do ich obsługi w różnych językach programowania, które to dzisiaj starałem się przedstawić.
Przy wyszukiwaniu czegoś w kodzie czasami przydają się wyrażenia regularne. Pozwalają one na znajdowanie nie tyle dokładnie określonych ciągów znaków, ale ciągów pasujących do określonego wzorca. Przy odrobinie wprawy można na przykład wyszukać wszystkie deklaracje zmiennych danego typu, wszystkie wywołania funkcji przeciążonej wyłącznie w wersji z trzema parametrami, i tak dalej.
Zwykle takich rzeczy szukamy jednak nie dla samego znajdowania, lecz po to, aby je potem zmodyfikować. Czasem można to zrobić ręcznie, ale jeśli oznacza to konieczność wykonania tych samych poprawek kilkanaście czy kilkadziesiąt razy, to szybko może nam to się znudzić. Pamiętajmy zresztą, że każdy programista dysponuje tylko skończonym czasem przeznaczonym na kodowanie :)
Wtedy może przydać się zastosowanie wyrażeń regularnych nie tylko do wyszukiwania (find), ale też do zastępowania (replace). Każde porządne IDE powinno bowiem oferować możliwość dopasowywania podwyrażeń (subexpressions lub tagged expressions) w znajdowanym ciągu. Nie inaczej jest w Visual Studio.
Załóżmy przykładowo, że piszemy w C++ i mamy klasę działającą jak abstrakcyjny interfejs (wszystkie metody czysto wirtualne) i chcemy go zaimplementować. Kopiujemy więc całą definicję klasy i chcielibyśmy teraz zmienić deklaracje jej metod: ze wszystkich usunąć słówko ‘
virtual
‘ i frazę ‘= 0
‘.
Jak to zrobić? Dość łatwo skonstruować wyrażenie regularne, które wyszuka nam wszystkie deklaracje:
Co jednak z polem Replace with? Tam chcielibyśmy wstawić wszystkie te znaki, które dopasują się do wyrażenia .*
. W tym celu powinniśmy wpierw zmienić tę frazę na tagged expression, otaczając ją nawiasami klamrowymi:
Teraz stało się ono podwyrażeniem i możemy odwołać się do dopasowanych do niego znaków poprzez sekwencję \1
. Zatem jako wyrażenie docelowe wstawimy po prostu:
Jak nietrudno się domyślić, podwyrażeń może być więcej; możemy wtedy odwoływać się do nich za pomocą kolejnych numerków: \2
, \3
, itd. (są one numerowane oczywiście od lewej). Dodatkowo symbol \0
odpowiada za cały znaleziony ciąg.
Widać więc, że przy użyciu tego mechanizmu można automatycznie dokonywać zmian, które bez niego zajęłyby nam dużo czasu i były dość żmudne.
Myślę, że większość programistów miała przynajmniej przelotny kontakt z wyrażeniami regularnymi. Zwykle używa się do bardziej zaawansowanego wyszukiwania podciągów w tekście – nie określonych sekwencji liter, ale fragmentów określonych raczej pewnymi warunkami. Innym ich zastosowaniem jest też sprawdzanie, czy podany łańcuch pasuje do pewnego określonego formatu. Przy pomocy poniższego wyrażenia:
można na przykład sprawdzić poprawność adresu e-mail (czy zawiera on znaczek @, czy nazwa domeny jest przynajmniej formalnie poprawna, itd.). Nietrudno zauważyć jednak, że sama postać wyrażenia jest dość odstręczająca, a poza tym sporo elementów się w nim powtarza – choćby sekwencja dopasowująca pojedynczy znak alfanumeryczny. Poza tym dokładna składnia wyrażeń w danej bibliotece może być niekiedy specyficzna, chociaż podstawy (czyli np. elementy pokazane powyżej) powinny być w zasadzie wszędzie takie same.
Mimo to czasem warto użyć tego narzędzia również w bardziej zaawansowanym celu: parsowania ciągów znaków w celu wydzielenia i odczytania z nich określonych fragmentów. Zapewne nie da się w ten sposób przetworzyć na przykład dokumentu XML czy innego skomplikowanego formatu, lecz w prostszych przypadkach może się to okazać szybsze i wygodniejsze. Alternatywą jest oczywiście samodzielne napisanie parsera, tokenizera czy innego tego typu obleśnego automatu stanów :)