Root/
| 1 | #!/bin/bash |
| 2 | # |
| 3 | # schhist2web - Web-browseable graphical revision history of schematics |
| 4 | # |
| 5 | # Written 2010, 2012 by Werner Almesberger |
| 6 | # Copyright 2010, 2012 Werner Almesberger |
| 7 | # |
| 8 | # This program is free software; you can redistribute it and/or modify |
| 9 | # it under the terms of the GNU General Public License as published by |
| 10 | # the Free Software Foundation; either version 2 of the License, or |
| 11 | # (at your option) any later version. |
| 12 | # |
| 13 | |
| 14 | |
| 15 | OUTDIR=_out |
| 16 | THUMB_OPTS="-w 3 -d 60 -c 0.5,0.5,0.5 -n 1,1,0" |
| 17 | BG_COLOR="f0f0ff" |
| 18 | FNAME_COLOR="#b0f0ff" |
| 19 | SEP_COLOR="#000000" |
| 20 | CUTOFF=300 |
| 21 | |
| 22 | |
| 23 | shrink() |
| 24 | { |
| 25 | pnmscale -width 120 "$@" || exit |
| 26 | } |
| 27 | |
| 28 | |
| 29 | pngdiff() |
| 30 | { |
| 31 | # pngdiff preproc outfile arg ... |
| 32 | pp="$1" |
| 33 | of="$2" |
| 34 | shift 2 |
| 35 | if ! PATH=$PATH:`dirname $0`/ppmdiff ppmdiff "$@" "$out/_tmp"; then |
| 36 | rm -f "$out/_tmp" |
| 37 | return 1 |
| 38 | fi |
| 39 | $pp "$out/_tmp" | pnmtopng >"$of" |
| 40 | rm "$out/_tmp" |
| 41 | } |
| 42 | |
| 43 | |
| 44 | symlink() |
| 45 | { |
| 46 | local old=$1 new=$2 |
| 47 | local src=`dirname "$new"`/$old |
| 48 | |
| 49 | if [ -L "$src" ]; then |
| 50 | ln -sf "`readlink \"$src\"`" "$new" |
| 51 | else |
| 52 | ln -sf "$old" "$new" |
| 53 | fi |
| 54 | } |
| 55 | |
| 56 | |
| 57 | commit_entry() |
| 58 | { |
| 59 | # usage: commit_entry <base-dir> <commit> |
| 60 | # note: the repository's base in $dir must be provided by the caller |
| 61 | |
| 62 | local dir=$1 next=$2 |
| 63 | |
| 64 | cat <<EOF |
| 65 | <TABLE bgcolor="$SEP_COLOR" cellspacing=0 width="100%"><TR><TD></TABLE> |
| 66 | EOF |
| 67 | echo "<PRE>" |
| 68 | ( cd "$dir" && git show \ |
| 69 | --pretty=format:"%aN <%aE>%n %ad%n%n %s" \ |
| 70 | --quiet $next; ) | sed '/^diff /Q' | |
| 71 | sed '/\(.\{'$CUTOFF'\}\).*/s//\1.../' | |
| 72 | sed 's/&/&/g;s/</\</g;s/>/\>/g' | |
| 73 | if [ -z "$SCHHIST_COMMIT_TEMPLATE" ]; then |
| 74 | cat |
| 75 | else |
| 76 | url=`echo "$SCHHIST_COMMIT_TEMPLATE" | sed "s/{}/$next/g"` |
| 77 | sed "1s|^|<A href=\"$url\"><B>\>\>\></B></a> |" |
| 78 | fi |
| 79 | echo "</PRE>" |
| 80 | } |
| 81 | |
| 82 | |
| 83 | wrapped_png() |
| 84 | { |
| 85 | local dir=$1 commit=$2 file=$3 |
| 86 | |
| 87 | mkdir -p "$dir/$commit/html" |
| 88 | echo "<A href=\"$commit/html/$file.html\"><IMG src=\"$commit/thumb/$file.png\"></A>" |
| 89 | cat <<EOF >"$dir/$commit/html/$file.html" |
| 90 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
| 91 | <HTML> |
| 92 | <TITLE>$file</TITLE> |
| 93 | <BODY> |
| 94 | <A href="../pdf/$file.pdf"><IMG src="../diff/$file.png"></A> |
| 95 | </BODY> |
| 96 | EOF |
| 97 | } |
| 98 | |
| 99 | |
| 100 | # |
| 101 | # Add extra : for :name: and :name= because they consume the leading : while |
| 102 | # =name: and =name= don't and would therefore be placed one position later. |
| 103 | # |
| 104 | ordered_names() |
| 105 | { |
| 106 | ls -1 "$out/names" | ( |
| 107 | while read n; do |
| 108 | if [ "${order%%:$n=*}" != "$order" ]; then |
| 109 | echo "${order%%:$n=*}": |
| 110 | elif [ "${order%%=$n:*}" != "$order" ]; then |
| 111 | echo "${order%%=$n:*}" |
| 112 | elif [ "${order%%=$n=*}" != "$order" ]; then |
| 113 | echo "${order%%=$n=*}" |
| 114 | else |
| 115 | echo "${order%%:$n:*}": |
| 116 | fi | tr -cd : | wc -c | tr '\n' ' ' |
| 117 | echo "$n" |
| 118 | done; ) | |
| 119 | sort -n -s | sed 's/^[^ ]* //' |
| 120 | } |
| 121 | |
| 122 | |
| 123 | usage() |
| 124 | { |
| 125 | cat <<EOF 2>&1 |
| 126 | usage: $0 [options] [top-dir] [top-schem] [out-dir] |
| 127 | |
| 128 | top-dir top-level directory of the git archive (default: locate it) |
| 129 | top-schem root sheet of the schematics (default: locate it in top-dir) |
| 130 | out-dir output directory (default: $OUTDIR) |
| 131 | -c cache-dir cache directory (default: same as out-dir) |
| 132 | -f identify sheets by their file name, not the sheet name |
| 133 | -n don't use previous cache content (rebuild the cache) |
| 134 | -D debug mode: make temporary work trees unique and keep them |
| 135 | -S sanitize KiCad profile and top-level sheet |
| 136 | -T tmp-dir use the specified temporary directory instead of _schhist |
| 137 | EOF |
| 138 | exit 1 |
| 139 | } |
| 140 | |
| 141 | |
| 142 | # --- Parse command-line options ---------------------------------------------- |
| 143 | |
| 144 | |
| 145 | no_cache=false |
| 146 | sanitize= |
| 147 | use_sch_name=false |
| 148 | debug=false |
| 149 | debug_opt= |
| 150 | tmp=`pwd`/_schhist2web |
| 151 | |
| 152 | while true; do |
| 153 | case "$1" in |
| 154 | -n) no_cache=true |
| 155 | shift;; |
| 156 | -c) [ -z "$2" ] && usage |
| 157 | cache=$2 |
| 158 | shift 2;; |
| 159 | -f) use_sch_name=true |
| 160 | shift;; |
| 161 | -D) debug=true |
| 162 | debug_opt=-D |
| 163 | shift;; |
| 164 | -S) sanitize=-S |
| 165 | shift;; |
| 166 | -T) [ -z "$2" ] && usage |
| 167 | tmp=$2 |
| 168 | shift 2;; |
| 169 | -*) usage;; |
| 170 | *) break;; |
| 171 | esac |
| 172 | done |
| 173 | |
| 174 | |
| 175 | # --- Interpret the command-line arguments ------------------------------------ |
| 176 | |
| 177 | |
| 178 | if [ ! -z "$1" -a -d "$1/.git" ]; then |
| 179 | dir="$1" |
| 180 | shift |
| 181 | else |
| 182 | dir=. |
| 183 | while [ ! -d $dir/.git ]; do |
| 184 | if [ $dir -ef $dir/.. ]; then |
| 185 | echo "no .git/ directory found in hierarchy" 1>&2 |
| 186 | exit 1 |
| 187 | fi |
| 188 | dir=$dir/.. |
| 189 | done |
| 190 | echo "found top-dir: $dir" 1>&2 |
| 191 | fi |
| 192 | |
| 193 | if [ ! -z "$1" -a -f "$dir/$1" -a \ |
| 194 | -f "$dir/${1%.sch}.pro" ]; then |
| 195 | sch="$1" |
| 196 | shift |
| 197 | else |
| 198 | for n in "$dir"/*.sch; do |
| 199 | [ -f "${n%.sch}.pro" ] || continue |
| 200 | if [ ! -z "$sch" ]; then |
| 201 | echo "multiple choices for top-level .sch file" 1>&2 |
| 202 | exit 1 |
| 203 | fi |
| 204 | sch="$n" |
| 205 | done |
| 206 | if [ -z "$sch" -o "$sch" = "$dir/*.sch" ]; then |
| 207 | echo "no candidate for top-level .sch file found" 1>&2 |
| 208 | exit 1 |
| 209 | fi |
| 210 | echo "found root sheet: $sch" 1>&2 |
| 211 | fi |
| 212 | |
| 213 | if [ ! -z "$1" ] && [ ! -e "$1" ] || [ -d "$1" -a ! -d "$1"/.git ]; then |
| 214 | out="$1" |
| 215 | shift |
| 216 | else |
| 217 | out=$OUTDIR |
| 218 | fi |
| 219 | [ -z "$cache" ] && cache="$out" |
| 220 | |
| 221 | [ -z "$1" ] || usage |
| 222 | |
| 223 | |
| 224 | # --- Set up some variables and the directories for cache and output ---------- |
| 225 | |
| 226 | |
| 227 | PATH=`dirname "$0"`:"$PATH" |
| 228 | first=`gitenealogy "$dir" "$sch" | sed '$s/ .*//p;d'` |
| 229 | schname=`gitenealogy "$dir" "$sch" | sed '$s/^.* //p;d'` |
| 230 | order=:$SCHHIST_ORDER: |
| 231 | |
| 232 | rm -rf "$out/*/"{diff,thumb,html,pdf} "$out/names" |
| 233 | $no_cache && rm -rf "$cache" |
| 234 | mkdir -p "$out/names" |
| 235 | mkdir -p "$cache" |
| 236 | |
| 237 | ppmmake '#e0e0e0' 5 30 | pnmtopng >"$out"/unchanged.png |
| 238 | |
| 239 | |
| 240 | # --- Generate/update the cache ----------------------------------------------- |
| 241 | |
| 242 | |
| 243 | head= |
| 244 | for n in $first `cd "$dir" && git rev-list --reverse $first..HEAD`; do |
| 245 | ( cd "$dir" && git show --pretty=format:'' --name-only $n; ) | |
| 246 | egrep -q '\.sch$|\.pro$|\.lib$' || continue |
| 247 | echo Processing $n |
| 248 | new=`gitenealogy "$dir" "$sch" | sed "/^$n /s///p;d"` |
| 249 | if [ ! -z "$new" ]; then |
| 250 | echo Name change $schname to $new 1>&2 |
| 251 | schname="$new" |
| 252 | fi |
| 253 | trap "rm -rf \"$cache/$n/ps\" \"$cache/$n/ppm0\" \"$cache/$n/ppm1\" \ |
| 254 | \"$cache/$n/ppm2\" \"$tmp\"" 0 |
| 255 | if [ ! -d "$cache/$n/ppm2" ]; then |
| 256 | rm -rf "$cache/$n/"{ps,ppm0,ppm1,ppm2} |
| 257 | mkdir -p "$cache/$n/"{ps,ppm0,ppm1,ppm2} |
| 258 | # |
| 259 | # potential optimization here: remember Postscript files from previous |
| 260 | # run (or their md5sum) and check if they have changed. If not, skip |
| 261 | # the ghostscript run and just put a symlink, replacing the less |
| 262 | # efficient optimization below. |
| 263 | # |
| 264 | tmp2=`gitsch2ps -k $sanitize $debug_opt "$dir" "$schname" $n "$tmp"` || |
| 265 | exit |
| 266 | for m in "$tmp"/*.ps; do |
| 267 | [ "$m" = "$tmp/*.ps" ] && break |
| 268 | # call sub-sheets by their own name, without prepending the |
| 269 | # top-level sheet's name |
| 270 | name=`basename "$m" .ps` |
| 271 | name=${name#`basename "$schname" .sch`-} |
| 272 | |
| 273 | # |
| 274 | # short-cut for $schname: since it's derived from the present $sch, |
| 275 | # we can just update it without further ado. |
| 276 | # |
| 277 | if [ "$name" = "`basename \"$schname\" .sch`" ]; then |
| 278 | name=`basename "$sch" .sch` |
| 279 | elif $use_sch_name && |
| 280 | path=`subschname2file "$tmp2/$schname" "$name"`; then |
| 281 | name=`basename "$path" .sch` |
| 282 | # |
| 283 | # if the file is easily located, also track any future name |
| 284 | # changes |
| 285 | # |
| 286 | path=`dirname "$schname"`/$path |
| 287 | if [ -f "$tmp2/$path" ]; then |
| 288 | name=`gitwhoareyounow "$tmp2" "$path"` |
| 289 | name=`basename "$name" .sch` |
| 290 | fi |
| 291 | fi |
| 292 | |
| 293 | # Postscript, for making PDFs later |
| 294 | ps="$cache/$n/ps/$name.ps" |
| 295 | normalizeschps "$m" "$ps" || exit |
| 296 | |
| 297 | # Unadorned pixmap, for comparison |
| 298 | ppm="$cache/$n/ppm0/$name.ppm" |
| 299 | schps2ppm -n "$ps" "$ppm" || exit |
| 300 | |
| 301 | # Pixmap with thin lines, for the detail views |
| 302 | ppm="$cache/$n/ppm1/$name.ppm" |
| 303 | normalizeschps -w 120 "$m" | schps2ppm - "$ppm" || exit |
| 304 | |
| 305 | # Pixmap with thick lines, for the thumbnails |
| 306 | ppm="$cache/$n/ppm2/$name.ppm" |
| 307 | normalizeschps -w 500 "$m" | schps2ppm - "$ppm" || exit |
| 308 | done |
| 309 | rm -rf "$tmp" |
| 310 | $debug || rm -rf "$tmp2" |
| 311 | fi |
| 312 | for m in "$cache/$n/ppm0/"*; do |
| 313 | [ "$m" = "$cache/$n/ppm0/*" ] && break |
| 314 | if [ ! -z "$head" ]; then |
| 315 | prev="$cache/$head/ppm0/${m##*/}" |
| 316 | if [ -r "$prev" ] && cmp -s "$prev" "$m"; then |
| 317 | for d in ppm0 ppm1 ppm2; do |
| 318 | symlink "../../$head/$d/${m##*/}" "$cache/$n/$d/${m##*/}" |
| 319 | done |
| 320 | m_ps=${m%.ppm}.ps |
| 321 | symlink "../../$head/ps/${m_ps##*/}" "$cache/$n/ps/${m_ps##*/}" |
| 322 | fi |
| 323 | fi |
| 324 | touch "$out/names/`basename \"$m\" .ppm`" |
| 325 | done |
| 326 | trap 0 |
| 327 | head=$n |
| 328 | done |
| 329 | |
| 330 | if [ -z "$head" ]; then |
| 331 | echo "no usable head found" 2>&1 |
| 332 | exit 1 |
| 333 | fi |
| 334 | |
| 335 | |
| 336 | # --- Title of the Web page and table header ---------------------------------- |
| 337 | |
| 338 | |
| 339 | index="$out/_index.html" |
| 340 | index_final="$out/index.html" |
| 341 | all= |
| 342 | { |
| 343 | cat <<EOF |
| 344 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
| 345 | <HTML> |
| 346 | EOF |
| 347 | if [ ! -z "$SCHHIST_TITLE" ]; then |
| 348 | echo "<TITLE>$SCHHIST_TITLE</TITLE>" |
| 349 | fi |
| 350 | echo "<BODY>" |
| 351 | if [ ! -z "$SCHHIST_TITLE" ]; then |
| 352 | echo "<H1>" |
| 353 | [ -z "$SCHHIST_HOME_URL" ] || echo "<A href=\"$SCHHIST_HOME_URL\">" |
| 354 | echo "$SCHHIST_TITLE" |
| 355 | [ -z "$SCHHIST_HOME_URL" ] || echo "</A>" |
| 356 | echo "</H1>" |
| 357 | fi |
| 358 | cat <<EOF |
| 359 | <TABLE bgcolor="$BG_COLOR" callpadding=1> |
| 360 | <TR bgcolor="$FNAME_COLOR"> |
| 361 | EOF |
| 362 | while read m; do |
| 363 | [ "${order%%=$m[:=]*}" = "$order" ] || continue |
| 364 | ps="$cache/$head/ps/$m.ps" |
| 365 | if [ -r "$ps" ]; then |
| 366 | # |
| 367 | # Note: we read from variable ps_$head but we write to constant |
| 368 | # pdf_head. We can't use pdf_$head here, because that may just be a |
| 369 | # commit with a change and we thus generate a delta PDF below. |
| 370 | # |
| 371 | mkdir -p "$out/pdf_head" |
| 372 | schps2pdf -o "$out/pdf_head/$m.pdf" "$ps" || exit |
| 373 | all="$all \"$ps\"" |
| 374 | echo "<TD><A href=\"pdf_head/$m.pdf\"><B>$m</B></A>" |
| 375 | else |
| 376 | echo "<TD><B>$m</B>" |
| 377 | fi |
| 378 | done < <(ordered_names) |
| 379 | proj=`basename "$sch" .sch` |
| 380 | eval schps2pdf -t \""$proj-"\" -o \""$out/pdf_$proj.pdf"\" $all |
| 381 | echo "<TD><A href=\"pdf_$proj.pdf\">All sheets</A>" |
| 382 | } >"$index" |
| 383 | |
| 384 | |
| 385 | # --- Diff all the revisions, newest to oldest -------------------------------- |
| 386 | |
| 387 | |
| 388 | next="$head" |
| 389 | for n in `cd "$dir" && git rev-list $first..HEAD~1` $first; do |
| 390 | [ -d "$cache/$n/ppm0" ] || continue |
| 391 | empty=true |
| 392 | s="<TR>" |
| 393 | td=false |
| 394 | mkdir -p "$out/$next/"{diff,thumb,html,pdf} |
| 395 | while read m; do |
| 396 | a0="$cache/$n/ppm0/$m.ppm" |
| 397 | a1="$cache/$n/ppm1/$m.ppm" |
| 398 | a2="$cache/$n/ppm2/$m.ppm" |
| 399 | aps="$cache/$n/ps/$m.ps" |
| 400 | |
| 401 | b0="$cache/$next/ppm0/$m.ppm" |
| 402 | b1="$cache/$next/ppm1/$m.ppm" |
| 403 | b2="$cache/$next/ppm2/$m.ppm" |
| 404 | bps="$cache/$next/ps/$m.ps" |
| 405 | |
| 406 | diff="$out/$next/diff/$m.png" |
| 407 | thumb="$out/$next/thumb/$m.png" |
| 408 | pdf="$out/$next/pdf/$m.pdf" |
| 409 | |
| 410 | if [ "${order%%=$m[:=]*}" = "$order" ]; then |
| 411 | $td && s="$s<TD>" |
| 412 | td=true |
| 413 | fi |
| 414 | |
| 415 | if [ -f "$a0" -a -f "$b0" ]; then |
| 416 | if ! pngdiff cat "$diff" "$a1" "$b1" "$a0" "$b0"; then |
| 417 | $td && s="$s<TD align=\"center\" valign=\"middle\">" |
| 418 | td=false |
| 419 | s="$s<IMG src=\"unchanged.png\">" |
| 420 | continue |
| 421 | fi |
| 422 | pngdiff shrink "$thumb" -f $THUMB_OPTS "$a2" "$b2" "$a0" "$b0" || |
| 423 | exit |
| 424 | schps2pdf -T BEFORE -T AFTER -o "$pdf" "$aps" "$bps" || exit |
| 425 | elif [ -f "$a0" ]; then |
| 426 | pngdiff cat "$diff" -f -c 1,0,0 "$a1" "$a1" || exit |
| 427 | pngdiff shrink "$thumb" -f $THUMB_OPTS -c 1,0,0 "$a2" "$a2" || |
| 428 | exit |
| 429 | schps2pdf -T DELETED -o "$pdf" "$aps" || exit |
| 430 | elif [ -f "$b0" ]; then |
| 431 | pngdiff cat "$diff" -f -c 0,1,0 "$b1" "$b1" || exit |
| 432 | pngdiff shrink "$thumb" -f $THUMB_OPTS -c 0,1,0 "$b2" "$b2" || |
| 433 | exit |
| 434 | schps2pdf -T NEW -o "$pdf" "$bps" || exit |
| 435 | else |
| 436 | continue |
| 437 | fi |
| 438 | $td && s="$s<TD>" |
| 439 | td=false |
| 440 | echo "$s" |
| 441 | s= |
| 442 | wrapped_png "$out" "$next" "$m" |
| 443 | empty=false |
| 444 | done < <(ordered_names) |
| 445 | if ! $empty; then |
| 446 | $td && s="$s<TD>" |
| 447 | echo "$s<TD valign=\"middle\">" |
| 448 | commit_entry "$dir" $next |
| 449 | fi |
| 450 | next=$n |
| 451 | done >>"$index" |
| 452 | |
| 453 | |
| 454 | # --- Add creation entries for all files in the first commit ------------------ |
| 455 | |
| 456 | |
| 457 | if [ -d "$cache/$next/ppm0" ]; then # could this ever be false ? |
| 458 | empty=true |
| 459 | echo "<TR>" |
| 460 | mkdir -p "$out/$next/"{diff,thumb,html,pdf} |
| 461 | while read m; do |
| 462 | p1="$cache/$next/ppm1/$m.ppm" |
| 463 | p2="$cache/$next/ppm2/$m.ppm" |
| 464 | ps="$cache/$next/ps/$m.ps" |
| 465 | diff="$out/$next/diff/$m.png" |
| 466 | thumb="$out/$next/thumb/$m.png" |
| 467 | pdf="$out/$next/pdf/$m.pdf" |
| 468 | |
| 469 | [ "${order%%=$m[:=]*}" = "$order" ] && echo "<TD>" |
| 470 | [ -f "$p1" ] || continue |
| 471 | pngdiff cat "$diff" -f -c 0,1,0 "$p1" "$p1" || exit |
| 472 | pngdiff shrink "$thumb" -f $THUMB_OPTS -c 0,1,0 "$p2" "$p2" || |
| 473 | exit |
| 474 | schps2pdf -T NEW -o "$pdf" "$ps" || exit |
| 475 | wrapped_png "$out" "$next" "$m" |
| 476 | empty=false |
| 477 | done < <(ordered_names) |
| 478 | if ! $empty; then |
| 479 | echo "<TD valign=\"middle\">" |
| 480 | commit_entry "$dir" $next |
| 481 | fi |
| 482 | fi >>"$index" |
| 483 | |
| 484 | |
| 485 | # --- Finish ------------------------------------------------------------------ |
| 486 | |
| 487 | |
| 488 | cat <<EOF >>"$index" |
| 489 | </TABLE> |
| 490 | <HR> |
| 491 | `date -u '+%F %X'` UTC |
| 492 | </BODY> |
| 493 | </HTML> |
| 494 | EOF |
| 495 | |
| 496 | mv "$index" "$index_final" |
| 497 |
Branches:
master
