CURRENT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
ZIP_NAME = OptionStratLib.zip
.PHONY: all
all: test fmt lint build
.PHONY: build
build:
cargo build
.PHONY: release
release:
cargo build --release
.PHONY: test
test:
LOGLEVEL=WARN cargo test
LOGLEVEL=WARN cargo test --features plotly
LOGLEVEL=WARN cargo test --features static_export,plotly
.PHONY: test-visual
test-visual:
LOGLEVEL=WARN cargo test --features static_export,plotly -- --ignored
.PHONY: fmt
fmt:
cargo +stable fmt --all
.PHONY: fmt-check
fmt-check:
cargo +stable fmt --check
.PHONY: lint
lint:
cargo clippy --all-targets --all-features --workspace -- -D warnings
.PHONY: lint-fix
lint-fix:
cargo clippy --fix --all-targets --all-features --allow-staged --allow-dirty --workspace -- -D warnings
.PHONY: clean
clean:
cargo clean
.PHONY: check
check: test fmt-check lint scan-banned
.PHONY: scan-banned
scan-banned:
@found=$$(for f in $$(find src -name '*.rs'); do \
awk -v file="$$f" ' \
BEGIN { skip = 0; depth = 0; pending = 0; awaiting = 0 } \
function braces(line, tmp, o, c) { \
tmp = line; o = gsub(/{/, "{", tmp); \
tmp = line; c = gsub(/}/, "}", tmp); \
return o - c; \
} \
{ \
raw = $$0; \
if (skip) { \
depth += braces(raw); \
if (depth <= 0) { skip = 0; depth = 0 } \
next; \
} \
if (awaiting) { \
depth += braces(raw); \
if (depth > 0) { awaiting = 0; skip = 1; next } \
if (raw ~ /;[[:space:]]*$$/) { awaiting = 0; depth = 0 } \
next; \
} \
if (pending) { \
if (raw ~ /^[[:space:]]*#\[/) { next } \
pending = 0; \
trimmed = raw; sub(/^[[:space:]]+/, "", trimmed); \
if (trimmed ~ /^\{/) { \
depth = braces(raw); \
if (depth > 0) { skip = 1 } \
next; \
} \
if (trimmed ~ /^(pub([[:space:]]*\([^)]*\))?[[:space:]]+)?(async[[:space:]]+)?(unsafe[[:space:]]+)?(mod|fn|impl|trait|struct|enum|union)([[:space:]<(]|$$)/) { \
depth = braces(raw); \
if (depth > 0) { skip = 1 } \
else if (raw ~ /;[[:space:]]*$$/) { depth = 0 } \
else { awaiting = 1; depth = 0 } \
next; \
} \
} \
if (raw ~ /^[[:space:]]*#\[cfg\(test\)\][[:space:]]*$$/) { pending = 1; next } \
print file ":" NR ":" raw; \
} \
' "$$f"; \
done \
| grep -E '\.unwrap\(\)|\.expect\(|\.exp\(\)|\.ln\(\)|\.powd\(|\.sqrt\(\)\.unwrap\(\)|[^_[:alnum:]](panic|unreachable|todo|unimplemented)!' \
| grep -v -E ':[0-9]+:[[:space:]]*(///|//!|//|\*+/)' \
| grep -v -E 'scan-banned: allow -- [^[:space:]]' || true); \
malformed=$$(grep -rn 'scan-banned: allow' src \
| grep -v -E 'scan-banned: allow -- [^[:space:]]' || true); \
if [ -n "$$malformed" ]; then \
echo "Exemption markers without a reason (use 'scan-banned: allow -- <reason>'):"; \
echo "$$malformed"; \
exit 1; \
fi; \
if [ -n "$$found" ]; then \
echo "Banned patterns found in production code:"; \
echo "$$found"; \
exit 1; \
fi; \
echo "OK: no unwrap/expect, no panic/unreachable/todo/unimplemented, no unchecked exp/ln/powd/sqrt in production code"
CARGO_PUBLIC_API_VERSION := 0.52.0
PUBLIC_API_NIGHTLY := nightly-2026-08-28
.PHONY: check-cargo-public-api
check-cargo-public-api:
@installed=$$(cargo-public-api --version 2>/dev/null | awk '{print $$2}'); \
if [ "$$installed" != "$(CARGO_PUBLIC_API_VERSION)" ]; then \
echo "Installing cargo-public-api $(CARGO_PUBLIC_API_VERSION) (found: $${installed:-none})..."; \
cargo install cargo-public-api --locked --version $(CARGO_PUBLIC_API_VERSION); \
fi
@rustup toolchain list | grep -q '^$(PUBLIC_API_NIGHTLY)' || (echo "Installing $(PUBLIC_API_NIGHTLY) for rustdoc JSON..."; rustup toolchain install $(PUBLIC_API_NIGHTLY) --profile minimal)
.PHONY: print-public-api-pins
print-public-api-pins:
@echo "cargo_public_api_version=$(CARGO_PUBLIC_API_VERSION)"
@echo "public_api_nightly=$(PUBLIC_API_NIGHTLY)"
.PHONY: public-api-update
public-api-update: check-cargo-public-api
@mkdir -p public-api
cargo +$(PUBLIC_API_NIGHTLY) public-api -sss --all-features > public-api/optionstratlib.txt
.PHONY: public-api-check
public-api-check: check-cargo-public-api
@mkdir -p target/public-api
@cargo +$(PUBLIC_API_NIGHTLY) public-api -sss --all-features > target/public-api/optionstratlib.txt
@if ! diff -u public-api/optionstratlib.txt target/public-api/optionstratlib.txt; then \
echo; \
echo "Public API drifted from public-api/optionstratlib.txt (see diff above)."; \
echo "If this change is intentional, run 'make public-api-update', review the"; \
echo "resulting diff, and commit it together with this change."; \
exit 1; \
fi
@echo "OK: public API matches public-api/optionstratlib.txt"
.PHONY: run
run:
cargo run
.PHONY: fix
fix:
cargo fix --allow-staged --allow-dirty
.PHONY: pre-push
pre-push: fix fmt lint-fix test readme doc
.PHONY: doc
doc:
cargo doc --all-features --no-deps
.PHONY: doc-open
doc-open:
cargo doc --open
.PHONY: publish
publish: readme
cargo login ${CARGO_REGISTRY_TOKEN}
cargo package
cargo publish
.PHONY: coverage
coverage:
export LOGLEVEL=WARN
export RUST_lOG=WARN
cargo install cargo-tarpaulin
mkdir -p coverage
cargo tarpaulin --verbose --all-features --workspace --timeout 0 --out Xml --output-dir coverage
.PHONY: coverage-html
coverage-html:
export LOGLEVEL=WARN
export RUST_lOG=WARN
cargo install cargo-tarpaulin
mkdir -p coverage
cargo tarpaulin --color Always --engine llvm --tests --all-targets --all-features --workspace --timeout 0 --out Html --output-dir coverage
.PHONY: open-coverage
open-coverage:
open coverage/tarpaulin-report.html
git-log:
@if [ "$(CURRENT_BRANCH)" = "HEAD" ]; then \
echo "You are in a detached HEAD state. Please check out a branch."; \
exit 1; \
fi; \
echo "Showing git log for branch $(CURRENT_BRANCH) against main:"; \
git log main..$(CURRENT_BRANCH) --pretty=full
.PHONY: create-doc
create-doc:
cargo doc --no-deps --document-private-items
.PHONY: readme
readme: check-cargo-readme create-doc
cargo readme > README.md
.PHONY: check-cargo-readme
check-cargo-readme:
@command -v cargo-readme > /dev/null || (echo "Installing cargo-readme..."; cargo install cargo-readme)
.PHONY: check-spanish
check-spanish:
@rg -n --pcre2 -e '^\s*(//|///|//!|#|/\*|\*).*?[áéíóúÁÉÍÓÚñÑ¿¡]' \
--glob '!target/*' \
--glob '!**/*.png' \
. || (echo "❌ Spanish comments found"; exit 1)
.PHONY: zip
zip:
@echo "Creating $(ZIP_NAME) without any 'target' directories, 'Cargo.lock', and hidden files..."
@find . -type f \
! -path "*/target/*" \
! -path "./.*" \
! -name "Cargo.lock" \
! -name ".*" \
| zip -@ $(ZIP_NAME)
@echo "$(ZIP_NAME) created successfully."
.PHONY: check-cargo-criterion
check-cargo-criterion:
@command -v cargo-criterion > /dev/null || (echo "Installing cargo-criterion..."; cargo install cargo-criterion)
.PHONY: bench
bench: check-cargo-criterion
cargo criterion --output-format=quiet
.PHONY: bench-show
bench-show:
open target/criterion/report/index.html
.PHONY: bench-save
bench-save: check-cargo-criterion
cargo criterion --output-format quiet --history-id v0.3.2 --history-description "Version 0.3.2 baseline"
.PHONY: bench-compare
bench-compare: check-cargo-criterion
cargo criterion --output-format verbose
.PHONY: bench-json
bench-json: check-cargo-criterion
cargo criterion --message-format json
.PHONY: bench-clean
bench-clean:
rm -rf target/criterion
.PHONY: workflow-coverage
workflow-coverage:
DOCKER_HOST="$${DOCKER_HOST}" act push --job code_coverage_report \
-P ubuntu-latest=catthehacker/ubuntu:latest \
--privileged
.PHONY: workflow-build
workflow-build:
DOCKER_HOST="$${DOCKER_HOST}" act push --job build \
-P ubuntu-latest=catthehacker/ubuntu:latest
.PHONY: workflow-lint
workflow-lint:
DOCKER_HOST="$${DOCKER_HOST}" act push --job lint
.PHONY: workflow-test
workflow-test:
DOCKER_HOST="$${DOCKER_HOST}" act push --job run_tests
.PHONY: workflow
workflow: workflow-build workflow-lint workflow-test workflow-coverage
.PHONY: generate_markdown
generate_markdown:
./doc/generate_md_docs.sh