From 17c6705c2830cbd68c7614ef145fbff69408054d Mon Sep 17 00:00:00 2001 From: Werner Almesberger Date: Tue, 7 Sep 2010 17:03:19 -0300 Subject: [PATCH] Initial commit of files moved over from ben-wpan. --- schhist/Makefile | 45 ++++ schhist/gitenealogy | 44 ++++ schhist/gitsch2ps | 81 ++++++++ schhist/normalizeschps | 60 ++++++ schhist/ppmdiff/Makefile | 3 + schhist/ppmdiff/ppmdiff.c | 412 +++++++++++++++++++++++++++++++++++++ schhist/sanitize-profile | 82 ++++++++ schhist/schhist2web | 417 ++++++++++++++++++++++++++++++++++++++ schhist/schps2pdf | 59 ++++++ schhist/schps2ppm | 57 ++++++ 10 files changed, 1260 insertions(+) create mode 100644 schhist/Makefile create mode 100755 schhist/gitenealogy create mode 100755 schhist/gitsch2ps create mode 100755 schhist/normalizeschps create mode 100644 schhist/ppmdiff/Makefile create mode 100644 schhist/ppmdiff/ppmdiff.c create mode 100755 schhist/sanitize-profile create mode 100755 schhist/schhist2web create mode 100755 schhist/schps2pdf create mode 100755 schhist/schps2ppm diff --git a/schhist/Makefile b/schhist/Makefile new file mode 100644 index 0000000..448460c --- /dev/null +++ b/schhist/Makefile @@ -0,0 +1,45 @@ +SHELL = /bin/bash + +CACHE_DIRS = {ppm0,ppm1,ppm2,ps} +DEST = werner@host:/home/httpd/almesberger/misc/ben/ +RSYNC = eval rsync -a --progress "--exclude "$(CACHE_DIRS)/ + +.PHONY: all ben-wpan-schhist ben-wpan-schhist-upload +.PHONY: xue-schhist xue-schhist-upload +.PHONY: cntr-schhist cntr-schhist-upload + +# All the targets are for demo purposes pnly ! + +all: + @echo "possible targets:" 2>&1 + @echo " ben-wpan-schhist ben-wpan-schhist-upload" 2>&1 + @echo " xue-schhist xue-schhist-upload" 2>&1 + @echo " cntr-schhist cntr-schhist-upload" 2>&1 + @exit 1 + +ben-wpan-schhist: + SCHHIST_TITLE=ben-wpan/atrf \ + SCHHIST_HOME_URL=http://projects.qi-hardware.com/index.php/p/ben-wpan/ \ + SCHHIST_COMMIT_TEMPLATE='http://projects.qi-hardware.com/index.php/p/ben-wpan/source/commit/{}/' \ + ./schhist2web atrf/wpan-atrf.sch + +ben-wpan-schhist-upload: + $(RSYNC) _out/* $(DEST)/demo/ + +xue-schhist: + SCHHIST_TITLE=Xue \ + SCHHIST_HOME_URL=http://projects.qi-hardware.com/index.php/p/xue/ \ + SCHHIST_COMMIT_TEMPLATE='http://projects.qi-hardware.com/index.php/p/xue/source/commit/{}/' \ + ./schhist2web -S ../../xue kicad/xue-rnc/xue-rnc.sch _xue + +xue-schhist-upload: + $(RSYNC) _xue/* $(DEST)/demo2/ + +cntr-schhist: + SCHHIST_TITLE=ben-wpan/cntr \ + SCHHIST_HOME_URL=http://projects.qi-hardware.com/index.php/p/ben-wpan/ \ + SCHHIST_COMMIT_TEMPLATE='http://projects.qi-hardware.com/index.php/p/ben-wpan/source/commit/{}/' \ + ./schhist2web cntr/cntr.sch _cntr + +cntr-schhist-upload: + $(RSYNC) _cntr/* $(DEST)/demo3/ diff --git a/schhist/gitenealogy b/schhist/gitenealogy new file mode 100755 index 0000000..463968c --- /dev/null +++ b/schhist/gitenealogy @@ -0,0 +1,44 @@ +#!/bin/sh +# +# gitenealogy - Trace the ancestry of a file in git across renames +# +# Written 2010 by Werner Almesberger +# Copyright 2010 Werner Almesberger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + + +usage() +{ + cat <&1 +usage: $0 repo-dir path + + The file to trace must be at repo-dir/path +EOF + exit 1 +} + + +if [ -z "$2" -o ! -z "$3" ]; then + usage +fi + +if [ ! -d "$1" -o ! -d "$1/.git" ]; then + echo "no git repository at $1" 1>&2 + exit 1 +fi +if [ ! -f "$1/$2" ]; then + echo "cannot find $2" 2>&1 + exit 1 +fi + +cd "$1" || exit +git log --follow --name-status -- "$2" | + awk ' +/^commit /{ if (c) print c, n; c = $2 } +{ if (NF) n = $(NF) } +END { if (c) print c, n; }' diff --git a/schhist/gitsch2ps b/schhist/gitsch2ps new file mode 100755 index 0000000..6245fe0 --- /dev/null +++ b/schhist/gitsch2ps @@ -0,0 +1,81 @@ +#!/bin/sh +# +# gitsch2ps - Generate PS files for KiCad schematics in git +# +# Written 2010 by Werner Almesberger +# Copyright 2010 Werner Almesberger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + + +usage() +{ + cat <&2 +usage: $0 [options] top-dir top-schem [commit] outdir + + -S sanitize the KiCad profile +EOF + exit 1 +} + + +sanitize=true +while true; do + case "$1" in + -S) sanitize=`PATH="$PATH":\`dirname "$0"\` which sanitize-profile` + [ "$sanitize" = "${sanitize#/}" ] && sanitize=`pwd`/"$sanitize" + shift;; + -*) + usage;; + *) + break;; + esac +done + +[ ! -z "$3" -a -z "$5" ] || usage +dir="$1" +schem="$2" +sdir=`dirname "$schem"` +if [ -z "$4" ]; then + commit=HEAD + outdir="$3" +else + commit="$3" + outdir="$4" +fi + +[ "$dir" != "${dir#/}" ] || dir=`pwd`/$dir + +[ "$commit" != HEAD -o -f "$dir/$schem" ] || usage +[ -d "$dir/.git" ] || usage + +tmp="$dir/../_gitsch2ps" +sch="$tmp/$sdir" + +rm -rf "$tmp" +rm -rf "$outdir" + +git clone -s -n "$dir/.git" "$tmp" || exit +( cd "$tmp" && git checkout -q "$commit"; ) || exit + +if [ ! -f "$tmp/$schem" ]; then + echo "$schem not found (checked out into $tmp)" 1>&2 + exit 1 +fi + +( + cd "$sch" || exit + rm -f *.ps + $sanitize "$tmp/${schem%.sch}.pro" || + exit + eeschema --plot "$tmp/$schem" +) || exit + +mkdir -p "$outdir" +mv "$sch"/*.ps "$outdir" + +rm -rf "$tmp" diff --git a/schhist/normalizeschps b/schhist/normalizeschps new file mode 100755 index 0000000..98cb740 --- /dev/null +++ b/schhist/normalizeschps @@ -0,0 +1,60 @@ +#!/bin/sh +# +# normalizeschps - Normalize eeschema Postscript +# +# Written 2010 by Werner Almesberger +# Copyright 2010 Werner Almesberger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + + +usage() +{ + cat <&2 +usage: $0 [options] [in.ps [out.ps]] + + -w points Postscript line width (default: use the original) +EOF + exit 1 +} + + +width= +while true; do + case "$1" in + -w) [ -z "$2" ] && usage + width="$2" + shift 2;; + -*) + usage;; + *) + break;; + esac +done + +[ -z "$3" ] || usage +in=${1:--} +out=${2:-/dev/stdout} + +sed ' + 1c%!PS-Adobe-3.0\ + currentdict /DidNormalize known not { \ + '"`[ -z \"$width\" ] || + echo \"/setlinewidth { $width 2 copy lt { exch } if pop \ + setlinewidth } bind def \"`"' \ + /rectfill { rectstroke } bind def \ + /DidNormalize true def \ + } if \ + gsave + /%%DocumentMedia: A4.*/a-20 -10 translate + /%%DocumentMedia: A3.*/{s/A3/A4/;a-20 -10 translate 0.70 dup scale + } + /%%DocumentMedia: A2.*/{s/A2/A4/;a-18 -12 translate 0.49 dup scale + } + $agrestore' "$in" >"$out" + +# /%%Orientation: Landscape/d' "$n" diff --git a/schhist/ppmdiff/Makefile b/schhist/ppmdiff/Makefile new file mode 100644 index 0000000..451c513 --- /dev/null +++ b/schhist/ppmdiff/Makefile @@ -0,0 +1,3 @@ +CFLAGS=-Wall -g + +ppmdiff: diff --git a/schhist/ppmdiff/ppmdiff.c b/schhist/ppmdiff/ppmdiff.c new file mode 100644 index 0000000..6533fb6 --- /dev/null +++ b/schhist/ppmdiff/ppmdiff.c @@ -0,0 +1,412 @@ +/* + * ppmdiff.c - Mark differences in two PPM files + * + * Written 2010 by Werner Almesberger + * Copyright 2010 Werner Almesberger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +#include +#include +#include +#include +#include + + +static uint8_t a_only[3] = { 255, 0, 0 }; +static uint8_t b_only[3] = { 0, 255, 0 }; +static uint8_t both[3] = { 220, 220, 220 }; +static uint8_t frame[3] = { 0, 0, 255 }; +static uint8_t frame_fill[3] = { 255, 255, 200 }; +static int frame_dist = 40; +static int frame_width = 2; + + +static uint8_t *load_ppm(const char *name, int *x, int *y) +{ + FILE *file; + char line[100]; + int this_x, this_y, depth; + int n; + uint8_t *img; + + file = fopen(name, "r"); + if (!file) { + perror(name); + exit(1); + } + if (!fgets(line, sizeof(line), file)) { + fprintf(stderr, "can't read file type\n"); + exit(1); + } + if (strcmp(line, "P6\n")) { + fprintf(stderr, "file type must be P6, not %s", line); + exit(1); + } + if (!fgets(line, sizeof(line), file)) { + fprintf(stderr, "can't read resolution\n"); + exit(1); + } + if (sscanf(line, "%d %d", &this_x, &this_y) != 2) { + fprintf(stderr, "can't parse resolution: %s", line); + exit(1); + } + if (*x || *y) { + if (*x != this_x || *y != this_y) { + fprintf(stderr, + "resolution changed from %dx%d to %dx%d\n", + *x, *y, this_x, this_y); + exit(1); + } + } else { + *x = this_x; + *y = this_y; + } + if (!fgets(line, sizeof(line), file)) { + fprintf(stderr, "can't read depth\n"); + exit(1); + } + if (sscanf(line, "%d", &depth) != 1) { + fprintf(stderr, "can't parse depth: %s", line); + exit(1); + } + if (depth != 255) { + fprintf(stderr, "depth must be 255, not %d\n", depth); + exit(1); + } + n = *x**y*3; + img = malloc(n); + if (!img) { + perror("malloc"); + exit(1); + } + if (fread(img, 1, n, file) != n) { + fprintf(stderr, "can't read %d bytes\n", n); + exit(1); + } + fclose(file); + return img; +} + + +static struct area { + int x0, y0, x1, y1; + struct area *next; +} *areas = NULL; + + +static void add_area(struct area **root, int x0, int y0, int x1, int y1) +{ + while (*root) { + struct area *area = *root; + + if (area->x0 > x1 || area->y0 > y1 || + area->x1 < x0 || area->y1 < y0) { + root = &(*root)->next; + continue; + } + x0 = x0 < area->x0 ? x0 : area->x0; + y0 = y0 < area->y0 ? y0 : area->y0; + x1 = x1 > area->x1 ? x1 : area->x1; + y1 = y1 > area->y1 ? y1 : area->y1; + *root = area->next; + free(area); + add_area(&areas, x0, y0, x1, y1); + return; + } + *root = malloc(sizeof(**root)); + if (!*root) { + perror("malloc"); + exit(1); + } + (*root)->x0 = x0; + (*root)->y0 = y0; + (*root)->x1 = x1; + (*root)->y1 = y1; + (*root)->next = NULL; +} + + +static void change(int x, int y) +{ + add_area(&areas, + x-frame_dist, y-frame_dist, x+frame_dist, y+frame_dist); +} + + +static void set_pixel(uint8_t *p, const uint8_t *color, const uint8_t *value) +{ + double f; + int i; + + f = (255-(value[0] | value[1] | value[2]))/255.0; + for (i = 0; i != 3; i++) + p[i] = 255-(255-color[i])*f; +} + + +static uint8_t *diff(const uint8_t *a, const uint8_t *b, int xres, int yres, + int mark_areas) +{ + uint8_t *res, *p; + int x, y; + unsigned val_a, val_b; + + res = p = malloc(xres*yres*3); + if (!res) { + perror("malloc"); + exit(1); + } + for (y = 0; y != yres; y++) + for (x = 0; x != xres; x++) { + val_a = a[0]+a[1]+a[2]; + val_b = b[0]+b[1]+b[2]; + if (val_a == val_b) { + set_pixel(p, both, b); + } else if (val_a < val_b) { + set_pixel(p, a_only, a); + if (mark_areas) + change(x, y); + } else if (val_a > val_b) { + set_pixel(p, b_only, b); + if (mark_areas) + change(x, y); + } else { + abort(); /* no longer used */ + memset(p, 255, 3); +// memcpy(p, "\0\0\xff", 3); + } + a += 3; + b += 3; + p += 3; + } + return res; +} + + +static void shadow_diff(const uint8_t *a, const uint8_t *b, int xres, int yres) +{ + int x, y; + + for (y = 0; y != yres; y++) + for (x = 0; x != xres; x++) { + if (memcmp(a, b, 3)) + change(x, y); + a += 3; + b += 3; + } +} + + +static void point(uint8_t *img, int x, int y, int xres, int yres) +{ + uint8_t *p; + + if (x < 0 || y < 0 || x >= xres || y >= yres) + return; + p = img+(y*xres+x)*3; + if ((p[0] & p[1] & p[2]) != 255) + return; + memcpy(p, frame, 3); +} + + +static void hline(uint8_t *img, int x0, int x1, int y, int xres, int yres) +{ + int x; + + for (x = x0; x <= x1; x++) + point(img, x, y, xres, yres); +} + + +static void vline(uint8_t *img, int y0, int y1, int x, int xres, int yres) +{ + int y; + + for (y = y0; y <= y1; y++) + point(img, x, y, xres, yres); +} + + +static void fill(uint8_t *img, int x0, int y0, int x1, int y1, + int xres, int yres) +{ + int x, y; + uint8_t *p; + + for (y = y0; y <= y1; y++) { + if (y < 0 || y >= yres) + continue; + p = img+(xres*y+x0)*3; + for (x = x0; x <= x1; x++) { + if (x >= 0 && x < xres && (p[0] & p[1] & p[2]) == 255) + memcpy(p, frame_fill, 3); + p += 3; + } + } +} + + +static void mark_areas(uint8_t *img, int x, int y) +{ + const struct area *area; + int r1 = 0, r2 = 0, i; + + if (frame_width) { + r1 = (frame_width-1)/2; + r2 = (frame_width-1)-r1; + } + for (area = areas; area; area = area->next) { + if (frame_width) + for (i = -r1; i <= r2; i++) { + hline(img, area->x0-r1, area->x1+r2, area->y0+i, + x, y); + hline(img, area->x0-r1, area->x1+r2, area->y1+i, + x, y); + vline(img, area->y0+r1, area->y1-r2, area->x0+i, + x, y); + vline(img, area->y0+r1, area->y1-r2, area->x1+i, + x, y); + } + fill(img, + area->x0+r1, area->y0+r1, area->x1-r2, area->y1-r2, x, y); + } +} + + +static void usage(const char *name) +{ + fprintf(stderr, +"usage: %s [-f] [-a color] [-b color] [-c color] [-d pixels]\n" +"%6s %*s [-m color] [-n color] [-w pixels] file_a.ppm file_b.ppm\n" +"%6s %*s [file_a'.ppm file_b'.ppm] [out.ppm]\n\n" +" file_a.ppm and file_b.ppm are two input images\n" +" file_a'.ppm and file_b'.ppm if present, are searched for changes as well\n" +" out.ppm output file (default: standard output)\n\n" +" -f generate output (and return success) even if there is no change\n" +" -a color color of items only in image A\n" +" -b color color of items only in image B\n" +" -c color color of items in both images\n" +" -d pixels distance between change and marker box. 0 disables markers.\n" +" -m color color of the frame of the marker box.\n" +" -n color color of the background of the marker box\n" +" -w pixels width of the frame of the marker box. 0 disables frames.\n\n" +" color is specified as R,B,G with each component as a floating-point\n" +" value from 0 to 1. E.g., 1,1,1 is white.\n\n" +" The images are expected to have dark colors on a perfectly white\n" +" background.\n" + , name, "", (int) strlen(name), "", "", (int) strlen(name), ""); + exit(1); +} + + +static void parse_color(uint8_t *c, const char *s, const char *name) +{ + float r, g, b; + + if (sscanf(s, "%f,%f,%f", &r, &g, &b) != 3) + usage(name); + c[0] = 255*r; + c[1] = 255*g; + c[2] = 255*b; +} + + +int main(int argc, char *const *argv) +{ + int force = 0; + int x = 0, y = 0; + uint8_t *old, *new, *d, *a, *b; + char *shadow_old = NULL, *shadow_new = NULL, *out_name = NULL; + FILE *out; + char *end; + int c; + + while ((c = getopt(argc, argv, "a:b:c:d:fm:n:w:")) != EOF) + switch (c) { + case 'a': + parse_color(a_only, optarg, *argv); + break; + case 'b': + parse_color(b_only, optarg, *argv); + break; + case 'c': + parse_color(both, optarg, *argv); + break; + case 'd': + frame_dist = strtoul(optarg, &end, 0); + if (*end) + usage(*argv); + break; + case 'f': + force = 1; + break; + case 'm': + parse_color(frame, optarg, *argv); + break; + case 'n': + parse_color(frame_fill, optarg, *argv); + break; + case 'w': + frame_width = strtoul(optarg, &end, 0); + if (*end) + usage(*argv); + break; + default: + usage(*argv); + } + switch (argc-optind) { + case 2: + break; + case 3: + out_name = argv[optind+2]; + break; + case 5: + out_name = argv[optind+4]; + /* fall through */ + case 4: + shadow_old = argv[optind+2]; + shadow_new = argv[optind+3]; + break; + default: + usage(*argv); + } + + old = load_ppm(argv[optind], &x, &y); + new = load_ppm(argv[optind+1], &x, &y); + if (shadow_old) { + a = load_ppm(shadow_old, &x, &y); + b = load_ppm(shadow_new, &x, &y); + if (!force && !memcmp(a, b, x*y*3)) + return 1; + shadow_diff(a, b, x, y); + } + if (!force && !areas && !memcmp(old, new, x*y*3)) + return 1; + d = diff(old, new, x, y, !shadow_old); + if (frame_dist) + mark_areas(d, x, y); + + if (out_name) { + out = fopen(out_name, "w"); + if (!out) { + perror(out_name); + exit(1); + } + } else { + out = stdout; + } + fprintf(out, "P6\n%d %d\n255\n", x, y); + fwrite(d, 1, x*y*3, out); + if (fclose(out) == EOF) { + perror("fclose"); + exit(1); + } + return 0; +} diff --git a/schhist/sanitize-profile b/schhist/sanitize-profile new file mode 100755 index 0000000..3538584 --- /dev/null +++ b/schhist/sanitize-profile @@ -0,0 +1,82 @@ +#!/usr/bin/perl +# +# sanitize-profile - Remove items from a KiCad profile that may cause an upset +# +# Written 2010 by Werner Almesberger +# Copyright 2010 Werner Almesberger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + + +@LIBS = ("/usr/share/kicad/library", "/usr/local/share/kicad/library"); + + +sub rewrite +{ + local ($s) = @_; + + return $s if $section ne "eeschema/libraries"; + return $s unless /^LibName(\d+)=(.*)\s*$/; + my $lib = $2; + if ($1 == $in_lib) { + $in_lib++; + } else { + print STDERR "LibName$1 when expecting $next_lib. Renumbering."; + $in_lib = $1+1; + } + $out_lib++; + my $var = "LibName$out_lib"; + if ($lib =~ /\//) { + return "$var=$lib\n" if -r "$lib.lib"; + } + for (".", $libdir, @LIBS) { + return "$var=$lib\n" if -r "$_/$lib.lib"; + } + print STDERR "cannot find $lib\n"; + $out_lib--; + return undef; +} + + +sub usage +{ + print STDERR "usage: $0 file.pro [outfile]\n"; + exit(1); +} + + +($file, $out) = @ARGV; +&usage if $#ARGV > 1; + +($dir = $file) =~ s|.*/||; +$dir = "." if $dir eq ""; + +open(FILE, $file) || die "$file: $!"; +while () { + if (/^\[(\S+)\]/) { + $section = $1; + if ($section eq "eeschema/libraries") { + $in_lib = 1; + $out_lib = 0; + } + } + if ($section eq "eeschema") { + $libdir = $2 if /^LibDir=(.*)\s*$/; + } + $s = &rewrite($_); + push(@f, $s) if defined $s; +} +close FILE; + +if (!defined $out) { + rename($file, "$file.bak"); + $out= $file; +} + +open(FILE, ">$out") || die "$out: $!"; +print FILE join("", @f) || die "$out: $!"; +close FILE || die "$out: $!"; diff --git a/schhist/schhist2web b/schhist/schhist2web new file mode 100755 index 0000000..3350ea6 --- /dev/null +++ b/schhist/schhist2web @@ -0,0 +1,417 @@ +#!/bin/bash +# +# schhist2web - Web-browseable graphical revision history of schematics +# +# Written 2010 by Werner Almesberger +# Copyright 2010 Werner Almesberger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + + +OUTDIR=_out +THUMB_OPTS="-w 3 -d 60 -c 0.5,0.5,0.5 -n 1,1,0" +BG_COLOR="f0f0ff" +FNAME_COLOR="#b0f0ff" +SEP_COLOR="#000000" + + +shrink() +{ + pnmscale -width 120 "$@" || exit +} + + +pngdiff() +{ + # pngdiff preproc outfile arg ... + pp="$1" + of="$2" + shift 2 + if ! PATH=$PATH:`dirname $0`/ppmdiff ppmdiff "$@" "$out/_tmp"; then + rm -f "$out/_tmp" + return 1 + fi + $pp "$out/_tmp" | pnmtopng >"$of" + rm "$out/_tmp" +} + + +symlink() +{ + local old=$1 new=$2 + local src=`dirname "$new"`/$old + + if [ -L "$src" ]; then + ln -sf "`readlink \"$src\"`" "$new" + else + ln -sf "$old" "$new" + fi +} + + +commit_entry() +{ + # usage: commit_entry + # note: the repository's base in $dir must be provided by the caller + + local dir=$1 next=$2 + + cat < +EOF + echo "
"
+    ( cd "$dir" && git show \
+        --pretty=format:"%aN <%aE>%n    %ad, %ar%n%n    %s" \
+	--quiet $next; ) |
+      sed 's/&/&/g;s//\>/g' |
+      if [ -z "$SCHHIST_COMMIT_TEMPLATE" ]; then
+	cat
+      else
+	url=`echo "$SCHHIST_COMMIT_TEMPLATE" | sed "s/{}/$next/g"`
+	sed "1s|^|\>\>\> |"
+      fi
+    echo "
" +} + + +wrapped_png() +{ + local dir=$1 commit=$2 file=$3 + + mkdir -p "$dir/$commit/html" + echo "" + cat <"$dir/$commit/html/$file.html" + + +$file + + + +EOF +} + + +usage() +{ + cat <&1 +usage: $0 [-c cache-dir] [-n] [-S] [top-dir] [top-schem] [out-dir] + + top-dir top-level directory of the git archive (default: locate it) + top-schem root sheet of the schematics (default: locate it in top-dir) + out-dir output directory (default: $OUTDIR) + -c cache-dir cache directory (default: same as out-dir) + -n don't use previous cache content (rebuild the cache) + -S sanitize KiCad profile +EOF + exit 1 +} + + +# --- Parse command-line options ---------------------------------------------- + + +no_cache=false +sanitize= + +while true; do + case "$1" in + -n) no_cache=true + shift;; + -c) [ -z "$1" ] && usage + cache="$1" + shift 2;; + -S) sanitize=-S + shift;; + -*) usage;; + *) break;; + esac +done + + +# --- Interpret the command-line arguments ------------------------------------ + + +if [ ! -z "$1" -a -d "$1/.git" ]; then + dir="$1" + shift +else + dir=. + while [ ! -d $dir/.git ]; do + if [ $dir -ef $dir/.. ]; then + echo "no .git/ directory found in hierarchy" 1>&2 + exit 1 + fi + dir=$dir/.. + done + echo "found top-dir: $dir" 1>&2 +fi + +if [ ! -z "$1" -a -f "$dir/$1" -a \ + -f "$dir/${1%.sch}.pro" ]; then + sch="$1" + shift +else + for n in "$dir"/*.sch; do + [ -f "${n%.sch}.pro" ] || continue + if [ ! -z "$sch" ]; then + echo "multiple choices for top-level .sch file" 1>&2 + exit 1 + fi + sch="$n" + done + if [ -z "$sch" -o "$sch" = "$dir/*.sch" ]; then + echo "no candidate for top-level .sch file found" 1>&2 + exit 1 + fi + echo "found root sheet: $sch" 1>&2 +fi + +if [ ! -z "$1" ] && [ ! -e "$1" ] || [ -d "$1" -a ! -d "$1"/.git ]; then + out="$1" + shift +else + out=$OUTDIR +fi +[ -z "$cache" ] && cache="$out" + +[ -z "$1" ] || usage + + +# --- Set up some variables and the directories for cache and output ---------- + + +PATH=`dirname "$0"`:"$PATH" +first=`gitenealogy "$dir" "$sch" | sed '$s/ .*//p;d'` +schname=`gitenealogy "$dir" "$sch" | sed '$s/^.* //p;d'` + +rm -rf "$out/*/"{diff,thumb,html,pdf} "$out/names" +$no_cache && rm -rf "$cache" +mkdir -p "$out/names" +mkdir -p "$cache" + +ppmmake '#e0e0e0' 5 30 | pnmtopng >"$out"/unchanged.png + + +# --- Generate/update the cache ----------------------------------------------- + + +head= +for n in $first `cd "$dir" && git rev-list --reverse $first..HEAD`; do + ( cd "$dir" && git show --pretty=format:'' --name-only $n; ) | + egrep -q '\.sch$|\.pro$|\.lib$' || continue + echo Processing $n + new=`gitenealogy "$dir" "$sch" | sed "/^$n /s///p;d"` + if [ ! -z "$new" ]; then + echo Name change $schname to $new 1>&2 + schname="$new" + fi + tmp=`pwd`/_schhist2web + trap "rm -rf \"$cache/$n/ps\" \"$cache/$n/ppm0\" \"$cache/$n/ppm1\" \ + \"$cache/$n/ppm2\" \"$tmp\"" 0 + if [ ! -d "$cache/$n/ppm2" ]; then + rm -rf "$cache/$n/"{ps,ppm0,ppm1,ppm2} + mkdir -p "$cache/$n/"{ps,ppm0,ppm1,ppm2} + # + # potential optimization here: remember Postscript files from previous + # run (or their md5sum) and check if they have changed. If not, skip + # the ghostscript run and just put a symlink, replacing the less + # efficient optimization below. + # + gitsch2ps $sanitize "$dir" "$schname" $n "$tmp" || exit + for m in "$tmp"/*.ps; do + # Postscript, for making PDFs later + ps="$cache/$n/ps/`basename "$m"`" + normalizeschps "$m" "$ps" || exit + + # Unadorned pixmap, for comparison + ppm="$cache/$n/ppm0/`basename "$m" .ps`.ppm" + schps2ppm -n "$ps" "$ppm" || exit + + # Pixmap with thin lines, for the detail views + ppm="$cache/$n/ppm1/`basename "$m" .ps`.ppm" + normalizeschps -w 120 "$m" | schps2ppm - "$ppm" || exit + + # Pixmap with thick lines, for the thumbnails + ppm="$cache/$n/ppm2/`basename "$m" .ps`.ppm" + normalizeschps -w 500 "$m" | schps2ppm - "$ppm" || exit + done + rm -rf "$tmp" + fi + for m in "$cache/$n/ppm0/"*; do + [ "$m" = "$cache/$n/ppm0/*" ] && break + if [ ! -z "$head" ]; then + prev="$cache/$head/ppm0/${m##*/}" + if [ -r "$prev" ] && cmp -s "$prev" "$m"; then + for d in ppm0 ppm1 ppm2; do + symlink "../../$head/$d/${m##*/}" "$cache/$n/$d/${m##*/}" + done + m_ps=${m%.ppm}.ps + symlink "../../$head/ps/${m_ps##*/}" "$cache/$n/ps/${m_ps##*/}" + fi + fi + touch "$out/names/`basename \"$m\" .ppm`" + done + trap 0 + head=$n +done + +if [ -z "$head" ]; then + echo "no usable head found" 2>&1 + exit 1 +fi + + +# --- Title of the Web page and table header ---------------------------------- + + +index="$out/index.html" +all= +{ + cat < + +EOF + if [ ! -z "$SCHHIST_TITLE" ]; then + echo "$SCHHIST_TITLE" + fi + echo "" + if [ ! -z "$SCHHIST_TITLE" ]; then + echo "

" + [ -z "$SCHHIST_HOME_URL" ] || echo "" + echo "$SCHHIST_TITLE" + [ -z "$SCHHIST_HOME_URL" ] || echo "" + echo "

" + fi + cat < + +EOF + while read m; do + ps="$cache/$head/ps/$m.ps" + if [ -r "$ps" ]; then + # + # Note: we read from variable ps_$head but we write to constant + # pdf_head. We can't use pdf_$head here, because that may just be a + # commit with a change and we thus generate a delta PDF below. + # + mkdir -p "$out/pdf_head" + schps2pdf -o "$out/pdf_head/$m.pdf" "$ps" || exit + all="$all \"$ps\"" + echo "$m" + else + echo "$m" + fi + done < <(ls -1 "$out/names") + proj=`basename "$sch" .sch` + eval schps2pdf -t \""$proj-"\" -o \""$out/pdf_$proj.pdf"\" $all + echo "All sheets" +} >"$index" + + +# --- Diff all the revisions, newest to oldest -------------------------------- + + +next="$head" +for n in `cd "$dir" && git rev-list $first..HEAD~1` $first; do + [ -d "$cache/$n/ppm0" ] || continue + empty=true + s="" + mkdir -p "$out/$next/"{diff,thumb,html,pdf} + while read m; do + a0="$cache/$n/ppm0/$m.ppm" + a1="$cache/$n/ppm1/$m.ppm" + a2="$cache/$n/ppm2/$m.ppm" + aps="$cache/$n/ps/$m.ps" + + b0="$cache/$next/ppm0/$m.ppm" + b1="$cache/$next/ppm1/$m.ppm" + b2="$cache/$next/ppm2/$m.ppm" + bps="$cache/$next/ps/$m.ps" + + diff="$out/$next/diff/$m.png" + thumb="$out/$next/thumb/$m.png" + pdf="$out/$next/pdf/$m.pdf" + + if [ -f "$a0" -a -f "$b0" ]; then + s="$s" + if ! pngdiff cat "$diff" "$a1" "$b1" "$a0" "$b0"; then + s="$s" + commit_entry "$dir" $next + fi + next=$n +done >>"$index" + + +# --- Add creation entries for all files in the first commit ------------------ + + +if [ -d "$cache/$next/ppm0" ]; then # could this ever be false ? + empty=true + echo "" + mkdir -p "$out/$next/"{diff,thumb,html,pdf} + while read m; do + p1="$cache/$next/ppm1/$m.ppm" + p2="$cache/$next/ppm2/$m.ppm" + ps="$cache/$next/ps/$m.ps" + diff="$out/$next/diff/$m.png" + thumb="$out/$next/thumb/$m.png" + pdf="$out/$next/pdf/$m.pdf" + + echo "" + [ -f "$p1" ] || continue + pngdiff cat "$diff" -f -c 0,1,0 "$p1" "$p1" || exit + pngdiff shrink "$thumb" -f $THUMB_OPTS -c 0,1,0 "$p2" "$p2" || + exit + schps2pdf -T NEW -o "$pdf" "$ps" || exit + wrapped_png "$out" "$next" "$m" + empty=false + done < <(ls -1 "$out/names") + if ! $empty; then + echo "" + commit_entry "$dir" $next + fi +fi >>"$index" + + +# --- Finish ------------------------------------------------------------------ + + +cat <>"$index" + +
+`date -u '+%F %X'` UTC + + +EOF diff --git a/schhist/schps2pdf b/schhist/schps2pdf new file mode 100755 index 0000000..7a16317 --- /dev/null +++ b/schhist/schps2pdf @@ -0,0 +1,59 @@ +#!/bin/bash +# +# schps2pdf - Generate PDF files from Eeschema Postscript +# +# Written 2010 by Werner Almesberger +# Copyright 2010 Werner Almesberger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + + +usage() +{ + cat <&2 +usage: $0 [options] [-o file.pdf] [file.ps ...] + + -t prefix make a table of content. Remove prefix from file names. + -T title make a table of content using the specified title. Each + sheet needs one -T option. +EOF + exit 1 +} + + +toc=false +out=- +while true; do + case "$1" in + -o) [ -z "$2" ] && usage + out=$2 + shift 2;; + -t) [ -z "$2" ] && usage + toc=true + prefix="$2" + shift 2;; + -T) [ -z "$2" ] && usage + toc=true + titles="$titles \"$2\"" + shift 2;; + -*) + usage;; + *) + break;; + esac +done + +in=${1:--} +shift + +eval gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=\"$out\" -f \ + `i=1; for n in "$in" "$@"; do \ + i=\`expr $i + 1\`; + echo "<(sheet=\"\`basename \"$n\" .ps\`\"; + set x $titles; eval t='\\${$i}'; + $toc && echo '[ /Title ('\"\\${t:-\\${sheet#$prefix}}\"') /OUT pdfmark'; + cat \"$n\";)"; done` diff --git a/schhist/schps2ppm b/schhist/schps2ppm new file mode 100755 index 0000000..372a8aa --- /dev/null +++ b/schhist/schps2ppm @@ -0,0 +1,57 @@ +#!/bin/sh +# +# schps2ppm - Generate PPM files from normalized Eeschema Postscript +# +# Written 2010 by Werner Almesberger +# Copyright 2010 Werner Almesberger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + + +RES=1280x850 + + +usage() +{ + cat <&2 +usage: $0 [options] [file.ps [file.ppm]] + + -n disable alpha blending + -r XxY image resolution (default: $RES) +EOF + exit 1 +} + + +alpha=true +while true; do + case "$1" in + -n) alpha=false + shift;; + -r) [ -z "$2" ] && usage + RES="$2" + shift 2;; + -) break;; + -*) usage;; + *) break;; + esac +done + +[ ! -z "$3" ] && usage +in=${1:--} +out=${2:-/dev/stdout} + +X=`echo $RES | sed 's/x.*//'` +Y=`echo $RES | sed 's/.*x//'` +IRES=${Y}x$X +res=`expr 72 \* $X / 800` + +cat "$in" | + gs -sDEVICE=ppmraw -sOutputFile=- -g$IRES -r$res \ + `$alpha && echo '' -dTextAlphaBits=4 -dGraphicsAlphaBits=4` \ + -q - | + pnmflip -r270 >"$out"