# 单行脚本 一本惯用 Deft 单行脚本的食谱。每个配方都展示首选的现代写法。编写脚本时可将这些作为参考。 ## 进程与 Shell
# Capture stdout of a command
def files [|ls -la]

# Run a subprocess and check status
def r [os/run "git" "status"]
if ($r~status == 0) { echo "clean" }

# Run a shell-style pipeline
def lines [os/shell "find . -name '*.dft' | wc -l"]

# Get the script's own filename
def argv [os/args]
def script-name $argv(first)

# Iterate shell output lines
for line [|ps aux](lines) { echo $line }

# Iterate shell output words
for word [|ls](words) { echo $word }
## 文件
# Read whole file as string
def text [unwrap [fs/read "config.json"]]

# Read lines into a list
def lines [unwrap [fs/read-lines "log.txt"]]

# Write a file
fs/write "out.txt" $content

# Append to a file
fs/append "log.txt" "newline\n"

# Check existence
if [fs/exists? "config.json"] { ... }

# List directory entries
for entry [unwrap [fs/list "."]] { echo $entry~name }

# Glob recursively
for path [unwrap [fs/glob "src/**/*.dft"]] { echo $path }

# Stat a file
def info [unwrap [fs/stat "file.dft"]]
echo $info~size
echo $info~mtime

# Make a directory
fs/mkdir "build/out"

# Path manipulation
fs/join "a" "b" "c.dft"                     # => "a/b/c.dft"
fs/base "/path/to/file.dft"                 # => "file.dft"
fs/dir "/path/to/file.dft"                  # => "/path/to"
fs/ext "file.dft"                           # => "dft"

# Temp paths
def tmp [fs/tmp-path ".json"]               # /tmp/abc123.json
## 字符串
# Length
"hello"(len)                                 # => 5

# Upper / lower
"hello"(upper)                               # => HELLO
"WORLD"(lower)                               # => world

# Trim
"  hi  "(trim)                               # => "hi"

# Split and join
"a,b,c"(split ",")                           # => @{a b c}
@{:a :b :c}(join "|")                           # => "a|b|c"

# Replace
"hello world"(replace "o" "0")               # => "hell0 w0rld"

# Test prefix / suffix / substring
"hello"(starts-with? "he")                   # => true
"hello"(ends-with? "lo")                     # => true
"hello"(contains "ell")                      # => true

# Substring
"hello"(0 .. 3)                              # => "hel"
"hello"(nth 1)                               # => "e"

# Pad
"5"(pad-left 3 "0")                          # => "005"

# Printf-style formatting
[format "%d times %#x is %e" 10 10 100]      # => "10 times 0xa is 1.000000e+02"
[format "%05.2f" 3.14]                       # => "03.14"

# Lines / words
"a\nb\nc"(lines)                             # => @{a b c}
"a b c"(words)                               # => @{a b c}

# Iterate chars
for ch in "abc" { echo $ch }
## 数字与数学
# Arithmetic
(1 + 2)
((3 * 4) - 5)
[mod 10 3)                                   # => 1
10(mod 3)                                    # => 1

# Comparison
($a < $b)
($x == 0)

# Rounding
(3.7)(floor)                                  # => 3
(3.2)(ceil)                                   # => 4
(3.5)(round)                                  # => 4

# Min / max / clamp
[min 3 7]                                     # => 3
[max 3 7]                                     # => 7
[clamp 15 0 10]                               # => 10

# Absolute value
[abs -5]                                      # => 5

# Specialised math
[math/sqrt 16]                                # => 4
[math/pow 2 10]                               # => 1024
[math/sin [math/pi]]
[math/sum @{1 2 3 4}]                         # => 10
[math/avg @{10 20 30}]                        # => 20

# Random
[random]                                      # => 0..max-int
[random 100]                                  # => 0..99
[math/random-range 10 20]                     # => 10..19
## 列表与序列
# Length / edges
@{1 2 3}(len)                                 # => 3
@{1 2 3}(first)                               # => 1
@{1 2 3}(last)                                # => 3

# Membership
@{1 2 3}(in? 3)                               # => true
[in? @{1 2 3} 3]                              # => true (prefix form)

# Slice
@{1 2 3 4 5}(slice 1 3)                       # => @{2 3}

# Reverse / sort / unique
@{3 1 2}(sort)                                # => @{1 2 3}
@{3 1 2}(reverse)                             # => @{2 1 3}
@{1 1 2 3 3}(unique)                          # => @{1 2 3}

# Append (mutating local)
def xs @{}
$xs << 1
$xs << 2                                      # xs is now @{1 2}

# Append (pure)
set ys [append $xs 3]                         # ys is @{1 2 3}, xs unchanged

# Pipelines
@{1 2 3 4 5} |> filter even? |> map { $it * 2 } |> collect
# => @{4 8}

# Reduce
@{1 2 3 4 5} |> reduce 0 { ($acc + $it) }     # => 15

# Find first match
@{1 2 3 4} |> find { ($it > 2) }              # => 3

# Existence
@{1 2 3} |> any? { ($it > 2) }                # => true
@{1 2 3} |> all? { ($it > 0) }                # => true

# Group
@{1 2 3 4} |> group { ($it % 2) == 0 }        # => %{ :true @{2 4} :false @{1 3} }

# Range
[range 0 5]                                   # => @{0 1 2 3 4}
[range 0 10 2]                                # => @{0 2 4 6 8}
## 映射
# Construction
def m %{ :a 1 :b 2 :c 3 }

# Lookup
$m~a                                          # => 1
[get $m :a]                                   # => 1
[get $m :missing 0]                           # => 0 (default)
[$m :a]                                       # => 1 (IFn form — map is callable)

# Add / remove
[assoc $m :d 4]                               # => %{ :a 1 :b 2 :c 3 :d 4 }
[dissoc $m :a]                                # => %{ :b 2 :c 3 }

# Merge
[merge %{ :a 1 } %{ :a 2 :b 3 }]              # => %{ :a 2 :b 3 }

# Keys / values / entries
[keys $m]                                     # => ^{:a :b :c}
[values $m]                                   # => @{1 2 3}
[entries $m]                                  # => @{ @{:a 1} @{:b 2} @{:c 3} }

# Iterate
for entry [entries $m] { echo "$entry(0) = $entry(1)" }
$m |> each { echo "$it(0) = $it(1)" }

# Nested assoc
[assoc-in %{ :user %{} } @{ :user :name } "Alice"]

# Nested lookup
[get-in $user @{ :profile :name }]                 # => value, or nil if missing
[get-in $user @{ :profile :age } :unknown]         # => :unknown if missing
## 集合
def roles ^{:admin :user :guest}

# Membership
:admin in? $roles                            # => true

# Add
($roles << :owner)                               # => ^{:admin :user :guest :owner}

# Set ops
[union ^{1 2} ^{2 3}]                         # => ^{1 2 3}
[intersection ^{1 2 3} ^{2 3 4}]              # => ^{2 3}
[difference ^{1 2 3} ^{2 3 4}]                # => ^{1}

# Relations
[subset? ^{1 2} ^{1 2 3}]                     # => true
[superset? ^{1 2 3} ^{1 2}]                   # => true
## JSON / YAML
# Parse
def data [json/parse $raw-text]
def yaml-data [yaml/load "config.yaml"]

# Stringify
[json/stringify $data]
[json/pretty $data]                           # indented

# Round-trip
[json/parse [json/stringify $data]]
## HTTP
# GET
set r [await [http/get "https://api.github.com/repos/ziglang/zig"]]
def body [json/parse $r~body]
echo $body~stargazers_count

# POST with JSON
def payload [json/stringify %{ :title "hello" }]
[await [http/post "https://example.com/api" $payload]]

# Concurrent — task bodies see globals (snapshotted), not captured locals
from "deft/stdext/async" import await-all
def api "https://api.example.com"
def futures @{
    [deftask { [await [http/get "$api/a"]] }]
    [deftask { [await [http/get "$api/b"]] }]
}
def results [await-all $futures]
## SQLite
# Open
def db [sqlite/open ":memory:"]

# Schema
sqlite/exec $db "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)"

# Insert with params
sqlite/exec $db "INSERT INTO users (name, age) VALUES (?, ?)" @{ "Alice" 30 }

# Query
def rows [sqlite/query $db "SELECT * FROM users"]
for row $rows { echo "$row~name" }

# Scalar
def count [sqlite/scalar $db "SELECT COUNT(*) FROM users"]

# Close
sqlite/close $db
## 模式匹配
# Dispatch
match $status {
    200 => "ok"
    404 => "missing"
    _   => "other"
}

# Destructure
match $entry {
    %{ :name $n :age $a } => "$n is $a"
    _                      => "unknown"
}

# Tagged map
match $result {
    %Ok{ :value $v }    => $v
    %Err{ :message $m } => [throw $m]
}

# Range
match $score {
    90 .. 100 => "A"
    80 .. 89  => "B"
    _         => "C"
}
## 转换器
# Build a reusable pipeline
def xf [comp
    [filter { ($it % 2) == 0 }]
    [map { $it * $it }]
    [take 5]]

# Apply to different sinks/sources
[into @{} xf [range 0 1000]]
[into ^{} xf $other-source]
[transduce xf + 0 [range 0 1000]]            # sum
## 并发
# Async value — deftask runs the body on a real OS thread
set f [deftask { [slow-op] }]
# ... do other work ...
def result [await $f]

# Concurrent map — bodies must be self-contained (no captured locals)
from "deft/stdext/async" import await-all
def f1 [deftask { [await [http/get "https://a.example.com"]] }]
def f2 [deftask { [await [http/get "https://b.example.com"]] }]
def results [await-all @{$f1 $f2}]

# One-shot timer
after 5000 { echo "five seconds" }

# Sleep
sleep 100
## Git
def branch [|git rev-parse --abbrev-ref HEAD]
def hash   [|git rev-parse HEAD]
def dirty  [|git status --porcelain]

if not(empty? [|git status --porcelain]) {
    echo "dirty tree"
}

# Files changed since main
def changed [|git diff --name-only main](lines)
## 类型检查
[type 42]                                     # => "number"
[type "hi"]                                   # => "string"
[type %User{...}]                             # => "User"

[is? "number" 42]                             # => true
[nil? $x]
[empty? $xs]
[satisfies? $val "Drawable"]
## 时间
def now [clock/seconds]
def iso [clock/format $now "%Y-%m-%dT%H:%M:%S%z"]

def later [clock/add $now 1 :hour]
def parsed [clock/scan "2024-05-01" %{ :format "%Y-%m-%d" :timezone "UTC" }]

def ms [clock/millis]
def elapsed ($end - $start)
## 环境
def home [os/env "HOME"]
def path [os/env "PATH"]
def cwd [os/cwd]

# Args
def args [os/args]
def argv $args(rest)
## 加密
def hash [crypto/sha256 "hello"]
def mac  [crypto/hmac :sha256 "key" "data"]
def salt [crypto/rand-hex 16]
def token [crypto/rand 32]

def encoded [crypto/b64-encode $data]
def decoded [crypto/b64-decode $encoded]
## 压缩
compress/gzip "file.txt" "file.txt.gz"
compress/gunzip "file.txt.gz" "file.txt"

compress/tgz-create @{"src"} "src.tar.gz"
compress/tgz-extract "src.tar.gz" "out/"
compress/list-tgz "src.tar.gz"
## 另见 - Shell 脚本 — 叙述式教程 - 标准库 — 完整标准库目录 - 模式匹配 — 完整模式参考