Obsah
1. Dasel – nástroj pro zpracování a modifikaci souborů JSON, YAML, XML a TOML
2. Instalace nástroje dasel ze zdrojových kódů
3. Čtení informací ze souborů s formátem TOML
4. Příklady jednoduchých dotazů
5. Čtení informací ze souborů s formátem YAML
6. Příklady jednoduchých dotazů
7. Filtrace na základě nepovinných atributů
8. Export výsledků do jiného formátu
9. Sekvence vs. seznam (pole) hodnot
10. Čtení informací ze souborů s formátem XML
12. Přidání nových atributů do souborů ve formátu TOML
13. Přidání globálního atributu
14. Přidání atributu do uloženého objektu
15. Vymazání celé sekce ze souboru typu TOML
16. Vymazání jednoho atributu ze souboru TOML
17. Zpracování tabulek uložených v souborech typu CSV
19. Repositář s pracovními soubory
1. Dasel – nástroj pro zpracování a modifikaci souborů JSON, YAML, XML a TOML
V praxi se velmi často setkáme s nutností čtení informací z konfiguračních souborů, popř. je nutné takové soubory nějakým způsobem modifikovat. Složitější konfigurační parametry se, zejména ve chvíli, kdy je nutné pracovat se strukturovanými daty a různými datovými typy, neukládají ani do souborů typu INI ani do .properties (ty se používají například ve světě Javy, ovšem jen v omezené míře). Namísto toho je možné využít například následující formáty:
- JSON (JavaScript Object Notation) – pravděpodobně nejznámější formát, který byl sice určen pro přenosy dat (typicky mezi webovou službou/serverem a další službou nebo klientem), ovšem dnes se s tímto formátem setkáme i v dalších odvětvích – serializace, uložení strukturovaných dat do databáze a taktéž konfigurační soubory.
- YAML (YAML Ain't Markup Language) – je formátem, který se namísto použití závorek pro určení struktury spoléhá spíše na použití odsazení (podobně, jako je tomu v Pythonu) a popř. i speciálních znaků (-, #, [, ] atd.). S tímto formátem se setkáme ve světě Dockeru a Kubernetes, ovšem je ho možné použít i pro další účely.
- XML (Extensible Markup Language) – s tímto formátem pravděpodobně není nutné čtenáře tohoto článku podrobněji seznamovat. XML se pro uložení konfiguračních parametrů používá již delší dobu, i když se z některých důvodů nemusí vždy jednat o ideální řešení.
- TOML (Tom's Obvious, Minimal Language) – formát TOML sice zdánlivě (alespoň na první pohled) vychází ze souborů typu INI, ovšem ve skutečnosti se jedná o odlišný, v mnoha ohledech vylepšený a především promyšlený formát, v němž byly odstraněny prakticky všechny nevýhody INI a přitom byla zachována čitelnost a snadnost úprav.
- edn (Extensible Data Notation) – tento formát vychází ze syntaxe a sémantiky programovacího jazyka Clojure, je tedy založen na S-výrazech rozšířených o možnost zápisu map (slovníků) a vektorů. Formát edn je rozšířen právě v ekosystému jazyka Clojure, ale v ostatních oblastech se prozatím příliš nerozšířil, takže je dnes uveden spíše pro úplnost. Popis formátu edn (a tím pádem i popis syntaxe Clojure) naleznete na stránce https://github.com/edn-format/edn.
2. Instalace nástroje dasel ze zdrojových kódů
Dasel je naprogramován v jazyce Go, takže ve chvíli, kdy máte nainstalovány základní nástroje Go (překladač atd.), je stažení, překlad a instalace Daselu vyřešena jediným příkazem:
$ go install -v github.com/tomwright/dasel/v2/cmd/dasel@master
Během instalace se stahují i všechny závislé balíčky (transitive dependencies), kterých je v tomto případě celá řada, což je ostatně patrné i z následujícího výpisu:
go: downloading github.com/tomwright/dasel v1.27.4-0.20230111120636-c9187a46f74e go: downloading github.com/tomwright/dasel/v2 v2.1.3-0.20230404173316-88bf4414bc0b go: downloading github.com/spf13/cobra v1.6.1 go: downloading github.com/alecthomas/chroma v0.10.0 go: downloading github.com/clbanning/mxj/v2 v2.5.7 go: downloading golang.org/x/net v0.8.0 go: downloading github.com/dlclark/regexp2 v1.4.0 go: downloading golang.org/x/text v0.8.0 golang.org/x/text/internal/utf8internal golang.org/x/net/html/atom golang.org/x/text/encoding/internal/identifier golang.org/x/text/transform golang.org/x/text/internal/tag github.com/tomwright/dasel/v2/internal github.com/tomwright/dasel/v2 github.com/dlclark/regexp2/syntax github.com/clbanning/mxj/v2 github.com/spf13/cobra github.com/pelletier/go-toml golang.org/x/net/html golang.org/x/text/internal/language golang.org/x/text/encoding golang.org/x/text/encoding/internal golang.org/x/text/encoding/charmap golang.org/x/text/encoding/japanese golang.org/x/text/encoding/korean github.com/dlclark/regexp2 golang.org/x/text/encoding/simplifiedchinese golang.org/x/text/encoding/traditionalchinese golang.org/x/text/runes golang.org/x/text/encoding/unicode golang.org/x/text/internal/language/compact golang.org/x/text/language github.com/alecthomas/chroma github.com/alecthomas/chroma/formatters/html github.com/alecthomas/chroma/lexers/internal github.com/alecthomas/chroma/formatters/svg github.com/alecthomas/chroma/styles github.com/alecthomas/chroma/lexers/a github.com/alecthomas/chroma/lexers/b github.com/alecthomas/chroma/formatters github.com/alecthomas/chroma/lexers/p github.com/alecthomas/chroma/lexers/j github.com/alecthomas/chroma/lexers/e github.com/alecthomas/chroma/lexers/f github.com/alecthomas/chroma/lexers/i github.com/alecthomas/chroma/lexers/d github.com/alecthomas/chroma/lexers/c github.com/alecthomas/chroma/lexers/k github.com/alecthomas/chroma/lexers/l github.com/alecthomas/chroma/lexers/n github.com/alecthomas/chroma/lexers/o github.com/alecthomas/chroma/lexers/q github.com/alecthomas/chroma/lexers/r github.com/alecthomas/chroma/lexers/t github.com/alecthomas/chroma/lexers/v github.com/alecthomas/chroma/lexers/w github.com/alecthomas/chroma/lexers/x github.com/alecthomas/chroma/lexers/y github.com/alecthomas/chroma/lexers/z github.com/alecthomas/chroma/lexers/h github.com/alecthomas/chroma/lexers/circular github.com/alecthomas/chroma/lexers/g github.com/alecthomas/chroma/lexers/m github.com/alecthomas/chroma/lexers/s github.com/alecthomas/chroma/lexers github.com/alecthomas/chroma/quick golang.org/x/text/encoding/htmlindex golang.org/x/net/html/charset github.com/tomwright/dasel/v2/storage github.com/tomwright/dasel/v2/internal/command github.com/tomwright/dasel/v2/cmd/dasel
Výsledkem překladu by měl být spustitelný soubor dasel:
$ whereis -b dasel dasel: /home/ptisnovs/go/bin/dasel
Jedná se přitom o pěkného „bumbrlíčka“, a to kvůli statickému slinkování s celou řadou tranzitivně závislých balíčků:
$ ls -l -h /home/ptisnovs/go/bin/dasel -rwxrwxr-x 1 ptisnovs ptisnovs 14M Apr 8 08:40 /home/ptisnovs/go/bin/dasel
3. Čtení informací ze souborů s formátem TOML
V navazujících kapitolách si ukážeme základní funkce, které nástroj Dasel uživatelům nabízí, na reálných konfiguračních souborech uložených v několika podporovaných formátech. Začneme konfiguračním souborem, jenž používá formát TOML. Tento soubor naleznete na adrese https://github.com/tisnik/slides/blob/master/files/dasel/config.toml:
[logging] debug = true log_level = "info" logging_to_cloud_watch_enabled = false [kafka_broker] enabled = true address = "kafka:29092" #provide in deployment env or as secret security_protocol = "PLAINTEXT" cert_path = "not-set" sasl_mechanism = "PLAIN" sasl_username = "not-used" sasl_password = "not-used" topic = "platform.notifications.ingress" #provide in deployment env or as secret timeout = "60s" likelihood_threshold = 0 impact_threshold = 0 severity_threshold = 0 total_risk_threshold = 2 event_filter = "totalRisk >= totalRiskThreshold" tag_filter_enabled = false tags = [] # valid units are SQL epoch time units: months days hours minutes seconds" # set to empty string "" or 0 to disable cooldown = "1 week" [service_log] client_id = "a-service-id" client_secret = "a-secret" created_by = "service-account-service" username ="service-username" token_url = "" enabled = false url = "https://api.foobar.com/api/service_logs/v1/" timeout = "15s" likelihood_threshold = 0 impact_threshold = 0 severity_threshold = 0 total_risk_threshold = 0 event_filter = "totalRisk >= totalRiskThreshold" tag_filter_enabled = true tags = ["osd_customer"] # valid units are SQL epoch time units: months days hours minutes seconds" # set to empty string "" or "0" to disable cooldown = "1 week" [storage] db_driver = "postgres" pg_username = "postgres" #provide in deployment env or as secret pg_password = "postgres" #provide in deployment env or as secret pg_host = "localhost" #provide in deployment env or as secret pg_port = 5432 #provide in deployment env or as secret pg_db_name = "notification" #provide in deployment env or as secret pg_params = "sslmode=disable" log_sql_queries = true
4. Příklady jednoduchých dotazů
Vyzkoušejme si nyní některé základní vlastnosti poskytované nástrojem Dasel. Dotazovací jazyk je do značné míry podobný jazyku, s nímž jsme se seznámili v článku o nástroji jq. Nejprve si selektorem . (tečka) necháme vypsat obsah celého souboru, přičemž se Dasel bude snažit o jeho naformátování:
$ dasel -f config.toml "."
Výsledek:
[kafka_broker] address = "kafka:29092" cert_path = "not-set" cooldown = "1 week" enabled = true event_filter = "totalRisk >= totalRiskThreshold" impact_threshold = 0 likelihood_threshold = 0 sasl_mechanism = "PLAIN" sasl_password = "not-used" sasl_username = "not-used" security_protocol = "PLAINTEXT" severity_threshold = 0 ... ... ... total_risk_threshold = 0 url = "https://api.foobar.com/api/service_logs/v1/" username = "service-username" [storage] db_driver = "postgres" log_sql_queries = true pg_db_name = "notification" pg_host = "localhost" pg_params = "sslmode=disable" pg_password = "postgres" pg_port = 5432 pg_username = "postgres"
Podporován je ovšem i obarvený výstup (pozor na zápis „colour“ a nikoli „color“):
$ dasel --colour -f config.toml "."
Výstup:
Obrázek 1: Obarvený výstup ve formátu TOML.
Dále se pokusíme o přečtení jedné konkrétní sekce:
$ dasel -f config.toml "service_log"
Výsledek:
client_id = "a-service-id" client_secret = "a-secret" cooldown = "1 week" created_by = "service-account-service" enabled = false event_filter = "totalRisk >= totalRiskThreshold" impact_threshold = 0 likelihood_threshold = 0 severity_threshold = 0 tag_filter_enabled = true tags = ["osd_customer"] timeout = "15s" token_url = "" total_risk_threshold = 0 url = "https://api.foobar.com/api/service_logs/v1/" username = "service-username"
Přečíst můžeme i jeden vybraný atribut:
$ dasel -f config.toml "service_log.cooldown" 1 week
Přečtení dalšího atributu:
$ dasel -f config.toml "kafka_broker.address" kafka:29092
A konečně se podívejme na výsledek snahy o přečtení neexistujícího atributu:
$ dasel -f config.toml kafka_broker.address.port (nevypíše se nic, ani prázdný řádek)
5. Čtení informací ze souborů s formátem YAML
Velmi často se setkáme i s konfiguračními soubory ve formátu YAML. Příkladem může být následující soubor obsahující konfiguraci úlohy, která se spustí na straně GitHubu po každé změně obsahu repositáře. Tato úloha pro repositář vygeneruje stránky s dokumentací, které jsou poté dostupné přímo na GitHubu (takzvané GitHub pages). Tento konfigurační soubor lze nalézt na adrese https://github.com/tisnik/slides/blob/master/files/dasel/gh-pages.yml a jeho obsah vypadá následovně:
name: Build and deploy Jekyll site to GitHub Pages on: push: branches: - master jobs: github-pages: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v2 with: go-version: '^1.14.1' - name: Generate docgo run: make godoc - name: Generate Jekyll site uses: helaili/jekyll-action@v2 with: token: ${{ secrets.GITHUB_TOKEN }} jekyll_src: 'docs'
6. Příklady jednoduchých dotazů
Opět si vyzkoušejme získat nějaké informace, tentokrát ze souboru ve formátu YAML. V prvním kroku se pokusíme o přeformátování souboru, tj. o výběr všech objektů a jejich atributů:
$ dasel -f gh-pages.yml '.' jobs: github-pages: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v2 with: go-version: ^1.14.1 - name: Generate docgo run: make godoc - name: Generate Jekyll site uses: helaili/jekyll-action@v2 with: jekyll_src: docs token: ${{ secrets.GITHUB_TOKEN }} name: Build and deploy Jekyll site to GitHub Pages "true": push: branches: - master
Obarvený výstup:
$ dasel --colour -f gh-pages.yml '.'
Obrázek 2: Obarvený výstup ve formátu YAML.
Samozřejmě můžeme provést i výběr určitého objektu, popř. vybraného atributu tohoto objektu:
$ dasel -f gh-pages.yml ".jobs.github-pages.steps" - name: Checkout uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v2 with: go-version: ^1.14.1 - name: Generate docgo run: make godoc - name: Generate Jekyll site uses: helaili/jekyll-action@v2 with: jekyll_src: docs token: ${{ secrets.GITHUB_TOKEN }}
Získat můžeme například i jména všech kroků, a to následujícím způsobem – s využitím funkce all:
$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all().name' Checkout Setup Go Generate docgo Generate Jekyll site
7. Filtrace na základě nepovinných atributů
Poměrně často se setkáme s nutností získání hodnot nějakých atributů, které však nejsou nastaveny pro všechny objekty v souborech YAML (TOML atd.). Příkladem může být atribut uses, který není nastaven vždy a proto pokus o přečtení hodnot tohoto atributu (atributů) nebude úspěšný:
$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all().uses' -w json (nic se nevypíše, tedy ani prázdný řádek)
V takovém případě musíme za jméno nepovinného atributu přidat otazník a celý dotaz tedy může vypadat takto:
$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all().uses?' -w json
Výsledek již bude odpovídat očekávání:
"actions/checkout@v2" "actions/setup-go@v2" "helaili/jekyll-action@v2"
Tato vlastnost je podporována nejenom u formátu YAML, a i u dalších formátů, tedy i TOML apod. Opět si tedy tuto užitečnou vlastnost otestujeme:
$ dasel -f config.toml '.all().cooldown?' 1 week 1 week
8. Export výsledků do jiného formátu
Jednou z velmi užitečných vlastností nástroje Dasel je jeho schopnost provést export dat z jednoho podporovaného formátu do formátu jiného. Příkladem může být převod zprávy uložené původně ve formátu JSON do formátu TOML, který lze provést následovně (povšimněte si přepínače –pretty, jenž zajistí tisk výsledku v naformátované a dobře čitelné podobě):
$ dasel --pretty -f msg_with_schema.json -w toml [payload] ID = 1.0 Name = "Linus" Surname = "Torvalds" [schema] fields = [{ field = "ID", optional = false, type = "int64" }, { field = "Name", optional = false, type = "string" }, { field = "Surname", optional = false, type = "string" }] optional = false type = "struct" version = 1.0
Další konverzí, kterou si ukážeme, je převod konfiguračního souboru z formátu YAML na formát JSON:
$ dasel -f gh-pages.yml -w json { "jobs": { "github-pages": { "runs-on": "ubuntu-latest", "steps": [ { "name": "Checkout", "uses": "actions/checkout@v2" }, { "name": "Setup Go", "uses": "actions/setup-go@v2", "with": { "go-version": "^1.14.1" } }, { "name": "Generate docgo", "run": "make godoc" }, { "name": "Generate Jekyll site", "uses": "helaili/jekyll-action@v2", "with": { "jekyll_src": "docs", "token": "${{ secrets.GITHUB_TOKEN }}" } } ] } }, "name": "Build and deploy Jekyll site to GitHub Pages", "true": { "push": { "branches": [ "master" ] } } }
9. Sekvence vs. seznam (pole) hodnot
Při použití nástroje Dasel je možné kombinovat selekci (filtraci) s výstupem do jiného (zvoleného) formátu. Například následující příkaz zajistí, že se vygeneruje seznam (pole) obsahující objekty typu „step“ (resp. přesněji řečeno jejich atributy):
$ dasel -f gh-pages.yml '.jobs.github-pages.steps' -w json
Nyní bude výsledek vypadat takto:
[ { "name": "Checkout", "uses": "actions/checkout@v2" }, { "name": "Setup Go", "uses": "actions/setup-go@v2", "with": { "go-version": "^1.14.1" } }, { "name": "Generate docgo", "run": "make godoc" }, { "name": "Generate Jekyll site", "uses": "helaili/jekyll-action@v2", "with": { "jekyll_src": "docs", "token": "${{ secrets.GITHUB_TOKEN }}" } } ]
V některých případech, typicky při použití funkce all() ovšem bude výsledkem ne jednotlivý objekt, ale sekvence objektů (ovšem nikoli pole nebo seznam):
$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all()' -w json { "name": "Checkout", "uses": "actions/checkout@v2" } { "name": "Setup Go", "uses": "actions/setup-go@v2", "with": { "go-version": "^1.14.1" } } { "name": "Generate docgo", "run": "make godoc" } { "name": "Generate Jekyll site", "uses": "helaili/jekyll-action@v2", "with": { "jekyll_src": "docs", "token": "${{ secrets.GITHUB_TOKEN }}" } }
Můžeme jít ještě dále a získat sekvenci řetězcových atributů (opět se nejedná o validní JSON):
$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all().name' -w json "Checkout" "Setup Go" "Generate docgo" "Generate Jekyll site"
Podobný příklad, ovšem nyní vracející obsah původních objektů uložených za sebou v sekvenci:
$ dasel -f config.toml '.all()' -w json { "client_id": "a-service-id", "client_secret": "a-secret", "cooldown": "1 week", "created_by": "service-account-service", "enabled": false, "event_filter": "totalRisk >= totalRiskThreshold", "impact_threshold": 0, "likelihood_threshold": 0, "severity_threshold": 0, "tag_filter_enabled": true, "tags": [ "osd_customer" ], "timeout": "15s", "token_url": "", "total_risk_threshold": 0, "url": "https://api.foobar.com/api/service_logs/v1/", "username": "service-username" } { "db_driver": "postgres", "log_sql_queries": true, "pg_db_name": "notification", "pg_host": "localhost", "pg_params": "sslmode=disable", "pg_password": "postgres", "pg_port": 5432, "pg_username": "postgres" } { "debug": true, "log_level": "info", "logging_to_cloud_watch_enabled": false } { "address": "kafka:29092", "cert_path": "not-set", "cooldown": "1 week", "enabled": true, "event_filter": "totalRisk >= totalRiskThreshold", "impact_threshold": 0, "likelihood_threshold": 0, "sasl_mechanism": "PLAIN", "sasl_password": "not-used", "sasl_username": "not-used", "security_protocol": "PLAINTEXT", "severity_threshold": 0, "tag_filter_enabled": false, "tags": [], "timeout": "60s", "topic": "platform.notifications.ingress", "total_risk_threshold": 2 }
10. Čtení informací ze souborů s formátem XML
Zpracovávat lze pochopitelně i data uložená do formátu XML. Pro ilustraci použijeme soubor, který byl vytvořen nástrojem SchemaSpy a obsahuje popis schématu relační databáze. Pro účely dnešního článku byl soubor zkrácen tak, že popisuje pouze několik tabulek a nikoli celé schéma. Jeho obsah naleznete na adrese https://github.com/tisnik/slides/blob/master/files/dasel/notification.public.xml:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <database name="notification" schema="public" type="PostgreSQL - 9.6.10"> <sequences> <sequence increment="1" name="read_errors_error_id_seq" startValue="1"/> </sequences> <tables> <table name="event_targets" numRows="2" remarks="specification of all event targets currently supported" schema="public" type="TABLE"> <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="int4" typeCode="4"> <child column="event_type_id" foreignKey="reported_event_type_id_fkey" implied="false" onDeleteCascade="false" schema="public" table="reported"/> </column> <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="name" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/> <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="metainfo" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/> <primaryKey column="id" sequenceNumberInPK="1"/> <index name="event_targets_pkey" unique="true"> <column ascending="true" name="id"/> </index> <index name="event_targets_metainfo_key" unique="true"> <column ascending="true" name="metainfo"/> </index> <index name="event_targets_name_key" unique="true"> <column ascending="true" name="name"/> </index> </table> <table name="migration_info" numRows="1" remarks="information about the latest DB schema and migration status" schema="public" type="TABLE"> <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="version" nullable="false" remarks="" size="10" type="int4" typeCode="4"/> </table> <table name="states" numRows="4" remarks="states for each row stored in reported table" schema="public" type="TABLE"> <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="XXXXXXXXX" size="10" type="int4" typeCode="4"> <child column="state" foreignKey="fk_state" implied="false" onDeleteCascade="false" schema="public" table="reported"/> </column> <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="value" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/> <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="comment" nullable="true" remarks="" size="2147483647" type="varchar" typeCode="12"/> <primaryKey column="id" sequenceNumberInPK="1"/> <index name="states_pkey" unique="true"> <column ascending="true" name="id"/> </index> </table> </tables> </database>
11. Ukázkové příklady
Podívejme se nyní na několik demonstračních příkladů, které ukazují způsob zpracování XML souborů.
Pouze pretty-printing obsahu celého souboru:
$ dasel -f notification.public.xml "." <database name="notification" schema="public" type="PostgreSQL - 9.6.10"> <sequences> <sequence increment="1" name="read_errors_error_id_seq" startValue="1"/> </sequences> <tables> <table name="event_targets" numRows="2" remarks="specification of all event targets currently supported" schema="public" type="TABLE"> <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="int4" typeCode="4"> <child column="event_type_id" foreignKey="reported_event_type_id_fkey" implied="false" onDeleteCascade="false" schema="public" table="reported"/> </column> ... ... ...
Pretty printing s obarvením syntaxe:
$ dasel --colour -f notification.public.xml "."
Obrázek 3: Obarvený výstup ve formátu XML.
Získání informací o tabulkách, nikoli o dalších objektech uložených v databázi (sekvence atd.). Povšimněte si, že aby vznikl korektní XML strom, bylo nutné přidat uzel doc:
$ dasel -f notification.public.xml ".database.tables" <doc> <table name="event_targets" numRows="2" remarks="specification of all event targets currently supported" schema="public" type="TABLE"> <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="int4" typeCode="4"> <child column="event_type_id" foreignKey="reported_event_type_id_fkey" implied="false" onDeleteCascade="false" schema="public" table="reported"/> </column> <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="name" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/> <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="metainfo" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/> <index name="event_targets_pkey" unique="true"> <column ascending="true" name="id"/> </index> <index name="event_targets_metainfo_key" unique="true"> <column ascending="true" name="metainfo"/> </index> <index name="event_targets_name_key" unique="true"> <column ascending="true" name="name"/> </index> <primaryKey column="id" sequenceNumberInPK="1"/> </table> <table name="migration_info" numRows="1" remarks="information about the latest DB schema and migration status" schema="public" type="TABLE"> <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="version" nullable="false" remarks="" size="10" type="int4" typeCode="4"/> </table> <table name="states" numRows="4" remarks="states for each row stored in reported table" schema="public" type="TABLE"> <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="XXXXXXXXX" size="10" type="int4" typeCode="4"> <child column="state" foreignKey="fk_state" implied="false" onDeleteCascade="false" schema="public" table="reported"/> </column> <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="value" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/> <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="comment" nullable="true" remarks="" size="2147483647" type="varchar" typeCode="12"/> <index name="states_pkey" unique="true"> <column ascending="true" name="id"/> </index> <primaryKey column="id" sequenceNumberInPK="1"/> </table> </doc>
Pokus o převod do formátu JSON, což není přímočará transformace, neboť je nutné pracovat i s atributy uzlů. Povšimněte si, jak jsou odlišeny původní atributy od poduzlů:
$ dasel -f notification.public.xml -w json | more { "database": { "-name": "notification", "-schema": "public", "-type": "PostgreSQL - 9.6.10", "sequences": { "sequence": { "-increment": "1", "-name": "read_errors_error_id_seq", "-startValue": "1" } }, "tables": { "table": [ { "-name": "event_targets", "-numRows": "2", "-remarks": "specification of all event targets currently supported", "-schema": "public", "-type": "TABLE", "column": [ { "-autoUpdated": "false", "-defaultValue": "null", "-digits": "0", "-id": "0", "-name": "id", "-nullable": "false", "-remarks": "", "-size": "10", "-type": "int4", "-typeCode": "4", "child": { "-column": "event_type_id", "-foreignKey": "reported_event_type_id_fkey", "-implied": "false", "-onDeleteCascade": "false", "-schema": "public", "-table": "reported" } }, { "-autoUpdated": "false", "-defaultValue": "null", "-digits": "0", "-id": "1", "-name": "name", "-nullable": "false", "-remarks": "", "-size": "2147483647", "-type": "varchar", "-typeCode": "12" }, { "-autoUpdated": "false", "-defaultValue": "null", "-digits": "0", "-id": "2", "-name": "metainfo", "-nullable": "false", "-remarks": "", "-size": "2147483647", "-type": "varchar", "-typeCode": "12" } ], "index": [ { "-name": "event_targets_pkey", "-unique": "true", "column": { "-ascending": "true", "-name": "id" } }, { "-name": "event_targets_metainfo_key", "-unique": "true", "column": { "-ascending": "true", "-name": "metainfo" } }, { "-name": "event_targets_name_key", "-unique": "true", "column": { "-ascending": "true", "-name": "name" } } ], "primaryKey": { "-column": "id", "-sequenceNumberInPK": "1" } }, { "-name": "migration_info", "-numRows": "1", "-remarks": "information about the latest DB schema and migration status", "-schema": "public", "-type": "TABLE", "column": { "-autoUpdated": "false", "-defaultValue": "null", "-digits": "0", "-id": "0", "-name": "version", "-nullable": "false", "-remarks": "", "-size": "10", "-type": "int4", "-typeCode": "4" } }, { "-name": "states", "-numRows": "4", "-remarks": "states for each row stored in reported table", "-schema": "public", "-type": "TABLE", "column": [ { "-autoUpdated": "false", "-defaultValue": "null", "-digits": "0", "-id": "0", "-name": "id", "-nullable": "false", "-remarks": "XXXXXXXXX", "-size": "10", "-type": "int4", "-typeCode": "4", "child": { "-column": "state", "-foreignKey": "fk_state", "-implied": "false", "-onDeleteCascade": "false", "-schema": "public", "-table": "reported" } }, { "-autoUpdated": "false", "-defaultValue": "null", "-digits": "0", "-id": "1", "-name": "value", "-nullable": "false", "-remarks": "", "-size": "2147483647", "-type": "varchar", "-typeCode": "12" }, { "-autoUpdated": "false", "-defaultValue": "null", "-digits": "0", "-id": "2", "-name": "comment", "-nullable": "true", "-remarks": "", "-size": "2147483647", "-type": "varchar", "-typeCode": "12" } ], "index": { "-name": "states_pkey", "-unique": "true", "column": { "-ascending": "true", "-name": "id" } }, "primaryKey": { "-column": "id", "-sequenceNumberInPK": "1" } } ] } } }
12. Přidání nových atributů do souborů ve formátu TOML
Velmi užitečnou vlastností, kterou nástroj Dasel uživatelům nabízí, je možnost přidání dalších atributů do zpracovávaných datových či konfiguračních souborů. Pro tento účel slouží příkaz nazvaný put, přičemž je nutné přepínačem -t specifikovat typ atributu a přepínačem -v jeho hodnotu, popř. dvojici hodnota+klíč (v tomto pořadí!). Mezi základní podporované typy patří skalární typy bool, int a number (v podstatě typ float) a referenční typy array a object.
13. Přidání globálního atributu
Příkaz put mění obsah zpracovávaného souboru, takže si nejdříve vytvoříme jeho kopii:
$ cp config.toml config2.toml
Do souboru config2.toml nyní přidáme nový globální atribut nazvaný „priority_threshold“ a nastavíme mu celočíselnou hodnotu 1:
$ dasel put -f config2.toml -t int -v 1 "priority_threshold"
Výsledkem výše uvedeného příkazu bude modifikovaný soubor config2.toml, který bude skutečně obsahovat nový globální atribut (zobrazený je hned na prvním řádku):
priority_threshold = 1 [kafka_broker] address = "kafka:29092" cert_path = "not-set" cooldown = "1 week" enabled = true event_filter = "totalRisk >= totalRiskThreshold" impact_threshold = 0 likelihood_threshold = 0 sasl_mechanism = "PLAIN" sasl_password = "not-used" sasl_username = "not-used" security_protocol = "PLAINTEXT" severity_threshold = 0 tag_filter_enabled = false tags = [] timeout = "60s" topic = "platform.notifications.ingress" total_risk_threshold = 2 [logging] debug = true log_level = "info" logging_to_cloud_watch_enabled = false [service_log] client_id = "a-service-id" client_secret = "a-secret" cooldown = "1 week" created_by = "service-account-service" enabled = false event_filter = "totalRisk >= totalRiskThreshold" impact_threshold = 0 likelihood_threshold = 0 severity_threshold = 0 tag_filter_enabled = true tags = ["osd_customer"] timeout = "15s" token_url = "" total_risk_threshold = 0 url = "https://api.foobar.com/api/service_logs/v1/" username = "service-username" [storage] db_driver = "postgres" log_sql_queries = true pg_db_name = "notification" pg_host = "localhost" pg_params = "sslmode=disable" pg_password = "postgres" pg_port = 5432 pg_username = "postgres"
14. Přidání atributu do uloženého objektu
Samozřejmě je možné přidat nový atribut do již existujícího souboru. V tomto případě se použije stejná syntaxe, jako při výběru (selekci) atributu či atributů – využije se klasická „tečková notace“. Vše je ukázáno na následujícím demonstračním příkladu, v němž opět dojde k přidání atributu nazvaného „priority_threshold“ s hodnotou 1, tentokrát ovšem nikoli na globální úrovni, ale do objektu „service_log“:
$ cp config.toml config3.toml $ dasel put -f config3.toml -t int -v 42 "service_log.priority_threshold"
Výsledný soubor config3.toml by měl vypadat následovně. Nový atribut je zvýrazněn:
[kafka_broker] address = "kafka:29092" cert_path = "not-set" cooldown = "1 week" enabled = true event_filter = "totalRisk >= totalRiskThreshold" impact_threshold = 0 likelihood_threshold = 0 sasl_mechanism = "PLAIN" sasl_password = "not-used" sasl_username = "not-used" security_protocol = "PLAINTEXT" severity_threshold = 0 tag_filter_enabled = false tags = [] timeout = "60s" topic = "platform.notifications.ingress" total_risk_threshold = 2 [logging] debug = true log_level = "info" logging_to_cloud_watch_enabled = false [service_log] client_id = "a-service-id" client_secret = "a-secret" cooldown = "1 week" created_by = "service-account-service" enabled = false event_filter = "totalRisk >= totalRiskThreshold" impact_threshold = 0 likelihood_threshold = 0 priority_threshold = 42 severity_threshold = 0 tag_filter_enabled = true tags = ["osd_customer"] timeout = "15s" token_url = "" total_risk_threshold = 0 url = "https://api.foobar.com/api/service_logs/v1/" username = "service-username" [storage] db_driver = "postgres" log_sql_queries = true pg_db_name = "notification" pg_host = "localhost" pg_params = "sslmode=disable" pg_password = "postgres" pg_port = 5432 pg_username = "postgres"
15. Vymazání celé sekce ze souboru typu TOML
Vzhledem k existenci operace put určené pro vložení nového atributu do zpracovávaných souborů nás pravděpodobně nepřekvapí, že Dasel podporuje i operaci delete, která nějaký atribut či celý objekt naopak vymaže. V tomto případě se pochopitelně specifikuje pouze selektor objektu či atributu, nikoli hodnota. Pokusme se například vymazat celý objekt „service_log“:
$ cp config.toml config4.toml $ dasel delete -f config4.toml "service_log"
Výsledkem bude tento zkrácený soubor:
[kafka_broker] address = "kafka:29092" cert_path = "not-set" cooldown = "1 week" enabled = true event_filter = "totalRisk >= totalRiskThreshold" impact_threshold = 0 likelihood_threshold = 0 sasl_mechanism = "PLAIN" sasl_password = "not-used" sasl_username = "not-used" security_protocol = "PLAINTEXT" severity_threshold = 0 tag_filter_enabled = false tags = [] timeout = "60s" topic = "platform.notifications.ingress" total_risk_threshold = 2 [logging] debug = true log_level = "info" logging_to_cloud_watch_enabled = false [storage] db_driver = "postgres" log_sql_queries = true pg_db_name = "notification" pg_host = "localhost" pg_params = "sslmode=disable" pg_password = "postgres" pg_port = 5432 pg_username = "postgres"
16. Vymazání jednoho atributu ze souboru TOML
Nástroj Dasel pochopitelně umožňuje i vymazání jediného atributu, což je opět vlastnost, kterou si nejlépe ukážeme na souboru config.toml. Nejprve si vytvoříme kopii tohoto souboru a posléze z v něm uložených dat vymažeme atribut „debug“ objektu „logging“:
$ cp config.toml config5.toml $ dasel delete -f config5.toml "logging.debug"
Výsledek by měl vypadat následovně:
[kafka_broker] address = "kafka:29092" cert_path = "not-set" cooldown = "1 week" enabled = true event_filter = "totalRisk >= totalRiskThreshold" impact_threshold = 0 likelihood_threshold = 0 sasl_mechanism = "PLAIN" sasl_password = "not-used" sasl_username = "not-used" security_protocol = "PLAINTEXT" severity_threshold = 0 tag_filter_enabled = false tags = [] timeout = "60s" topic = "platform.notifications.ingress" total_risk_threshold = 2 [logging] log_level = "info" logging_to_cloud_watch_enabled = false [service_log] client_id = "a-service-id" client_secret = "a-secret" cooldown = "1 week" created_by = "service-account-service" enabled = false event_filter = "totalRisk >= totalRiskThreshold" impact_threshold = 0 likelihood_threshold = 0 severity_threshold = 0 tag_filter_enabled = true tags = ["osd_customer"] timeout = "15s" token_url = "" total_risk_threshold = 0 url = "https://api.foobar.com/api/service_logs/v1/" username = "service-username" [storage] db_driver = "postgres" log_sql_queries = true pg_db_name = "notification" pg_host = "localhost" pg_params = "sslmode=disable" pg_password = "postgres" pg_port = 5432 pg_username = "postgres"
17. Zpracování tabulek uložených v souborech typu CSV
Teoreticky by měl nástroj Dasel podporovat i práci se soubory typu CSV, takže by se mělo jednat o alternativu k nástroji Q (i když dotazovací jazyk Q odvozený od SQL je podle mého názoru mnohem sofistikovanější). Nicméně import CSV nemusí být vždy funkční. Můžeme si to ostatně ukázat na následujícím souboru:
Year,Winner 2022,C++ 2021,Python 2020,Python 2019,C 2018,Python 2017,C 2016,Go 2015,Java 2014,JavaScript 2013,Transact-SQL 2012,Objective-C 2011,Objective-C 2010,Python 2009,Go 2008,C 2007,Python 2006,Ruby 2005,Java 2004,PHP 2003,C++
Při pokusu o načtení tohoto souboru dojde pouze k lakonickému zobrazení chyby a k následnému ukončení Daselu:
$ dasel -f hall_of_fame.csv Error: CSVParser.toBytes cannot handle type []map[string]interface {}
18. Transformace dat do CSV
Na druhou stranu je však možné data transformovat do CSV, což je výhodné pouze tehdy, pokud informace uložené v TOML, YAML, XML či JSONu obsahují opakující se typy hodnot. Například takto lze vyexportovat informace o atributech z JSON schématu:
$ dasel -f msg_with_schema.json -w csv "schema.fields" field,optional,type ID,false,int64 Name,false,string Surname,false,string
Nepatrně složitější příklad:
$ dasel -f notification.public.xml -w csv "database.tables.table" -name,-numRows,-remarks,-schema,-type,column,index,primaryKey event_targets,2,specification of all event targets currently supported,public,TABLE,[map[-autoUpdated:false -defaultValue:null -digits:0 -id:0 -name:id -nullable:false -remarks: -size:10 -type:int4 -typeCode:4 child:map[-column:event_type_id -foreignKey:reported_event_type_id_fkey -implied:false -onDeleteCascade:false -schema:public -table:reported]] map[-autoUpdated:false -defaultValue:null -digits:0 -id:1 -name:name -nullable:false -remarks: -size:2147483647 -type:varchar -typeCode:12] map[-autoUpdated:false -defaultValue:null -digits:0 -id:2 -name:metainfo -nullable:false -remarks: -size:2147483647 -type:varchar -typeCode:12]],[map[-name:event_targets_pkey -unique:true column:map[-ascending:true -name:id]] map[-name:event_targets_metainfo_key -unique:true column:map[-ascending:true -name:metainfo]] map[-name:event_targets_name_key -unique:true column:map[-ascending:true -name:name]]],map[-column:id -sequenceNumberInPK:1] migration_info,1,information about the latest DB schema and migration status,public,TABLE,map[-autoUpdated:false -defaultValue:null -digits:0 -id:0 -name:version -nullable:false -remarks: -size:10 -type:int4 -typeCode:4],, states,4,states for each row stored in reported table,public,TABLE,[map[-autoUpdated:false -defaultValue:null -digits:0 -id:0 -name:id -nullable:false -remarks:XXXXXXXXX -size:10 -type:int4 -typeCode:4 child:map[-column:state -foreignKey:fk_state -implied:false -onDeleteCascade:false -schema:public -table:reported]] map[-autoUpdated:false -defaultValue:null -digits:0 -id:1 -name:value -nullable:false -remarks: -size:2147483647 -type:varchar -typeCode:12] map[-autoUpdated:false -defaultValue:null -digits:0 -id:2 -name:comment -nullable:true -remarks: -size:2147483647 -type:varchar -typeCode:12]],map[-name:states_pkey -unique:true column:map[-ascending:true -name:id]],map[-column:id -sequenceNumberInPK:1]
19. Repositář s pracovními soubory
Pracovní soubory ve formátu TOML, YAML, JSON, XML a CSV byly uloženy do repositáře, jenž je dostupný na adrese https://github.com/tisnik/slides/. V případě, že nebudete chtít klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé soubory, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Comma-Separated Values
https://en.wikipedia.org/wiki/Comma-separated_values - Tab-Separated Values
https://en.wikipedia.org/wiki/Tab-separated_values - Delimiter-separated values
https://en.wikipedia.org/wiki/Delimiter-separated_values - q – Run SQL directly on CSV or TSV files
https://harelba.github.io/q/ - q – examples
https://harelba.github.io/q/#examples - Repositář projektu q (GitHub)
https://github.com/harelba/q - How to run SQL queries directly on CSV or TSV file
https://computingforgeeks.com/run-sql-queries-directly-on-csv-files/ - Tab separated values
https://datatables.net/extensions/buttons/examples/flash/tsv.html - csvkit 1.0.5 (dokumentace)
https://csvkit.readthedocs.io/en/latest/# - Repositář projektu csvkit (GitHub)
https://github.com/wireservice/csvkit - Příklad CSV schématu
https://github.com/wireservice/ffs/blob/master/us/irs/irs_exempt_org_schema.csv - Repositář projektu jq (GitHub)
https://github.com/stedolan/jq - GitHub stránky projektu jq
https://stedolan.github.io/jq/ - 5 modern alternatives to essential Linux command-line tools
https://opensource.com/article/20/6/modern-linux-command-line-tools - Návod k nástroji jq
https://stedolan.github.io/jq/tutorial/ - jq Manual (development version)
https://stedolan.github.io/jq/manual/ - Introducing JSON
https://www.json.org/json-en.html - jq.py: a lightweight and flexible JSON processor
https://github.com/mwilliamson/jq.py - Discover how to use jq, a JSON manipulation command line, with GeoJSON
https://webgeodatavore.com/jq-json-manipulation-command-line-with-geojson.html - Reshaping JSON with jq
https://programminghistorian.org/en/lessons/json-and-jq - Python bindings for jq
https://pypi.org/project/jq/ - edn
https://github.com/edn-format/edn - Why use JSON over XML?
https://www.sitepoint.com/json-vs-xml/ - XML and XPath
https://www.w3schools.com/XML/xml_xpath.asp - XPath (Wikipedia)
https://en.wikipedia.org/wiki/XPath - RFC7159
https://www.ietf.org/rfc/rfc7159.txt - The Art of Unix Programming – DSV Style
https://www.linuxtopia.org/online_books/programming_books/art_of_unix_programming/ch05s02.html