# Analizator statyczny dla jednego z projektów. Sprawdza kod pod kątem
# jakości począwszy od prostych sprawdzeń jak np. maksymalna długości
# linii na złożonych jak np. prawidłowo otwierane/zamykane tagi
# "<tr>", "<td>", oznaczenie kodowania znaków w plikach źródłowych
# *.py.
# 
# Realizuje też mnóstwo testów specjalnie dla tego projektu np.
# sprawdzanie wykorzystania zapytań, konwencje nazewnicze w systemie,
# prawidłowe renderowanie kwot w plikach styli.
# 
# Pilnowanie takich drobnych rzeczy ręcznie było by bardzo uciążliwe,
# dzięki odpowiednim konwencjom nazewniczym i prostym skryptom opartym
# na wyrażeniach regularnych można egzekwować te reguły przed każym
# uruchomieniem testów (co oszczędziło nam już bardzo wielu błędów).

#!/bin/sh

HTML="view.html dokumentyFiskalne.html prowizje.html klienci.html
zalaczniki.html zapotrzebowania.html prezentacje.html zamowienia.html
operacjeKasowe.html produkty.html crm.html zadania.html waluty.html
kontrahent.html"

SQL="queries.sql klienci.sql dokumentyFiskalne.sql prowizje.sql
zapotrzebowania.sql zamowienia.sql operacjeKasowe.sql produkty.sql
crm.sql zadania.sql prezentacje.sql waluty.sql kontrahent.sql"

######################################################################
awk '

# wychwycenie (zapewne) błędnej konstrukcji NOT LIKE - nieintuicyjny
# priorytet operatorów w SQL
/NOT [a-zA-Z]* LIKE/ {
	print FILENAME ":" FNR ": dodaj nawiasy !"
	err = 1
}

# Wszystkie zapytania operujące na tabeli zamówienie powinny sprawdzać
# obecność flagi "ukryjNiewidoczne"
/^\[/ {
	if(byloUzycieZamowienie && !byloUzycieWidoczne)
	{
		print FILENAME ":" FNR ": brak if:ukryjNiewidoczne"
		err = 1
	}
	byloUzycieZamowienie = 0
	byloUzycieWidoczne = 0
}
!/^\[/ && /Zamowienie/ {
	byloUzycieZamowienie = 1
}
!/^\[/ && /Prowizja/ {
	byloUzycieZamowienie = 0
}
/{if:ukryjNiewidoczne} AND (Z.)?widoczne {endif}/ {
	byloUzycieWidoczne = 1
}
/INSERT|UPDATE/ {
	byloUzycieWidoczne = 1
}
END {
	if(byloUzycieZamowienie && !byloUzycieWidoczne)
	{
		print FILENAME ":" FNR ": brak if:ukryjNiewidoczne"
		err = 1
	}
	exit err
}
' $SQL || exit 1

######################################################################
DESCR='all modules with frm... are imported in dispatch.py'

F1=/tmp/$USER.1
F2=/tmp/$USER.2

grep -l 'class frm' *.py | sed 's/\.py//' | sort > $F1
sed -n '/from [a-zA-Z_]* import/ {
	s/.*from \([a-zA-Z_]*\) import.*/\1/
	p
}' dispatch.py | sort > $F2
diff $F1 $F2 || { echo $DESCR; exit 1; }

######################################################################

awk '

# jeśli przy testach wywołujemy mktemp(), to należy plik tymaczsowy
# skasować poprzez os.unlink()
#
/mktemp/ {
	byloMktemp = 1
}
/os.unlink/ {
	byloMktemp = 0
}
/^[^\t]/ {
	if(byloMktemp) {
		print FILENAME ":" FNR ": mktemp() without os.unlink()"
		byloMktemp = 0
		err = 1
	}
}

# forsowanie konwencji nazewniczej dla szablonów HTML (nazwy zaczynają
# się od przedrostka "htm"
#
/handleTag\("/ && !/handleTag\("\/?htm/ {
	print FILENAME ":" FNR ": handleTag() first arg should have htm prefix"
	err = 1
}

# inne konwencje stylistyczne w kodzie źródłowym
#
/def [a-zA-Z_0-9]* \(/ {
	print FILENAME ":" FNR ": usun spację pomiędzy "\
		"nazwą funkcji a nawiasem"
	err = 1
}
/class [a-zA-Z_0-9]* \(/ {
	print FILENAME ":" FNR ": usun spację pomiędzy "\
		"nazwą klasy a nawiasem"
	err = 1
}

# każda formatka musi mieć explicite podany atrybut group określający
# poziom uprawnień potrzebny do jej używania
#
/^class frm/ {
	if(byloFrm != "")
	{
		print FILENAME ":" FNR ": brak group dla " byloFrm
		err = 1
	}
	byloFrm = $0
	next
}
/group = / {
	byloFrm = ""
	next
}

# nazwy testów są unikalne - zabezpieczamy się przed nadpisaniem
# funkcji testującej w tym samym pliku
#
/def test/ {
	match($0, /test[A-Za-z0-9_]*/)
	sName = substr($0, RSTART, RLENGTH)
	if(sName in testNames) {
		print FILENAME ":" FNR ": powtórzona nazwa testu: " sName
		err = 1
	}
	testNames[sName] = 1
}

# inne konwencje dotyczące stylu kodu źródłowego
#
/def.*:$/ || /class .*:$/ {
	needEmptyLine = 1
	next
}
/^[\t ]*$/ {
	needEmptyLine = 0
}
needEmptyLine {
	print FILENAME ":" FNR ": dodaj pustą linię tu (styl)"
	err = 1
	needEmptyLine = 0
}
END {
	if(byloFrm != "")
	{
		print FILENAME ":" FNR ": brak group dla " byloFrm
		err = 1
	}
	exit(err)
}
' *.py || exit 1

######################################################################
awk '

# obowiązkowe kodowanie urlencode() dla argumentów przekazanych
# poprzez żądanie HTTP typu GET
#
/={/ && !/={[^:]*:url}/ {
	print FILENAME ":" FNR ": w URL musi być modyfikator :url"
	err = 1
}

# nowa konwencja podawania nazwy skryptu zastępuje starą - tu
# sprawdzam czy stara konwencja nie występuje
#
/frm\.cgi/ {
	print FILENAME ":" FNR ": use {_cgi} instead"
	err = 1
}

# proste sprawdzenie na kodzie HTML (tagi tabelek, list, formularzy)
#
function handleTag(sName, bOpen) {
	if(bOpen) {
		if(openTags[sName]) {
			print FILENAME ":" FNR ": double open " sName
			err = 1
		} else {
			openTags[sName] = 1
		}
	} else {
		if(openTags[sName]) {
			openTags[sName] = 0
		} else {
			print FILENAME ":" FNR ": double close " sName
			err = 1
		}
	}
}
function requireOpenTag(sName) {
	if(!openTags[sName]) {
		print FILENAME ":" FNR ": open of " sName " missing"
		err = 1
	}
}

/<table/ { handleTag("table", 1) }
/<tr/ {
	handleTag("tr", 1)
	requireOpenTag("table")
}
/<td/ {
	handleTag("td", 1)
	requireOpenTag("tr")
}
/<\/td/ { handleTag("td", 0) }
/<\/tr/ { handleTag("tr", 0) }
/<\/table/ { handleTag("table", 0) }

/<ul/ { handleTag("ul", 1) }
/<li/ { handleTag("li", 1) }
/<\/li/ { handleTag("li", 0) }
/<\/ul/ { handleTag("ul", 0) }

/<th/ {
	handleTag("th", 1)
	requireOpenTag("tr")
}
/<\/th/ { handleTag("th", 0) }

/<h1/ { handleTag("h1", 1) }
/<\/h1/ { handleTag("h1", 0) }

/<form/ { handleTag("form", 1) }
/<input/ {
	requireOpenTag("form")
}
/<\/form/ { handleTag("form", 0) }

/<h2/ { handleTag("h2", 2) }
/<\/h2/ { handleTag("h2", 0) }

# prawidłowe zapisywanie encji w strybutach HTML, tu forsuję
# (częściowo) zgodność z XHTML
#
/\?.*\&[^a][^m]/ {
	print FILENAME ":" FNR ": zmień & -> &amp;"
	err = 1
}

/<br\/>/ {
	print FILENAME ":" FNR ": zmień <br/> -> <br />"
	err = 1
}

# strony przeznaczone do drukowania powinny się otwierać w nowym oknie
# przeglądarki
#
/_printable=/ && ! /target="_blank"/ {
	print FILENAME ":" FNR ": _printable: dodaj: target=\"_blank\""
	err = 1
}

# sprawdzam, czy nie pozostawiono otwartych znaczników HTML
#
END {
	handleTag("tr", 1)
	handleTag("th", 1)
	handleTag("td", 1)
	handleTag("table", 1)
	handleTag("form", 1)
	exit(err)
}
' $HTML || exit 1

######################################################################
DESCR='cena, netto, suma, wartosc, rabat* has :money modifier'

# wszystkie zmienne typu wautowego powinny być renderowane wg.
# standardów polskich (z przecinkiem)
# W przypadku plików SQL jest to konwersja standardu polskiego na
# zapis z kropką
#
awk '
/{rabat[A-Za-z0-9_]*[^:}]}/ || \
/{cena[^:}]*}/ || \
/{brutto[^:}]*}/ || \
/{netto[^:}]*}/ || \
/{suma[^:}]*}/ || \
/{wplyw[^:}]*}/ || \
/{wyplyw[^:}]*}/ || \
/{wartosc[a-zA-Z0-9_]*}/ {
	print FILENAME ":" FNR ": " $0
	err = 1
}
END {
	exit(err)
}
' $HTML *.sql || { echo $DESCR; exit 1; }


######################################################################
DESCR='names of frm... names dont match in source and style files'

F1=/tmp/$USER.1
F2=/tmp/$USER.2

sed -n '
/_frm=/{
	s/^.*_frm=\([a-zA-Z0-9]*\).*$/\1/
	p
}
/name="_frm" value="/{
	s/^.*value="\([^"]*\)".*$/\1/
	p
}
' $HTML | sort | uniq > $F1

sed -n '
/^class frm/{
	s/^.*\(frm[a-zA-Z0-9]\+\)(.*$/\1/
	p
}
' *.py | sort | uniq > $F2

diff $F1 $F2 || { echo $DESCR; exit 1; }

######################################################################
DESCR='names of htm... tags dont match in source and style files'

F1=/tmp/$USER.htm
F2=/tmp/$USER.py

sed -n '
/\[htm[A-Z]/{
	s!.*\[\(htm[a-zA-Z0-9]*\)\].*!\1!
	p
}
/\[\/htm[A-Z]/{
	s!.*\[\(.htm[a-zA-Z0-9]*\)\].*!\1!
	p
}
' $HTML | sort | uniq > $F1

sed -n '
/"htm[A-Z]/{
	s!.*"\(htm[A-Z][a-zA-Z0-9]*\)".*!\1!
	p
}
/"\/htm[A-Z]/{
	s!.*"\(.htm[A-Z][a-zA-Z0-9]*\)".*!\1!
	p
}
' *.py *.cgi | sort | uniq > $F2

diff $F1 $F2 || { echo $DESCR; exit 1; }


######################################################################
DESCR='names of sql tags dont matches in source and sql files'

F1=/tmp/$USER.sql
F2=/tmp/$USER.py

sed -n '
/\[qry[A-Z]/{
	s!.*\[\(qry[a-zA-Z0-9_]*\)\].*!\1!
	p
}
' *.sql | sort > $F1

sed -n '
/"qry[A-Z][a-z]/{
	s!.*"\(qry[a-zA-Z0-9_]*\)".*!\1!
	p
}
' *.py *.cgi | sort | uniq > $F2

diff $F1 $F2 || { echo $DESCR; exit 1; }


######################################################################
awk '

BEGIN {
	err = 0
}

/--\[/ {
	sTag = $0
}

/<form/ {
	sid = 0
	frm = 0
}

# przekazanie nazwy formatki poprzez HIDDEN
/name="_frm/ {
	frm = 1
}

# stary, wycofany sposób przekazywania sesji - tu zgłaszam jako błąd
#
/name="sid"/ {
	print FILENAME ":" FNR ": " $0
	err = 1
}

# każda formatka powinna mieć naswę jako parametr HIDDEN
#
/<\/form/ {
	if(!frm) {
		print FILENAME ":" FNR ": brakuje frm w tagu " sTag
		err = 1
	}
}

# kontrolowanie obecności _frm dla wywołań GET
#
/href="{_cgi}/ || /HREF="{_cgi}/ {
	if(/sid={sid:url}/) {
		print FILENAME ":" FNR ": usuń sid"
		err = 1
	}
	if(!/_frm=frm/) {
		print FILENAME ":" FNR ": brak _frm"
		err = 1
	}
}

# stylistyka w HTML (nieco zbliża do XHTML)
#
/<[A-Z]/ {
	print FILENAME ":" FNR ": html tags should be lower-case"
	err = 1
}
/value=.*:url/ || /VALUE=.*:url/ {
	print FILENAME ":" FNR ": if value= then should no :url modifier"
	err = 1
}
END {
	exit(err)
}

' $HTML || exit 1

######################################################################
DESCR='max length of line exceeded'

awk '
{
	gsub("\t", "        ")
	if(length($0) > 80)
	{
		print FILENAME ":" FNR ": line length exceded"
		err = 1
	}
}
END {
	exit(err)
}
' *.py *.sql $HTML *.sh || { echo $DESCR; exit 1; }

######################################################################

# python 2.3 wymaga zapisania kodowania na początku pliku
#
awk '
FNR == 2 && !/-\*- coding: iso-8859-2 -\*-/ {
	print FILENAME ":" FNR ": brak: -*- coding: iso-8859-2 -*-"
	err = 1
}
END {
	exit err
}
' *.py || exit 1

awk '
FNR == 2 && !/-\*- coding: iso-8859-2 -\*-/ {
	print FILENAME ":" FNR ": brak: -*- coding: iso-8859-2 -*-"
	err = 1
}
END {
	exit err
}
' `grep -l "^#.*python$" *.*` || exit 1



######################################################################
#exit 0

