Commenter

Commenter jest programem do zakomentowywania i odkomentowywania sekcji plików tekstowych, w zależności od ustawionych słów kluczowych (definicji). Lista definicji jest podawana przy uruchomieniu programu.

Commenter powstał na moje potrzeby jako narzędzie ułatwiające zarządzanie jednym repozytorium konfiguracji dzielonym między kilka komputerów1. Program działa podobnie do preprocesora analizującego dyrektywy #ifdef, z tą różnicą, że Commenter po przefiltrowaniu pliku pozostawia zarówno dyrektywy, jak i zakomentowany (lub odkomentowany) tekst.

Przykładowo, weźmy następujący plik:

$ cat infile
zażółć gęślą jaźń
# comm:begin foo || bar
# foo or bar
# comm:end

Po przefiltrowaniu go przez commentera uruchomionego z opcją -D foo lub -D bar, otrzymamy wynik w postaci pliku z odkomentowaną trzecią linijką:

$ commenter <infile -D foo
zażółć gęślą jaźń
# comm:begin foo || bar
foo or bar
# comm:end

I na odwrót, gdyby powyższe ponownie przepuścić przez commentera, ale tym razem z ustalonym dowolnie innym zbiorem definicji, znów otrzymamy tekst z zakomentowanym blokiem “foo or bar”:

$ commenter <infile -D foo | commenter -D blaaah
zażółć gęślą jaźń
# comm:begin foo || bar
# foo or bar
# comm:end

Bloki można zagnieżdżać, obsługiwane są również podstawowe wyrażenia boolowskie (zbudowane z następujących działań logicznych: and, or, not zapisanych przy pomocy operatorów znanych z języka C; istnieje również możliwość łączenia działań przy pomocy nawiasów). Commenter, na podstawie tego jak wygląda nagłówek bloku, sam określa w jaki sposób tekst powinien zostać zakomentowany. Zachowuje również wcięcia.

git-attributes

Commenter powstał z myślą o automatycznym użyciu przy każdej modyfikacji, checkoucie i commicie plików zebranych w repozytorium Gita. W tym celu używam filtrów gitowych, które automatycznie odkomentowują wszystkie sekcje commentera gdy plik jest wysyłany do zdalnego repozytorium i zakomentowują odpowiednie sekcje w lokalnych plikach na dysku.

Aby to osiągnąć, w katogu ze sklonowanym repozytorium należy wykonać dwie komendy: pierwsza ustawi filtr gitowy generujący “czysty” plik w postaci takiej, jaka zostanie wysłana do repozytorium zdalnego; druga ustawi filtr modyfikujący pliki na potrzeby lokalne. Opcja -u (lub --force-uncomment w swej długiej formie) użyta w pierwszej komendzie wymusza odkomentowanie wszystkich sekcji w danym pliku - jest zatem perfekcyjnym kandydatem dla filtra czyszczącego2.

$ git config filter.commenter.clean "commenter -u"
$ git config filter.commenter.smudge "$commenter -D foo -D bar -D baz"

W mojej rzeczywistej konfiguracji lista definicji generowana jest automatycznie dla każdego komputera: na podstawje jego hostname’a, nazwy dystrybucji itp:

function release {
    lsb_release -a 2>/dev/null  | grep Codename | sed 's/Codename://' | xargs
}

defines="-D $(release)"
defines="$defines -D $(hostname)"
printf "commenter %s\n" "$defines"

Oprócz ustawienia filtrów, musimy również utworzyć plik .gitattributes, w którym określimy, które pliki będą filtrowane. Możemy na przykład wybrać wszystkie pliki w repozytorium, albo tylko niektóre z nich, na przykład znajdujące się w konkretnych katalogach:

# filtrowanie wszystkich plików
* filter=commenter
.* filter=commenter

# filtrowanie plików w niektórych katalogach
dotfiles/**/* filter=commenter

W przypadku gdy dodamy filtry do repozytorium, które istniało już wcześniej, prawdopodobnie będziemy musieli zmusić gita do odświeżenia plików lokalnych. Można to zrobić w następujący sposób (UWAGA: poniższe komendy usuną wszystkie niescommitowane zmiany wewnątrz repozytorium)

git_dir="$(git rev-parse --show-toplevel)"
rm "$git_dir/.git/index"
git checkout HEAD -- "$git_dir"

Sprawy formalne

Commenter jest Wolnym Oprogramowaniem udostępnianym na warunkach licencji GPL w wersji 3. lub (wedle uznania) dowolnej nowszej.

Do napisania Commentera zainspirował mnie inny program tego typu, mir.qualia, w którym brakowało mi możliwości obsługi wyrażeń boolowskich. Polecam zapoznanie się z nim oraz z jego kodem źródłowym, gdyż autor w bardzo interesujący i czysty sposób używa - czy raczej “emuluje” - programowanie funkcyjne w Pythonie.


  1. Takie rozwiązanie ułatwia, wbrew pozorom, zarządzanie wspólnymi częściami konfiguracji. Alternatywą jest np. stworzenie brancha dla każdego komputera, co jednak powoduje konieczność wielokrotnego merge’owania zmian w sekcjach współdzielonych oraz rozwiązywania konfliktów, które z każdą zmianą są coraz mniej trywialne. 

  2. Po prawdzie, to została zaprojektowana dokładnie z myślą o takim jej użyciu.