smokex
A compact, sigil-based DSL for expressing port sets in Go.
SmokeX turns patterns like ~!$0:1023!#8080 or @web@k8s-api into lazy
iter.Seq[int] iterators — no intermediate slices, context-aware, and safe
for concurrent use.
~!$0:1023 → all 64512 non-privileged ports
#80#443$8000:8999 → 80, 443, and 8000–8999
@web!#8080 → web alias minus 8080
$1024:65535!$9000:9100!#8080 → range with layered exclusions
Requirements
- Go 1.23 or later (
iter.Seq and range-over-func)
Installation
go get github.com/buzzlightyear1309/smokex-ports
Quick start
package main
import (
"context"
"fmt"
"github.com/buzzlightyear1309/smokex-ports"
)
func main() {
ps := smokex.MustParse("$8000:8010!#8005")
fmt.Println(ps.Len()) // 10
fmt.Println(ps.String()) // $8000:8004$8006:8010
for port := range ps.All(context.Background()) {
fmt.Println(port)
}
}
Sigil reference
| Sigil |
Meaning |
Example |
Resolves to |
#<port> |
Single port |
#443 |
443 |
$<lo>:<hi> |
Inclusive range |
$8000:8003 |
8000 8001 8002 8003 |
~ |
All ports (0–65535) |
~ |
0 … 65535 |
!#<port> |
Exclude single port |
$8000:8003!#8001 |
8000 8002 8003 |
!$<lo>:<hi> |
Exclude range |
$0:10!$3:6 |
0 1 2 7 8 9 10 |
@<n> |
Named alias |
@https |
443 4443 8443 |
Exclusion binding: ! always binds to the immediately preceding $ or ~
token. A bare #port cannot carry exclusions — #80!#443 is a parse error.
Spaces anywhere in the expression are stripped before parsing, so
$ 8000 : 8010 ! # 8005 is valid.
API
Parsing
// Parse compiles a SmokeX expression. Returns a descriptive ParseError on failure.
ps, err := smokex.Parse("~!#22!#3389")
// MustParse panics on error. Useful for package-level vars and test fixtures.
var privileged = smokex.MustParse("$0:1023")
ParseError carries both a human-readable message and the byte position in
the (space-stripped) input where parsing failed:
ps, err := smokex.Parse("$500:100") // start > end
var pe *smokex.ParseError
if errors.As(err, &pe) {
fmt.Println(pe.Pos, pe.Msg)
}
Iterating — iter.Seq[int]
ctx := context.Background()
for port := range ps.All(ctx) {
scan(port)
}
All yields ports in ascending order. It stops early if the context is
cancelled or the caller's range body executes break:
// Stop after the first match
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for port := range ps.All(ctx) {
if isOpen(port) {
fmt.Println("first open port:", port)
cancel()
}
}
Membership — Contains
O(log n) binary search on the normalised interval list.
ps := smokex.MustParse("$1024:65535!$9000:9100")
ps.Contains(8080) // true
ps.Contains(9050) // false — excluded
ps.Contains(70000) // false — out of range
Cardinality — Len
O(1). Computed once at parse time.
smokex.MustParse("~").Len() // 65536
smokex.MustParse("~!$0:1023").Len() // 64512
smokex.MustParse("#80#443").Len() // 2
Materialising — ToSlice
Pre-allocates exactly Len() capacity. Respects context cancellation.
ports := smokex.MustParse("#22#80#443").ToSlice(context.Background())
// []int{22, 80, 443}
String() returns a minimal, round-trippable SmokeX expression.
Overlapping inputs are merged; duplicates are removed.
ps := smokex.MustParse("#80#80$0:100$50:200")
ps.String() // "$0:200"
ps.Len() // 201
// Round-trip guarantee
ps2, _ := smokex.Parse(ps.String())
ps2.Len() == ps.Len() // always true
Interval normalisation
After parsing, SmokeX merges all base tokens and exclusions into a sorted,
non-overlapping list of closed intervals. Consequences:
- Duplicates (
#80#80) are silently deduplicated — one port.
- Overlapping ranges (
$0:100$50:200) are coalesced — $0:200.
- Adjacent ranges (
$10:20$21:30) are merged — $10:30.
- A single port inside a range (
#80$70:100) is not double-counted.
Contains is O(log n); Len is O(1).
Named aliases
Aliases expand to a SmokeX pattern before parsing. They can be combined with
other tokens and carry exclusions.
smokex.MustParse("@ssh") // #22
smokex.MustParse("@web!#8080") // #80#443#8443
smokex.MustParse("@ssh@web") // #22#80#443#8080#8443
smokex.MustParse("@vnc!#5905") // $5900:5904$5906:5910
Built-in aliases
| Alias |
Expands to |
@http |
#80 |
@https |
#443#8443#4443 |
@http-alt |
#8080#8008 |
@web |
#80#443#8080#8443 |
@ssh |
#22 |
@rdp |
#3389 |
@vnc |
$5900:5910 |
@telnet |
#23 |
@dns |
#53 |
@ftp |
#20#21 |
@smtp |
#25#465#587 |
@imap |
#143#993 |
@pop3 |
#110#995 |
@mail |
#25#465#587#143#993#110#995 |
@mysql |
#3306 |
@postgres |
#5432 |
@mssql |
#1433 |
@oracle |
#1521 |
@redis |
#6379 |
@mongodb |
#27017 |
@elastic |
#9200#9300 |
@ldap |
#389#636 |
@k8s-api |
#6443#8443 |
@k8s-node |
$10250:10255 |
@etcd |
#2379#2380 |
@docker |
#2375#2376 |
@prometheus |
#9090#9091 |
@grafana |
#3000 |
@alertmanager |
#9093 |
@kafka |
$9092:9093 |
@rabbitmq |
#5672#5671#15672 |
@nats |
#4222#6222#8222 |
@vault |
#8200#8201 |
@consul |
#8300#8301#8500#8501#8600 |
Registering custom aliases
Register validates the expansion pattern before committing it. Aliases are
global and safe to register concurrently.
// Register once at startup (e.g. in an init function)
if err := smokex.Register("@myapp", "#9000#9001$9100:9110"); err != nil {
log.Fatal(err)
}
ps := smokex.MustParse("@myapp!#9105")
ps.Len() // 12
ps.String() // #9000#9001$9100:9104$9106:9110
Aliases cannot reference other aliases — one level of expansion only. Circular
or chained aliases are rejected at registration time.
Inspecting the registry
all := smokex.Aliases() // returns a map[string]string snapshot
fmt.Println(len(all)) // 33 built-ins + any custom registrations
Concurrency
*PortSet is immutable after Parse returns. All, Contains, Len,
String, and ToSlice are safe to call concurrently from any number of
goroutines without additional synchronisation.
The alias registry (Register, Aliases, and alias expansion during Parse)
uses an internal sync.RWMutex and is also concurrency-safe.
Nil safety
All *PortSet methods are nil-safe:
var ps *smokex.PortSet
ps.Len() // 0
ps.Contains(80) // false
ps.String() // ""
ps.ToSlice(context.Background()) // nil
for range ps.All(ctx) {} // iterates zero times
Error reference
All parse failures return *ParseError with a byte position:
| Input |
Error |
"" |
empty expression |
"#99999" |
port 99999 out of range [0, 65535] |
"$500:100" |
range start 500 > end 100 |
"$60000:70000" |
port 70000 out of range [0, 65535] |
"#" |
expected digit at position 1 |
"$10001000" |
$ range missing : after 10001000 |
"!#80" |
! exclusion must follow a $ range or ~ wildcard |
"$0:100!" |
! at end of input with no target |
"~1024" |
~ wildcard takes no arguments |
"@unknown" |
unknown alias "@unknown" |
License
MIT