Checksumcolor

From Rosetta Code
Revision as of 16:01, 21 April 2019 by Trizen (talk | contribs) (Added Sidef)
Checksumcolor is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Context: In December 2013 a patch was proposed to the coreutils list to add a --color option to the commands md5sum and shaXsum to display the checksums in color to make it easier to visually identify similarities in a list of printed checksums. The patch was not accepted for inclusion and instead it was suggested to create a command line utility which can be used to pipe the output of the md5sum and shaXsum commands similar to the utility colordiff.

Task: The task is to create this command line utility that we can use to pipe the output of the md5sum and shaXsum commands and that colors the checksum part of the output. Take each group of 3 or 6 hexadecimal characters and interpret it as if it was a color code and print it with the closest console color. Print with colors if the output is the terminal or print the input unchanged if the output of the utility is a pipe.

Example:

$ md5sum coreutils-* | checksumcolor
ab20d840e13adfebf2b6936a2dab071b  coreutils-8.29.tar.gz
b259b2936bb46009be3f5cc06a12c32d  coreutils-8.30.tar.gz
03cf26420de566c306d340df52f6ccd7  coreutils-8.31.tar.gz


Go

Translation of: OCaml

<lang go>package main

import (

   "bufio"
   "fmt"
   "golang.org/x/crypto/ssh/terminal"
   "log"
   "os"
   "regexp"
   "strconv"

)

type Color struct{ r, g, b int }

type ColorEx struct {

   color Color
   code  string

}

var colors = []ColorEx{

   {Color{15, 0, 0}, "31"},
   {Color{0, 15, 0}, "32"},
   {Color{15, 15, 0}, "33"},
   {Color{0, 0, 15}, "34"},
   {Color{15, 0, 15}, "35"},
   {Color{0, 15, 15}, "36"},

}

func squareDist(c1, c2 Color) int {

   xd := c2.r - c1.r
   yd := c2.g - c1.g
   zd := c2.b - c1.b
   return xd*xd + yd*yd + zd*zd

}

func printColor(s string) {

   n := len(s)
   k := 0
   for i := 0; i < n/3; i++ {
       j := i * 3
       c1 := s[j]
       c2 := s[j+1]
       c3 := s[j+2]
       k = j + 3
       r, err := strconv.ParseInt(fmt.Sprintf("0x%c", c1), 0, 64)
       check(err)
       g, err := strconv.ParseInt(fmt.Sprintf("0x%c", c2), 0, 64)
       check(err)
       b, err := strconv.ParseInt(fmt.Sprintf("0x%c", c3), 0, 64)
       check(err)
       rgb := Color{int(r), int(g), int(b)}
       m := 676
       colorCode := ""
       for _, cex := range colors {
           sqd := squareDist(cex.color, rgb)
           if sqd < m {
               colorCode = cex.code
               m = sqd
           }
       }
       fmt.Printf("\033[%s;1m%c%c%c\033[00m", colorCode, c1, c2, c3)
   }
   for j := k; j < n; j++ {
       c := s[j]
       fmt.Printf("\033[0;1m%c\033[00m", c)
   }

}

var (

   r       = regexp.MustCompile("^([A-Fa-f0-9]+)([ \t]+.+)$")
   scanner = bufio.NewScanner(os.Stdin)
   err     error

)

func colorChecksum() {

   for scanner.Scan() {
       line := scanner.Text()
       if r.MatchString(line) {
           submatches := r.FindStringSubmatch(line)
           s1 := submatches[1]
           s2 := submatches[2]
           printColor(s1)
           fmt.Println(s2)
       } else {
           fmt.Println(line)
       }
   }
   check(scanner.Err())

}

func cat() {

   for scanner.Scan() {
       line := scanner.Text()
       fmt.Println(line)
   }
   check(scanner.Err())

}

func check(err error) {

   if err != nil {
       log.Fatal(err)
   }

}

func main() {

   if terminal.IsTerminal(int(os.Stdout.Fd())) {
       colorChecksum()
   } else {
       cat()
   }

}</lang>

Output:
Same as OCaml entry.


OCaml

<lang ocaml>#load "unix.cma"

  1. load "str.cma"

let colors = [|

 ((15,  0,  0), "31");
 (( 0, 15,  0), "32");
 ((15, 15,  0), "33");
 (( 0,  0, 15), "34");
 ((15,  0, 15), "35");
 (( 0, 15, 15), "36");

|]

let square_dist (r1, g1, b1) (r2, g2, b2) =

 let xd = r2 - r1 in
 let yd = g2 - g1 in
 let zd = b2 - b1 in
 (xd * xd + yd * yd + zd * zd)

let print_color s =

 let n = String.length s in
 let k = ref 0 in
 for i = 0 to pred (n / 3) do
   let j = i * 3 in
   let c1 = s.[j]
   and c2 = s.[j+1]
   and c3 = s.[j+2] in
   k := j+3;
   let rgb =
     int_of_string (Printf.sprintf "0x%c" c1),
     int_of_string (Printf.sprintf "0x%c" c2),
     int_of_string (Printf.sprintf "0x%c" c3)
   in
   let m = ref 676 in
   let color_code = ref "" in
   Array.iteri (fun i (color, code) ->
     let sqd = square_dist color rgb in
     if sqd < !m then begin
       color_code := code;
       m := sqd;
     end
   ) colors;
   Printf.printf "\027[%s;1m%c%c%c\027[00m" !color_code c1 c2 c3;
 done;
 for j = !k to pred n do
   let c = s.[j] in
   Printf.printf "\027[0;1m%c\027[00m" c;
 done

let r = Str.regexp "^\\([A-Fa-f0-9]+\\)\\([ \t]+.+\\)$"

let color_checksum () =

 try while true do
   let line = input_line stdin in
   if Str.string_match r line 0
   then begin
     let s1 = Str.matched_group 1 line in
     let s2 = Str.matched_group 2 line in
     print_color s1;
     print_endline s2;
   end
   else print_endline line
 done with End_of_file -> ()

let cat () =

 try while true do
   let line = input_line stdin in
   print_endline line
 done with End_of_file -> ()

let () =

 if Unix.isatty Unix.stdout
 then color_checksum ()
 else cat ()</lang>
Output:
$ md5sum coreutils-* | ocaml checksumcolor.ml
ab20d840e13adfebf2b6936a2dab071b  coreutils-8.29.tar.gz
b259b2936bb46009be3f5cc06a12c32d  coreutils-8.30.tar.gz
03cf26420de566c306d340df52f6ccd7  coreutils-8.31.tar.gz

Perl 6

Works with: Rakudo version 2019.03

To determine the colors, rather than breaking the md5sum into groups of 3 characters, (which leaves two lonely character at the end), I elected to replicate the first 5 characters onto the end, then for each character, used it and the 5 characters following as a true-color index. I also added an option to output as HTML code for ease of pasting in here.

<lang perl6>unit sub MAIN ($mode = 'ANSI');

if $*OUT.t or $mode eq 'HTML' { # if OUT is a terminal or if in HTML $module

say '

'
     if $mode eq 'HTML';
   while my $line = get() {
       my $cs  = $line.words[0];
       my $css = $cs ~ $cs.substr(0,5);
       given $mode {
           when 'ANSI' {
               print "\e[48;5;232m";
               .print for $css.comb.rotor(6 => -5)».map({ ($^a, $^b).join })\
               .map( { sprintf "\e[38;2;%d;%d;%dm", |$_».parse-base(16) } ) Z~ $cs.comb;
               say "\e[0m  {$line.words[1..*]}";
           }
           when 'HTML' {
               print "$_\" for $css.comb.rotor(6 => -5)\
               .map( { "" } ) Z~ $cs.comb;
               say "  {$line.words[1..*]}";
               say '
'; } default { say $line; } } }
say '

' if $mode eq 'HTML';

} else { # just pass the unaltered line through

   .say while $_ = get();

} </lang>

Can't really show the ANSI output directly so show the HTML output. Essentially identical.

Output:
md5sum *.p6 | perl6 checksum-color.p6 HTML > checksum-color.html

yields:

f09a3fc8551d8a703d64d8e918ece236 checksum-color (another copy).p6
f09a3fc8551d8a703d64d8e918ece236 checksum-color (copy).p6
f09a3fc8551d8a703d64d8e918ece236 checksum-color.p6
bbd8a92c326c8a35e80d2d71ab8902cd something-competely-different.p6

Sidef

<lang ruby>var ansi = frequire("Term::ANSIColor")

func colorhash(hash) {

   hash.split(2).map{|s| ansi.colored(s, "ansi" + s.hex) }.join

}

ARGF.each {|line|

   if (STDOUT.is_on_tty && (line =~ /^(xdigit:+)(.*)/)) {|m|
       say (colorhash(m[0]), m[1])
   }
   else {
       say line
   }

}</lang>

Output:

% md5sum *.sf | sf checksumcolor.sf
f8ac04c1857c109145e1bf0fe25550d2 checksumcolor (copy).sf
f8ac04c1857c109145e1bf0fe25550d2 checksumcolor.sf
af086941b6dc67001cd831bb1f22c3ed farey.sf
b585c25146e94df370ec48466911d9ae pell.sf

zkl

Translation of: OCaml

<lang zkl>var [const] colorRGBs=T(T(15, 0, 0), T(0 ,15, 0), T(15, 15, 0), T( 0, 0, 15), T(15, 0, 15), T( 0, 15, 15) ),

           colorTxt =T("31","32","33","34","35","36");  // esc[<ab>m

fcn squareDist(rgb1,rgb2){ rgb2.zipWith('-,rgb1).apply("pow",2).sum(0) } fcn colorize(chksum){ // "check sum" --> ansi color escape sequence

  k:=chksum.len()/3*3; // every three digits gets its own color
  chksum[0,k].pump(String,T(Void.Read,2), fcn(r,g,b){
     // find color closest to these three digits of check sum
     // minMaxNs returns indexes of min and max (in list of ints)
     vm.arglist.apply("toInt",16) : // f("a","b","c")-->(10,11,12)
       colorRGBs.apply(squareDist.fp(_)) : (0).minMaxNs(_)[0] : colorTxt[_] :
       "\e[%s;1m%c%c%c".fmt(_, r,g,b)
  })
  .append("\e[0m",chksum[k,*]); // reset color, rest of check sum

}</lang> Fake "md5sum coreutils-* | zkl checksumcolor" for testing <lang zkl>re,lines := RegExp("([A-Fa-f0-9]+)([ \t]+.+)"),

  1. <<<

"ab20d840e13adfebf2b6936a2dab071b coreutils-8.29.tar.gz b259b2936bb46009be3f5cc06a12c32d coreutils-8.30.tar.gz 03cf26420de566c306d340df52f6ccd7 coreutils-8.31.tar.gz"

  1. <<<

.split("\n");

foreach line in (lines){

  if(re.search(line)){
     chksum,txt := re.matched[1,*];
     println(colorize(chksum),txt);
  }

}</lang>

Output:
Same as the OCaml entry

This is what we would do to implement "md5sum chksum.zkl | zkl chksum" (instead of the above test code) <lang zkl>re:=RegExp("([A-Fa-f0-9]+)([ \t]+.+)"); foreach line in (File.stdin){

  if(re.search(line)){
     chksum,txt := re.matched[1,*];
     println(colorize(chksum),txt);
  } else print(line);

}</lang>