Samstag, August 15, 2020

AssertJ und java.util.List

AssertJ hat eine praktische Möglichkeit, Listen in JUnit Tests abzuprüfen. Insbesondere, wenn in der Liste komplexe Objekte abgelegt sind, spielt es seine Stärke aus. Über die Methode #extracting(...) werden die Eigenschaften angegeben, die aus dem Objekt per getter ausgelesen werden sollen. Die ausgelesenen Eigenschaften werden über #tuple(...) miteinander verglichen. Das ist dann praktisch, wenn nicht, wie in diesem Beispiel, die Objekte als Instanzen zur Verfügung stehen.
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;

import java.util.List;

import org.junit.jupiter.api.Test;

/**
 * Some examples to test a list.
 * 
 * @author Andre Winkler
 */
class ListAssert {

    @Test
    public void assertJdemo() {
        final Person a = Person.of("Winkler", "Andre");
        final Person b = Person.of("Winkler", "Adam");
        final Person c = Person.of("Winkler", "Lars");
        final Person d = Person.of("Winkler", "Erwin");

        final List list = List.of(a, b, c, d);

        assertThat(list).extracting("name", "firstName")
                .contains(
                        tuple("Winkler", "Andre"),
                        tuple("Winkler", "Adam"),
                        tuple("Winkler", "Lars"),
                        tuple("Winkler", "Erwin"));
    }

    private static class Person {
        private final String name;
        private final String firstName;

        private Person(String name, String firstName) {
            this.name = name;
            this.firstName = firstName;
        }

        public static Person of(String name, String firstName) {
            return new Person(name, firstName);
        }
        
        public String getName() {
            return name;
        }
        
        public String getFirstName() {
            return firstName;
        }
    }

}

Links

Donnerstag, Juni 18, 2020

Java Preview Features aktivieren

Einmal die neuen Textblöcke in Java 13/14 ausprobieren? Oder vielleicht das Switch-Case mit Strings? Einfach das neue JDK runterladen, auspacken, Pfade setzen und den richtigen Schalter bei der Compilierung einschalten!

Hier die Anleitung für Eclipse und Maven. IntelliJ erkennt anhand der pom.xml (Maven) wie zu Verfahren ist.

Eclipse

Package-Explorer öffnen, Projekt anwählen, rechts-klick, Properties auswählen oder Package-Explorer öffnen, Projekt anwählen und Alt-Enter drücken. Den Eintrag 'Java Compiler' auswählen, 'Enable project specific settings' einschalten und dann den Eintrag 'Enable preview features for Java X' aktivieren.

Maven

Mit Einsatz von Maven wird der Compiler-Schalter per Konfiguration in der pom.xml eingeschaltet:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <compilerArgs>--enable-preview</compilerArgs>
        <encoding>UTF-8</encoding>
        <source>14</source>
        <target>14</target>
    </configuration>
</plugin>

Java Example Project on GitHub

Donnerstag, Mai 21, 2020

VI Tastenkürzel

Die Tastensteuerung im VI kann ich mir nur grob granular merken. Deswegen dieser Versuch, die Sachen einmal aufzuschreiben und natürlich nebenbei anzuwenden. Wer eine richtige Anleitung für den VI sucht, sollte lieber woanders schauen. Dort wird vielleicht auch die Philosophie vom VI erklärt.

Grundbefehle

Die Cursorsteuerung haben meine Finger angenommen. Trotzdem zur Erinnerung:

  • j runter
  • k rauf
  • l rechts
  • h links

Um in den Insert-Modus zu wechseln, drücke ich i. Mit ESC kann ich diesen wieder verlassen. :w schreibt die Datei und mit :q verlasse ich den VI. Das Kommando 'schreiben und verlassen' lässt sich auch zusammen fassen mit :wq.

Ok. Soweit sind mir die Dinge geläufig.

Fortgeschrittene Navigation

  • ^ Cursor auf das erste Zeichen der Zeile. Unter Windows mit Cygwin muss ich die Taste zweimal drücken.
  • $ Cursor auf das letzte Zeichen der Zeile.
  • G oder :$ An das Dateiende springen.

Seitenweise Navigation

  • <ctrl>d Halbe Seite nach unten springen.
  • <ctrl>u Halble Seite nach oben springen.
  • H M L Zum Kopf, Mitte oder Ende der Seite springen.
  • w Wort nach rechts springen.
  • W Cursor auf das Leerzeichen vor dem nächsten Wort.
  • b Wort nach links springen.
  • B Cursor auf das Leerzeichen vor vorherigen Wort.

Zeile löschen

dd oder mit einer Zahl voran gestellt 4dd werden 4 Zeilen gelöscht. Gelöschte Zeilen werden zwischen gespeichert und können mit p wieder eingefügt werden.

Suchen

/<Suchbegriff> Sucht nach dem ersten auftreten des Suchbegriffs. Falls der Suchbegriff gefunden wird, positioniert VI den Cursor an diese Stelle.

Suchen und ersetzen

:%s/<Suchbegriff>/<Ersetzung>/g

TODO: Ergänzungen folgen…?!?

Sonntag, März 29, 2020

ZSH aktualisieren unter cygwin

DISCLAIMER: Das ist kein ZSH Problem. Um es mal vorweg zu nehmen, der Schuldige sitzt, wie in vielen anderen Fällen, vor dem Monitor.

Was ist ZSH: ZSH ist eine Sammlung von Aliasen und hilfreichen Bash-Shell Skripten zur Verwendung unter Linux. Auf meinem Windows Rechner verwende ich cygwin, um so ein wenig der schönen Linux Welt auf meinem Rechner zu haben.

Vor cygwin 1.7.9 war es möglich, cygwin so zu konfigurieren, dass CRLF Dateien verarbeitet werden können. Diese Option gibt es in den aktuellen Versionen nicht mehr.

GIT lasse ich per cygwin installieren. Und war auch schon da, als ich mich entschied, ZSH zu installieren. Damals habe ich den Schalter core.autocrlf auf true gestellt. Warum? Das erschien mir praktisch…

Cygwin und Linux erwarten ein einfaches LF (LineFeed), während DOS/Windows auf CRLF (Carriage Return und LineFeed) setzt. Das ist vor allem dann problematisch, da der Shell-Interpreter CRLF nicht mag. Die bekannten Texteditoren können mit diesem Problem heute wunderbar umgehen.

Und jetzt das Drama: Nach der ersten Aktualisierung von ZSH waren ein oder zwei Dateien nicht mehr ausführbar. Was habe ich getan? Ich habe die Dateien mit einem Editor geöffnet und das richtige Linefeed eingetragen. Da die ganzen ZSH Dateien in einem GIT Repository liegen, die Änderungen im Anschluss commited. Mit der nächsten ZSH Aktualisierung kam es zu den gleichen Problemen. Zusätzlich hatte ich einige Merge-Konflikte. Und irgendwann habe ich mich auf die Suche nach einer Lösung begeben.

Diese lautet:

git config --global autocrlf false

Und das ist auch die Default-Einstellung, wenn man GIT installiert.

Allerdings gibt es unter Windows noch zwei weitere GIT Konfigurationen, die Einfluß nehmen können:

git config --global --edit
git config --system --edit
git config --local --edit

Letzters funktioniert nur in einem lokalen GIT Repository.

Mit git config --global -l kann man sich alle Schalter anschauen.

Bevor ich auf diese einfache Lösung gekommen bin, habe ich verschiedene andere hilflose Versuche unternommen:

  • Alle Änderungen aus dem Remote-Repository ungesehen übernehmen.

    git pull -s recursive -X theirs <remoterepo or other repo>

    Das verhindert aber nicht die autocrlf Konvertierung.

  • Oder

    git pull -X theirs

  • Oder falls die Dateien bereits in einem konfliktbehaftetem Zustand sind:

    git checkout --theirs path/to/file

  • Ein SED Skript, um die autocrlf Konvertierung wieder rückgängig zu machen: (Entfernt alle \r aus einer Textdatei):

  sed -i 's/\r$//' script

Aber wie gesagt / TLDR: autocrlf = false.

Oder in das lokale ZSH-GIT Repository wechseln:

cd ~/.oh-my-zsh git config core.autocrlf false

Freitag, Januar 17, 2020

Jenkins mit Tomcat unter Ubuntu auf einem Raspi 3

Grundlage sind ein Rapsi 3 und ein Ubuntu Server Image. Die Paketauswahl erscheint mir dort größer. Außerdem erhält man ein 'echtes' Server OS.

Einloggen geht initial über ssh mit der Kombination ubuntu/ubuntu.

Als erstes ein Update fahren:

sudo apt-get update
sudo apt-get upgrade

Das kann eine Weile in Anspruch nehmen. Eventuell werden diese Befehle geblockt, da der Server bereits selbständig Updates zieht.

Im nächsten Schritt lege ich mir einen Benutzer 'tomcat' an. In einigen Tutorials wird empfohlen einen User ohne Login-Rechte angzulegen. Das ist unter Sicherheitsaspekten vermutlich sinnvoll. Ich finde es allerdings praktisch, wenn ich mich direkt unter dem User einloggen kann. Der Raspi läuft bei mir nur im lokalen Netz.

sudo adduser tomcat

Meine benötigten Tools installiere ich unter dem Verzeichnis /opt/devtools/. Dazu gibt es für jedes Tool ein separatest Unterverzeichnis.

sudo mkdir /opt/devtools
sudo mkdir /opt/devtools/java
sudo mkdir /opt/devtools/maven
sudo mkdir /opt/devtools/node
sudo mkdir /opt/devtools/groovy
sudo mkdir /opt/devtools/ant

Die Tools hole ich mir mittels wget. Also z.B.

cd /opt/devtools/maven
sudo wget http://mirror.netcologne.de/apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz

Im Anschluss entpacken

tar -xvf apache-maven-3.6.3-bin.tar.gz

und einen symbolischen Link anlegen, der auf die latest Version verweist.

sudo ln -s /opt/devtools/maven/apache-maven-3.6.3 /opt/devtools/maven/latest

Tools wie ein aktuelles JDK oder NodeJS installiert man sich am besten über die Paketverwaltung von Ubuntu. In dieser findet man aktuelle Versionen des JDKs bzw. NodeJS. Im Raspian OS sind diese nicht auf dem neusten Stand.

Ein aktuellen Tomcat ziehe ich mir direkt aus dem Download Bereich des Jakarta Projekts. Also gleiches Spiel wie mit Maven. Im Anschluss vergebe ich die Benutzerrechte an Tomcat:

cd /opt/devtools/tomcat
sudo chown -R tomcat:tomcat /opt/devtools/tomcat

Für eine produktive Tomcat Instanz wären das zu weitgehende Rechte. Hier würde ich empfehlen, zumindest die Verzeichnisse bin, conf und lib auszuschließen, so das der Tomcat User nur auf die Verzeichnisse webapps, work, logs, temp schreibend zugreifen kann.

Damit der Tomcat bei einem Neustart automatisch mit hochgefahren wird, binden wir diesen als Systemd Service in das OS ein: sudo vi /etc/systemd/system/tomcat.service Und in die Datei landet dann der folgende Inhalt (Pfadangaben sind ggf. anzupassen).

[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking

Environment=JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64/jre
Environment=CATALINA_PID=/opt/devtools/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/devtools/tomcat/latest
Environment=CATALINA_BASE=/opt/devtools/tomcat/latest
Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'

ExecStart=/opt/devtools/tomcat/latest/bin/startup.sh
ExecStop=/opt/devtools/tomcat/latest/bin/shutdown.sh

User=tomcat
Group=tomcat
UMask=0007
RestartSec=10
Restart=always

[Install]
WantedBy=multi-user.target

Im Anschluss den Daemon neu laden und den Tomcat starten, bzw. den Status abfragen.

sudo systemctl daemon-reload
sudo systemctl start tomcat
sudo systemctl status tomcat

Damit wäre die Basis angerichtet. Im nächsten Abschnitt deploye ich das Jenkins WAR. Aktuelle WARs finden sich auf der Jenkins Homepage. Und der Rest ist dann sehr einfach. Die WAR Datei wird in das webapps Verzeichnis vom Tomcat kopiert. Der Raspi braucht eine Weile, um die Anwendung zu starten. Nach einigen/vielen Minuten kann man die ersten Projekte anlegen. Das Initialpasswort findet sich unter:

sudo more /home/tomcat/.jenkins/secrets/initialAdminPassword

Man sollte von der Performance keine Wunder erwarten.

Sonntag, November 17, 2019

Java, Date, Rest, Javascript,…

In der Aufzählung oben fehlen SpringBoot, WebComponents und irgendwas mit Containern oder Cloud. Also hier ist das Beispielprojekt auf Github. Die Anwendung mit

git clone git@github.com:gluehloch/springboot-demo.git

und

mvn clean package

bauen. Im Anschluss das gepackte Jar mit

java -jar ./target/restdemo-1.0.jar

starten. Unter der Adresse http://localhost:8080/demo/ping findet sich eine Response mit verschiedenen Datumsformaten wieder.

Die Anwendung verwendet Spring-Boot bzw. das eingebaute Jackson für die Serialisierung/Deserialisierung von Objekten. Über die application.properties kann das allgemeine Verhalten von Jackson eingestellt werden. Die folgenden Eigenschaften sind dafür zuständig:

spring.jackson.serialization.write_dates_as_timestamps = false
spring.jackson.date-format = yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone = Europe/Berlin

Ich denke, die Schalter oben sind weitestgehend selbsterklärend. Auf die Timezone/Länderkennzeichen würde ich nicht verzichten. Auch wenn mir bekannt ist, wo der Server steht und die Konsumenten vermutlich nur aus Deutschland kommen. Allein dieser Denkansatz ist diskutabel. In der Beispielanwendung habe ich mich für obigen Einstellungen entschieden. Für java.util.Date erhält man damit die folgende Ausgabe
Z.B. 2019-10-17 18:49:10. Wer es genauer mag, packt die Millisekunden mit hinten dran. Genauere Informationen finden sich in der Klasse PingDateTime. Dort habe ich verschiedene Datumsformaten hinterlegt.

Die nächsten Abschnitte gliedern sich in einen Server und einen Client Teil. Der Server ist mit Java und SpringBoot implementiert. Das Frontend besteht aus einer HTML und einer Javascript Datei.

Server

RestController:

Hier ein Auszug aus dem Spring-Boot RestController:

@RestController
@RequestMapping("/demo")
public class DateTimeController {

    @GetMapping("/ping")
    public PingDateTime ping() {
        PingDateTime pdt = new PingDateTime();
        pdt.setDateTime(new Date());
        return pdt;
    }

}

Interessant ist die Klasse PingDateTime. Für die Datum/String Transformation ist Jackson verantwortlich. Mit der Annotation @JsonFormat in der Klasse DateTimeJson wird der Serialisierungsprozess gesteuert. Out-of-the-box funktioniert die Annotation nur mit den Java Datentypen java.util.Date und java.time.LocalDate bzw. java.time.LocalDateTime. Für den Joda org.joda.time.DateTime Datentyp gibt es auf GitHub eine Extension für Jackson.

Fehlt die Annotation, versucht der Serialisierer Jackson eine Objekt-Dekonstruktion per Reflection. Im Falle von java.util.Date werden die Millisekunden (Unix-Timestamp) zurückgeliefert. In der Spring-Boot Anwendung wird dateTimeMillies formatiert ausgegeben trotz fehlender @JsonFormat Angabe. Das Joda DateTime Objekt erfährt bei der Serialisierung eine detailreiche Dekonstruktion, wenn nicht die oben genannten Extension verwendet wird.

Eine Bemerkung zu dem Kürzel "UTC". "UTC" ist die sogenannte Weltzeit (Coordinated Universal Time). Diese definiert keine Zeitzonen und gilt einheitlich auf der ganzen Welt. 'UTC' ist im strengen Sinne keine Zeitzone. Die Parameterbezeichnung 'timezone' für den Typ 'UTC' ist also nicht ganz korrekt. Die Verwendung von "UTC" hat dann einen Vorteil, wenn die Konsumenten in verschiedenen Zeitzonen sitzen.

Das Datumsangaben ohne Zeitzonen keine gute Idee sind, ist auch der Grund, warum java.util.Date durch java.time.LocalDateTime ersetzt werden sollte.

JSON Objekt

Das REST/JSON Modell mit den verschiedenen Formatangaben:

public class PingDateTime {

    // -- java.util.Date

    @JsonFormat(
        shape = JsonFormat.Shape.STRING,
        pattern = "dd.MM.yyyy HH:mm",
        locale = "de_DE",
        timezone = "Europe/Berlin")
    private Date dateTimeBerlin;

    @JsonFormat(shape =
        JsonFormat.Shape.STRING,
        pattern = "dd.MM.yyyy HH:mm:ss.SSSZ",
        locale = "de_DE",
        timezone = "Europe/Berlin")
    private Date dateTimeBerlinWithMilli;

    @JsonFormat(
        shape = JsonFormat.Shape.STRING,
        pattern = "dd.MM.yyyy HH:mm",
        locale = "de_DE",
        timezone = "UTC")
    private Date dateTimeUTC;

    private Date dateTimeWithoutFormatDefinition;

    // -- Joda DateTime

    @JsonFormat(
        shape = JsonFormat.Shape.STRING,
        pattern = "dd.MM.yyyy HH:mm",
        locale = "de_DE",
        timezone = "Europe/Berlin")
    private DateTime jodaDateTimeBerlin;

    // -- java.time.LocalDateTime

    @JsonFormat(
        shape = JsonFormat.Shape.STRING,
        pattern = "dd.MM.yyyy HH:mm",
        locale = "de_DE",
        timezone = "UTC")
    private LocalDateTime localDateTimeUTC;

    @JsonFormat(
        shape = JsonFormat.Shape.STRING,
        pattern = "dd.MM.yyyy HH:mm",
        locale = "de_DE",
        timezone = "Europe/Berlin")
    private LocalDateTime localDateTimeBerlin;

    // getter and setter ...

    /**
     * Initialisierung.
     */
    public void setDateTime(Date dateTime) {
        this.dateTimeUTC = dateTime;
        this.dateTimeBerlin = dateTime;
        this.dateTimeBerlinWithMilli = dateTime;
        this.dateTimeWithoutFormatDefinition = dateTime;
        this.localDateTimeUTC =
            dateTime.toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime();
        this.localDateTimeBerlin =
            dateTime.toInstant().atZone(ZoneId.of("Europe/Berlin")).toLocalDateTime();
        this.jodaDateTimeBerlin = DateTime.now();
    }

}

In Projektverzeichnis findet sich die Datei rest.http. Wer VisualCode mit dem Rest-Plugin verwendet, kann sich mit Hilfe dieser Datei die Response bequem im Editor anschauen. Ebenfalls empfehlenswert ist das Tool httpie. Mit dem Kommandozeilenaufruf

http http://localhost:8080/demo/ping

erhält man eine nett formatierte Ausgabe der Response:

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 15 Nov 2019 18:26:13 GMT
Connection: close

{
  "dateTimeBerlin": "15.11.2019 19:26",
  "dateTimeUTC": "15.11.2019 18:26",
  "dateTimeWithoutFormatDefinition": 1573842373004,
  "jodaDateTimeBerlin": "15.11.2019 19:26",
  "localDateTimeUTC": "15.11.2019 18:26",
  "localDateTimeBerlin": "15.11.2019 19:26"
}

Mit curl würde das natürlich auch funktionieren.

Client

Als Konsumenten für den REST Service habe ich mir eine kleine Javascript 'Anwendung' gebaut, die diesen Servie anzapft und die aktuelle Uhrzeit darstellt.

Der Client besteht aus zwei Dateien: index.html und DateTimeController.js. In der HTML Seite ist das Element <date-time></date-time> spannend. Das ist der Aufhänger für die WebComponent, die in der JS Datei definiert wird. Kurze Anmerkung: Im Internet Explorer oder im Browser Edge funktioniert das Beispiel nicht. Siehe canisuse.

WebComponent

In der Datei DateTimeController.js finden sich zwei Klassen. Einen Controller für das anzapfen des REST Services auf Server Seite. Interessant ist die Funktion date(). Diese definiert ein sogenanntes Promise. Aufgabe: Abfrage der REST Schnittstelle und im Erfolgsfall die Response nach JSON konvertieren oder eine Fehlermeldung in der Konsole ausgeben.

export default class DateTimeController {
    constructor() {
        this.$dateTime = null; // Hier speichern wir die aktuelle Uhrzeit.
    }

    date() {
        return new Promise((resolve, reject) => {
            fetch('./demo/ping').then(response => {
                    return response.json();
                }).then(data => {
                    resolve(data);
                }).catch(err => {
                    console.error(err);
                    reject(err);
                });
        });
    }

    getCurrentDateTime(callback) {
        this.date().then(dateTime => {
            this.storeDate(dateTime);
            callback(dateTime);
        })
    }

    storeDate(dateTime) {
        this.$dateTime = dateTime;
        console.log(dateTime);
    }
}

Im nächsten Schritt wird ein DOM Element angelegt. Dieses enthält den Button zur Abfrage der Uhrzeit und ein Ausgabeelement zur Anzeige der selbigen.

const template = document.createElement('template');
template.innerHTML = `<button>Get Time</button><br/><h3>Uhrzeit:</h3><div id="dateTime"></div>`;

Die WebComponent selbst findet sich im nächsten Code-Schnipsel:

class DateTimeElement extends HTMLElement {

    constructor() {
        super();
        this.dateTimeController = new DateTimeController();

        this._shadowRoot = this.attachShadow({ 'mode': 'open' });
        this._shadowRoot.appendChild(template.content.cloneNode(true));
        this.$dateTime = this._shadowRoot.querySelector('#dateTime');

        this.$getTimeButton = this._shadowRoot.querySelector('button');
        this.$getTimeButton.addEventListener('click', (e) => {
            this.getDateTime();
        });
    }

    getDateTime() {
        this.dateTimeController.getCurrentDateTime((dateTime) => {
            this.$dateTime.innerHTML = dateTime.dateTimeBerlinWithMilli;
        });
    }

    render(dateTime) {
        this.$dateTime.innerHTML = dateTime.dateTimeBerlin;
    }

    connectedCallback() {
        console.log('connected!');
    }

    disconnectedCallback() {
        console.log('disconnected!');
    }

    attributeChangedCallback(name, oldVal, newVal) {
        console.log(`Attribute: ${name} changed!`);
    }

    adoptedCallback() {
        console.log('adopted!');
    }
}

window.customElements.define('date-time', DateTimeElement);

Fertig.

AngularIO

Zum Abschluss und als Ergänzung: In AngularIO würde ich ein Javascript Date immer mit dem folgenden Ausdruck 'pipen':

{{model.dateTime | date: 'dd.MM.yyyy HH:mm': '+0000'}}

Referenzen:

Sonntag, Oktober 27, 2019

Windows Powershell Enhancements

DOS-Box: Next Generation

Wer als Entwickler behauptet, er arbeitet unter Windows, der erntet von seinen Linux/MacOS Kollegen ein müdes lächeln. Die Ursache dafür scheint allein in der vernachlässigten Shell zu liegen. Bei den IDEs sah es bei Microsoft nie schlecht aus. Eher war die Windows Plattform hier Vorreiter, wenn man an Tools wie Visual Studio oder Eclipse in früheren Jahren denkt.

In einer Sache sind Linux und MacOS den Windows Besitzern immer noch weit überlegen: Die Kommandozeile, Shell oder Terminal kann unter Linux / MacOS einfach mehr. Zwar kann der Windows Besitzer cygwin installieren, eine perfekte Integration ist das nicht (immer). Man denke nur an die unterschiedlichen Pfade: ‘/’ statt ‘\’. Mit oder ohne /cygwin Root. Oder die Tools die doppelt installiert werden, z.B. git oder ssh. Das hat mittlerweile auch Microsoft erkannt und rüstet an dieser Front auf, um (mehr) Entwickler auf die Windows Plattform zu ziehen. Eine der vielen Initiativen ist die Powershell. Ich bin kein Spezialist, was Shell Skripte anbelangt, aber so richtig anfreunden konnte ich mich mit der Powershell nie. Wenn ich Skripte unter Windows schreibe, dann für die ‘bash’ Shell unter Verwendung von cygwin. Aber ich komme vom Thema ab.

Windows Terminal

Das neue Angebot an die Windows Entwickler heißt Windows-Terminal. Die Applikation befindet sich in der Betaphase. Funktioniert allerdings zuverlässig: Windows Terminal Preview.

oh-my-zsh

Die heißeste Killer-Shell unter Linux ist momentan oh-my-zsh. Auch unter cygwin verfügbar. oh-my-zsh bietet neben den visuellen Eindrücken interessante Erweiterungen, die aus meiner Sicht, vor allem den Umgang mit GIT betreffen.

oh-my-posh / posh-git

Inspiriert von der oh-my-zsh Shell-Erweiterung, gibt es eine Variante für die Powershell. oh-my-posh bzw. posh-git.

oh-my-posh wird zusammen mit posh-git installiert. Bevor die Installation gestartet werden kann, muss vermutlich eine Sicherheitseinstellung der Powershell geändert werden. Dazu öffnet man als Administrator die Powershell und setzt den folgenden Befehl ab:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Danach wechselt man wieder in die ‘normale’ Powershell (ohne Admin-Rechte) und kann die folgenden Befehle ausführen:

Install-Module posh-git -Scope CurrentUser
Install-Module oh-my-posh -Scope CurrentUser

Im Anschluss wird das ‘Theme’ gesetzt:

Set-Theme Agnoster

Das ist mein Favorit. Weitere Themes finden sich auf der Projektseite von oh-my-posh/themes.

Damit oh-my-posh bei jedem öffnen der Powershell gestartet wird, muss die Konfiguration angepasst werden.

notepad $PROFILE

und dort die Zeilen

Import-Module posh-git
Import-Module oh-my-posh
Set-Theme Agnoster

eintragen. Und falls ihr in ein GIT Verzeichnis wechselt, wird euch auffallen, dass einige GIT-Repo Informationen farblich dargstellt werden. Falls ihr nicht den passenden Zeichensatz installiert und ausgewählt habt, werden einige Zeichen falsch dargestellt, so wie bei mir. Im Anschluss installiert man sich den passenden Zeichensatz, ergänzt um die sogenannten ‘Powerline’ Symbole.

Powerline Fonts

Auf GitHub findet sich ein Powerline Repo. Das lokal auf die Platte clonen:

git clone https://github.com/powerline/fonts.git

und im Anschluss

cd fonts
.\install.ps1

starten und warten, bis alle Zeichensätze installiert sind. Oder man pickt sich nur die geneigten Zeichensätze heraus und installiert diese.

Zeichensatz einstellen

In der Powershell mit rechts klick auf die Titelzeile, Eigenschaften auswählen und dann auf den Reiter ‘Schriftart’ wechseln. Dort z.B. den Zeichensatz

"Noto Mono For Powerline"

auswählen. Im Windows Terminal geht man ebenfalls in die Titelzeile und klickt auf das Symbol rechts von ‘+’. Dort ‘Settings’ auswählen und notepad öffnet sich. Dort den Abschnitt ‘fontFace’ aus dem Beispiel kopieren und an gleicher Stelle einfügen.

// To view the default settings, hold "alt" while clicking on the "Settings" button.
// For documentation on these settings, see: https://aka.ms/terminal-documentation

{
    "$schema": "https://aka.ms/terminal-profiles-schema",

    "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",

    "profiles":
    [
        {
            // Make changes here to the powershell.exe profile
            "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
            "name": "Windows PowerShell",
            "commandline": "powershell.exe",
            "hidden": false,
            "fontFace": "Noto Mono For Powerline",
            "fontSize": 12
        },
        {
            // Make changes here to the cmd.exe profile
            "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
            "name": "cmd",
            "commandline": "cmd.exe",
            "hidden": false
        },
        {
            "guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}",
            "hidden": false,
            "name": "Azure Cloud Shell",
            "source": "Windows.Terminal.Azure"
        }
    ],

    // Add custom color schemes to this array
    "schemes": [],

    // Add any keybinding overrides to this array.
    // To unbind a default keybinding, set the command to "unbound"
    "keybindings": []
}

Fertig.

Powershell ohne Powerline

Links und Referenzen

AssertJ und java.util.List

AssertJ hat eine praktische Möglichkeit, Listen in JUnit Tests abzuprüfen. Insbesondere, wenn in der Liste komplexe Objekte abgelegt sind, s...