Compare commits

..

No commits in common. "main" and "v0.3.0" have entirely different histories.
main ... v0.3.0

371 changed files with 598 additions and 7565 deletions

View file

@ -1,8 +0,0 @@
.DS_Store
/docs
*.md
.git*
.idea/
.golangci.yml
coverage.out
.trivycache/

5
.github/FUNDING.yml vendored
View file

@ -1 +1,4 @@
github: rjNemo
# These are supported funding model platforms
github: rjNemo # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]

View file

@ -1,11 +1,25 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '24 15 * * 6'
jobs:
analyze:
name: Analyze
@ -14,18 +28,43 @@ jobs:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View file

@ -1,23 +0,0 @@
name: Tests
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
fetch-depth: 2
- uses: actions/setup-go@v4
with:
go-version: "1.23"
- name: Run tests with coverage
run: go test -coverprofile=coverage.out -covermode=count ./...
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.out
flags: unittests
name: codecov-umbrella
fail_ci_if_error: true
verbose: true

7
.gitignore vendored
View file

@ -57,10 +57,3 @@ Icon
Network Trash Folder
Temporary Items
.apdisk
docs/public
.trivycache/
.vscode/launch.json
.claude
AGENTS.md
bench*txt
ACTION_PLAN.md

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "docs/themes/compose"]
path = docs/themes/compose
url = https://github.com/onweru/compose/

View file

@ -1,54 +1,42 @@
version: "2"
skip-dirs-use-default: true
run:
timeout: 5m
linters:
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- errcheck
- errorlint
- exportloopref
- gocritic
- gocyclo
- gofmt
- goimports
- goprintffuncname
- gosimple
- gosec
- govet
- ineffassign
- misspell
- noctx
- nolintlint
- prealloc
- rowserrcheck
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
settings:
depguard:
rules:
main:
list-mode: lax
files:
- $all
allow:
- $gostd
- github.com/rjNemo/underscore
- github.com/rjNemo/underscore/...
- github.com/stretchr/testify/...
- golang.org/x/exp/constraints
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
fast: true
linters-settings:
goimports:
local-prefixes: github.com/rjNemo/underscore

183
CLAUDE.md
View file

@ -1,183 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
`underscore` is a Go library providing functional programming helpers inspired by underscore.js, built on Go 1.18+ generics. The library is organized as a flat structure with individual files for each function, plus a `maps` subpackage for map-specific utilities.
## Development Commands
### Testing
```sh
# Run all tests (local)
go test ./...
# Run all tests with coverage (local)
go test ./... -coverpkg=./... -coverprofile cov.out -covermode=count
go tool cover -func cov.out
rm cov.out
# Run tests in Docker (preferred for CI/validation)
make test
# Run a single test
go test -run TestFunctionName
# Run tests for a specific file
go test -run TestMap
```
### Building
```sh
# Build Docker image
make build
# Install dependencies
go mod download
```
### Linting & Security
```sh
# Scan Docker image for vulnerabilities
make scan
# Scan config files
make scan-config
```
### Documentation
```sh
# Serve docs locally at http://localhost:1313
make docs
# Build static docs
make build-docs
```
## Architecture
### Code Organization
The library uses a **flat structure** where each function is implemented in its own file:
- `<function>.go` - implementation
- `<function>_test.go` - tests
Example: `filter.go` + `filter_test.go`, `map.go` + `map_test.go`
### Core Patterns
**Generic Functions**: Most functions use Go generics with constraints from `cmp.Ordered` or custom type parameters. Functions operate on slices and return new slices (immutable style).
**Pipe Chain**: The `Pipe[T]` struct enables method chaining for ordered types. Methods that return slices continue the chain, while methods that return values (like `All`, `Any`, `Reduce`) break the chain and return the final value.
```go
// pipe.go defines Pipe[T cmp.Ordered]
// Chain-continuing: Filter, Map
// Chain-breaking: All, Any, Reduce, Min, Max, Partition, Find, Each
```
**Concurrency Helpers**: `ParallelMap` and `ParallelFilter` use worker pools with:
- Context-based cancellation
- Order preservation (results match input order)
- First-error-wins semantics
- Default workers = GOMAXPROCS if workers <= 0
Implementation detail: Uses `sync.Once` to capture first error and cancel context immediately.
**Subpackages**:
- `maps/` - Map-specific utilities (`Keys`, `Values`, `Map`)
- Uses type alias `M[K, V] = map[K]V` for cleaner signatures
- `Map` function allows transforming map entries
### Testing Conventions
- Use `testify/assert` for assertions
- Test file names match source files with `_test.go` suffix
- Table-driven tests are common (see `map_test.go`, `filter_test.go`)
- Internal tests (using `package underscore` rather than `package underscore_test`) are used sparingly for testing unexported functions
## Key Constraints
- **Minimum Go version**: 1.24.2 (see go.mod)
- **Generic constraints**: Most collection functions require `cmp.Ordered` types; some use `comparable` or no constraints
- **Order preservation**: `ParallelMap` and `ParallelFilter` guarantee output order matches input order
- **No mutation**: Functions return new slices; `UniqueInPlace` is the exception (in-place deduplication)
## Known Limitations
### Recently Fixed (2025-11-14)
1. ✅ **Filter allocation** - Now pre-allocates with `make([]T, 0, len(values))` (90% fewer allocations)
2. ✅ **OrderBy algorithm** - Replaced bubble sort with `slices.SortFunc` (629x faster for large datasets)
3. ✅ **Partition allocation** - Now pre-allocates both result slices
4. ✅ **Max/Min empty slices** - Now panics with clear message: "underscore.Max: empty slice"
5. ✅ **Drop semantics** - Fixed to drop first N elements (breaking change). Old behavior available as `RemoveAt`
### API Design Issues
1. **Pipe constraint** (`pipe.go:7`) - `Pipe[T cmp.Ordered]` prevents usage with custom types
2. **Last panics** (`last.go:5-8`) - No empty slice handling
### Missing Features
Popular FP utilities not yet implemented: `TakeWhile`, `DropWhile`, `Scan`, `First/FirstN`, `Init`, `Intersperse`, `Sliding`, `FoldRight`, `Tap`, `Transpose`, `Unzip`, `ParallelReduce`, `Replicate`
## Performance Characteristics
### Good Performance Patterns
- `Filter` pre-allocates: `make([]T, 0, len(values))` ✅ (Fixed 2025-11-14)
- `Map` pre-allocates: `make([]P, 0, len(values))`
- `Partition` pre-allocates: `make([]T, 0, len(values))` for both slices ✅ (Fixed 2025-11-14)
- `Chunk` pre-calculates capacity: `(l+n-1)/n`
- `ParallelFilter` pre-counts before allocation
- `OrderBy` uses `slices.SortFunc`: O(n log n) performance ✅ (Fixed 2025-11-14)
### Remaining Performance Issues
- `Flatmap`: Accumulation overhead from repeated appends
- `GroupBy`: Map initialized with capacity 0 (useless hint)
### When to Use ParallelMap vs Map
Use `ParallelMap` when:
- Processing 100+ elements with expensive operations (>1ms per element)
- Operations are CPU-bound (not I/O-bound with shared resources)
- Order preservation is required
- Context cancellation is needed
Use regular `Map` when:
- Small slices (<100 elements)
- Fast operations (<100µs per element)
- Avoiding goroutine overhead matters
- Simple transformations without error handling
**Worker count guidelines:**
- Default (workers=0): Uses `runtime.GOMAXPROCS(0)` - good starting point
- CPU-bound: Use GOMAXPROCS or GOMAXPROCS*2
- I/O-bound: Can use higher values (10-100) if not sharing resources
## Contributing Notes
When adding new functions:
1. Create both `<function>.go` and `<function>_test.go`
2. Add examples in comments using Go doc format
3. Pre-allocate slices with `make([]T, 0, len(input))` when output size is similar to input
4. Document panic conditions (empty slices, nil inputs, invalid indices)
5. Add edge case tests (empty, single element, nil)
6. If the function applies to Pipe chains, add a method to `pipe.go`
7. Update README.md function list if adding new collection functions
8. Follow SemVer for version numbers
9. Ensure all tests pass: `make test`
When fixing bugs:
- Add regression tests before fixing
- Run benchmarks if performance-related: `go test -bench=. -benchmem`
- Check for similar issues in other functions

View file

@ -1,14 +1,10 @@
FROM golang:1.24.2-alpine
FROM golang:1.18beta1-bullseye
ENV CGO_ENABLED 0
ENV GOOS linux
ENV GOARCH amd64
RUN apk upgrade --no-cache
WORKDIR /lib
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . ./
COPY *.go ./
RUN go test ./... -cover

View file

@ -1,21 +0,0 @@
FROM golang:1.24.2-alpine AS builder
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
RUN apk -U upgrade --no-cache
RUN apk add hugo
WORKDIR /docs
COPY go.* ./
RUN go mod download
COPY . .
RUN hugo --gc --minify
FROM scratch
COPY --from=builder /docs/public /public

View file

@ -1,22 +1,5 @@
TEST=go test ./...
COVER=-coverpkg=./... -coverprofile cov.out -covermode=count; go tool cover -func cov.out; rm cov.out
IMAGE=underscore
test:
docker build .
build:
docker build -t $(IMAGE):latest .
test: build
docker run --name $(IMAGE) --rm -i $(IMAGE) sh -c "$(TEST) $(COVER)"
scan:
trivy --cache-dir .trivycache/ image --exit-code 0 --no-progress --severity CRITICAL $(IMAGE)
scan-config:
trivy config .
.PHONY: docs
docs:
cd docs && hugo server -D
build-docs:
cd docs && hugo --gc --minify
doc:
cd docs && hugo server -D

167
README.md
View file

@ -1,27 +1,20 @@
# \_Underscore
# Underscore
![License](https://img.shields.io/github/license/rjNemo/underscore?style=for-the-badge)
[![Go version](https://img.shields.io/github/go-mod/go-version/rjNemo/underscore?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/rjNemo/underscore)
![Go report](https://goreportcard.com/badge/github.com/rjNemo/underscore?style=for-the-badge)
![test coverage](https://img.shields.io/codecov/c/github/rjNemo/underscore?style=for-the-badge&logo=codecov)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9726/badge?style=for-the-badge)](https://www.bestpractices.dev/projects/9726)
![Go version](https://img.shields.io/github/go-mod/go-version/rjNemo/underscore?style=for-the-badge)
![underscore](https://socialify.git.ci/rjNemo/underscore/image?description=1&font=KoHo&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FrjNemo%2Funderscore%2Fmain%2Fdocs%2Fstatic%2Flogo.png&owner=1&pattern=Floating%20Cogs&stargazers=1&theme=Dark)
![underscore](https://socialify.git.ci/rjNemo/underscore/image?description=1&font=Raleway&language=1&logo=https%3A%2F%2Fgithub.com%2FrjNemo%2Funderscore%2Fblob%2Fmain%2Fdocs%2Fstatic%2Flogo.png%3Fraw%3Dtrue&name=1&pattern=Floating%20Cogs&theme=Light)
`underscore` is a `Go` library providing useful functional programming helpers without
extending any built-in objects.
`underscore` is a `Go` library providing useful functional programming helpers without extending any built-in objects.
It is mostly a port from the `underscore.js` library based on generics brought by
`Go 1.18`.
It is mostly a port from the `underscore.js` library based on generics brought by `go1.18`.
## Usage
📚 Follow this link for the [documentation](https://underscore.onrender.com/).
Install the library using
```sh
go get github.com/rjNemo/underscore@latest
```shell
go get github.com/rjNemo/underscore
```
Please check out the [examples](examples) to see how to use the library.
@ -30,47 +23,49 @@ Please check out the [examples](examples) to see how to use the library.
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
// filter even numbers from the slice
evens := u.Filter(numbers, func(n int) bool { return n%2 == 0 })
// square every number in the slice
squares := u.Map(evens, func(n int) int { return n * n })
// reduce to the sum
res := u.Reduce(squares, func(n, acc int) int { return n + acc }, 0)
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
// filter even numbers from the slice
evens := u.Filter(numbers, func(n int) bool { return n%2 == 0 })
// square every number in the slice
squares := u.Map(evens, func(n int) int { return n * n })
// reduce to the sum
res := u.Reduce(squares, func(n, acc int) int { return n + acc }, 0)
fmt.Println(res) // 120
fmt.Println(res) // 120
}
```
## Getting Started
These instructions will get you a copy of the project up and running on your local
machine for development and testing purposes.
These instructions will get you a copy of the project up and running on your local machine for development and testing
purposes.
### Prerequisites
You need at least `go1.24` for development. The project is shipped with a [Dockerfile](Dockerfile)
based on `go1.24`.
You need at least `go1.18` for development. The project is shipped with a [Dockerfile](Dockerfile) based on `go1.18`. If
you prefer local development, at the moment the easiest way to do it:
If you prefer local development, navigate to the [official
download page](https://go.dev/dl/) and install version `1.24` or beyond.
```shell script
go install golang.org/dl/go1.18beta1@latest
go1.18beta1 download
```
### Installing
First clone the repository
```sh
```shell
git clone https://github.com/rjNemo/underscore.git
```
Install dependencies
```sh
```shell
go mod download
```
@ -78,108 +73,36 @@ And that's it.
## Tests
To run the unit tests, you can simply run:
Building the docker image will run the tests automatically. Otherwise, you can simply run:
```sh
make test
```shell
go test ./...
```
## Functions
`underscore` provides many of functions that support your favorite functional helpers
`underscore` provides 100s of functions that support your favorite functional helpers
### Collections
- `All`
- `Any`
- `Chunk`
- `Contains`
- `ContainsBy`
- `Count`
- `Difference`
- `Drop`
- `Contains` (only numerics values at the moment)
- `Each`
- `Every`
- `Filter`
- `Find`
- `Flatmap`
- `GroupBy`
- `Intersection`
- `Join` / `JoinProject`
- `Last`
- `Map`
- `Max`
- `Min`
- `OrderBy`
- `Partition`
- `Range`
- `Reduce`
- `RemoveAt`
- `Sum` / `SumMap`
- `Unique`
- `UniqueBy`
- `UniqueInPlace`
- `Zip`
- `Some`
### Pipe
### Chaining
Calling `NewPipe` will cause all future method calls to return wrapped values. When
you've finished the computation, call `Value` to retrieve the final value.
Calling `NewChain` will cause all future method calls to return wrapped objects. When you've finished the computation,
call `Value` to retrieve the final value.
Methods not returning a slice such as `Reduce`, `All`, `Any`, will break the `Chain`
and return `Value` instantly.
### Concurrency
- `ParallelMap(ctx, values, workers, fn)`: apply a function concurrently while preserving order and supporting context cancellation.
- `ParallelFilter(ctx, values, workers, fn)`: filter concurrently with order preserved and context support.
```go
package main
import (
"context"
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
out, err := u.ParallelMap(context.Background(), []int{1, 2, 3, 4}, 4,
func(ctx context.Context, n int) (int, error) { return n * n, nil },
)
fmt.Println(out, err) // [1 4 9 16] <nil>
}
```
```go
// ParallelFilter example
package main
import (
"context"
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
out, err := u.ParallelFilter(context.Background(), []int{1,2,3,4,5}, 3,
func(ctx context.Context, n int) (bool, error) { return n%2==0, nil },
)
fmt.Println(out, err) // [2 4] <nil>
}
```
### Utilities
- `Ternary`: conditional expression helper
- `ToPointer`: convert values to pointers
- `SortSliceASC` / `SortSliceDESC`: sort slices in ascending or descending order
- `Result`, `Ok`, `Err`, `ToResult`: Result type for error handling
- `Tuple`: generic tuple type for paired values
### Subpackages
- `maps.Keys(m)` / `maps.Values(m)`: extract keys or values from maps
- `maps.Map(m, fn)`: transform map entries
Methods not returning a slice such as `Reduce`, `Every`, `Some`, will break the chain and return `Value` instantly.
## Built With
@ -187,8 +110,8 @@ func main() {
## Contributing
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct,
and the process for submitting pull requests to us.
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull
requests to us.
## Versioning
@ -199,15 +122,13 @@ the [tags on this repository](https://github.com/rjNemo/underscore/tags).
- **Ruidy** - _Initial work_ - [Ruidy](https://github.com/rjNemo)
See also the list of [contributors](https://github.com/rjNemo/underscore/contributors)
who participated in this project.
See also the list of [contributors](https://github.com/rjNemo/underscore/contributors) who participated in this project.
## License
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md)
file for details
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
## Acknowledgments
This project is largely inspired by [Underscore.js](https://underscorejs.org/#)
library. Check out the original project if you don't already know it.
This project is largely inspired by [Underscore.js](https://underscorejs.org/#) library. Check out the original project
if you don't already know it.

View file

@ -1,21 +0,0 @@
package underscore_test
import (
"testing"
"github.com/stretchr/testify/assert"
u "github.com/rjNemo/underscore"
)
func TestAll(t *testing.T) {
nums := []int{1, 3, 5, 7, 9}
isOdd := func(n int) bool { return n%2 != 0 }
assert.True(t, u.All(nums, isOdd))
}
func TestNotAll(t *testing.T) {
nums := []int{1, 3, 5, 7, 9, 10}
isOdd := func(n int) bool { return n%2 != 0 }
assert.False(t, u.All(nums, isOdd))
}

9
any.go
View file

@ -1,9 +0,0 @@
package underscore
import "slices"
// Any returns true if any of the values in the slice pass the predicate truth test.
// Short-circuits and stops traversing the slice if a true element is found.
func Any[T any](values []T, predicate func(T) bool) bool {
return slices.ContainsFunc(values, predicate)
}

View file

@ -1,23 +0,0 @@
package underscore_test
import (
"testing"
"github.com/stretchr/testify/assert"
u "github.com/rjNemo/underscore"
)
func TestSome(t *testing.T) {
nums := []int{1, 2, 4, 6, 8}
isOdd := func(n int) bool { return n%2 != 0 }
assert.True(t, u.Any(nums, isOdd))
}
func TestNotSome(t *testing.T) {
nums := []int{2, 4, 6, 8}
isOdd := func(n int) bool { return n%2 != 0 }
assert.False(t, u.Any(nums, isOdd))
}

View file

@ -1,56 +1,47 @@
package underscore
import (
"cmp"
)
import "constraints"
type Pipe[T cmp.Ordered] struct {
type Chain[T constraints.Ordered] struct {
Value []T
}
// NewPipe starts a Pipe. All future method calls will return Pipe structs. When you've finished the computation,
// NewChain starts a Chain. All future method calls will return Chain structs. When you've finished the computation,
// call Value to retrieve the final value.
//
// Methods not returning a slice such as Reduce, All, Any, will break the Pipe and return Value instantly.
func NewPipe[T cmp.Ordered](value []T) Pipe[T] {
return Pipe[T]{Value: value}
// Methods not returning a slice such as Reduce, Every, Some, will break the chain and return Value instantly.
func NewChain[T constraints.Ordered](value []T) Chain[T] {
return Chain[T]{Value: value}
}
// All returns true if all the values in the slice pass the predicate truth test.
// Short-circuits and stops traversing the slice if a false element is found.
// Breaks the Pipe.
func (c Pipe[T]) All(predicate func(T) bool) bool {
return All(c.Value, predicate)
}
// Any returns true if any of the values in the slice pass the predicate truth test.
// Short-circuits and stops traversing the slice if a true element is found.
// Breaks the Pipe.
func (c Pipe[T]) Any(predicate func(T) bool) bool {
return Any(c.Value, predicate)
}
// Contains returns true if the value is present in the slice and breaks the Pipe.
func (c Pipe[T]) Contains(value T) bool {
// Contains returns true if the value is present in the slice and breaks the Chain.
func (c Chain[T]) Contains(value T) bool {
return Contains(c.Value, value)
}
// Each iterates over a slice of elements, yielding each in turn to an action function.
// Breaks the Pipe.
func (c Pipe[T]) Each(action func(T)) {
// Breaks the Chain.
func (c Chain[T]) Each(action func(T)) {
Each(c.Value, action)
}
// Every returns true if all the values in the slice pass the predicate truth test.
// Short-circuits and stops traversing the slice if a false element is found.
// Breaks the Chain.
func (c Chain[T]) Every(predicate func(T) bool) bool {
return Every(c.Value, predicate)
}
// Filter looks through each value in the slice, returning a slice of all the values that pass a truth test (predicate).
func (c Pipe[T]) Filter(predicate func(n T) bool) Pipe[T] {
return Pipe[T]{Value: Filter(c.Value, predicate)}
func (c Chain[T]) Filter(predicate func(n T) bool) Chain[T] {
return Chain[T]{Value: Filter(c.Value, predicate)}
}
// Find looks through each value in the slice, returning the first one that passes a truth test (predicate),
// or the default value for the type and an error if no value passes the test.
// The function returns as soon as it finds an acceptable element, and doesn't traverse the entire slice.
// Breaks the Pipe.
func (c Pipe[T]) Find(predicate func(n T) bool) (T, error) {
// Breaks the Chain.
func (c Chain[T]) Find(predicate func(n T) bool) (T, error) {
return Find(c.Value, predicate)
}
@ -58,35 +49,42 @@ func (c Pipe[T]) Find(predicate func(n T) bool) (T, error) {
// a transform function.
//
// TODO: Move from T to P.
func (c Pipe[T]) Map(transform func(n T) T) Pipe[T] {
return Pipe[T]{Value: Map(c.Value, transform)}
func (c Chain[T]) Map(transform func(n T) T) Chain[T] {
return Chain[T]{Value: Map(c.Value, transform)}
}
// Max returns the maximum value in the slice.
// This function can currently only compare numbers reliably.
// This function uses operator <.
// Breaks the Pipe.
func (c Pipe[T]) Max() T {
// Breaks the Chain.
func (c Chain[T]) Max() T {
return Max(c.Value)
}
// Min returns the minimum value in the slice.
// This function can currently only compare numbers reliably.
// This function uses operator <.
// Breaks the Pipe.
func (c Pipe[T]) Min() T {
// Breaks the Chain.
func (c Chain[T]) Min() T {
return Min(c.Value)
}
// Partition splits the slice into two slices: one whose elements all satisfy predicate
// and one whose elements all do not satisfy predicate.
// Breaks the Pipe.
func (c Pipe[T]) Partition(predicate func(T) bool) ([]T, []T) {
// Breaks the Chain.
func (c Chain[T]) Partition(predicate func(T) bool) ([]T, []T) {
return Partition(c.Value, predicate)
}
// Reduce combine a list of values into a single value and breaks the Pipe.
// Reduce combine a list of values into a single value and breaks the Chain.
// acc is the initial state, and each successive step of it should be returned by the reduction function.
func (c Pipe[T]) Reduce(reducer func(n, acc T) T, acc T) T {
func (c Chain[T]) Reduce(reducer func(n, acc T) T, acc T) T {
return Reduce(c.Value, reducer, acc)
}
// Some returns true if any of the values in the slice pass the predicate truth test.
// Short-circuits and stops traversing the slice if a true element is found.
// Breaks the Chain.
func (c Chain[T]) Some(predicate func(T) bool) bool {
return Some(c.Value, predicate)
}

View file

@ -3,16 +3,16 @@ package underscore_test
import (
"testing"
u "github.com/rjNemo/underscore"
"github.com/stretchr/testify/assert"
u "github.com/rjNemo/underscore"
)
func TestChainFilter(t *testing.T) {
want := []int{2, 4, 6, 8}
assert.Equal(t,
want,
u.NewPipe(nums).Filter(isEven).Value,
u.NewChain(nums).Filter(isEven).Value,
)
}
@ -20,7 +20,7 @@ func TestChainFilterMap(t *testing.T) {
want := []int{4, 16, 36, 64}
assert.Equal(t,
want,
u.NewPipe(nums).
u.NewChain(nums).
Filter(isEven).
Map(toSquare).
Value)
@ -30,14 +30,14 @@ func TestChainFilterMapReduce(t *testing.T) {
want := 120
assert.Equal(t,
want,
u.NewPipe(nums).
u.NewChain(nums).
Filter(isEven).
Map(toSquare).
Reduce(sum, 0))
}
func TestChainFilterMapContains(t *testing.T) {
assert.True(t, u.NewPipe(nums).
assert.True(t, u.NewChain(nums).
Filter(isEven).
Map(toSquare).
Contains(16))
@ -46,22 +46,22 @@ func TestChainFilterMapContains(t *testing.T) {
func TestChainFilterMapEach(t *testing.T) {
want := []int{5, 17, 37, 65}
res := make([]int, 0)
u.NewPipe(nums).
u.NewChain(nums).
Filter(isEven).
Map(toSquare).
Each(func(n int) { res = append(res, n+1) })
assert.Equal(t, want, res)
}
func TestChainFilterMapAll(t *testing.T) {
assert.True(t, u.NewPipe(nums).
func TestChainFilterMapEvery(t *testing.T) {
assert.True(t, u.NewChain(nums).
Filter(isEven).
Map(toSquare).
All(func(n int) bool { return n%4 == 0 }))
Every(func(n int) bool { return n%4 == 0 }))
}
func TestChainFilterMapFind(t *testing.T) {
n, err := u.NewPipe(nums).
n, err := u.NewChain(nums).
Filter(isEven).
Map(toSquare).
Find(func(n int) bool { return n%4 == 0 })
@ -71,7 +71,7 @@ func TestChainFilterMapFind(t *testing.T) {
func TestChainFilterMapMax(t *testing.T) {
want := 64
assert.Equal(t, want, u.NewPipe(nums).
assert.Equal(t, want, u.NewChain(nums).
Filter(isEven).
Map(toSquare).
Max())
@ -79,7 +79,7 @@ func TestChainFilterMapMax(t *testing.T) {
func TestChainFilterMapMin(t *testing.T) {
w := 4
assert.Equal(t, w, u.NewPipe(nums).
assert.Equal(t, w, u.NewChain(nums).
Filter(isEven).
Map(toSquare).
Min())
@ -88,7 +88,7 @@ func TestChainFilterMapMin(t *testing.T) {
func TestChainFilterMapPartition(t *testing.T) {
wantLeft := []int{4, 16}
wantRight := []int{36, 64}
left, right := u.NewPipe(nums).
left, right := u.NewChain(nums).
Filter(isEven).
Map(toSquare).
Partition(func(n int) bool { return n < 20 })
@ -97,11 +97,11 @@ func TestChainFilterMapPartition(t *testing.T) {
assert.Equal(t, wantRight, right)
}
func TestChainFilterMapAny(t *testing.T) {
assert.True(t, u.NewPipe(nums).
func TestChainFilterMapSome(t *testing.T) {
assert.True(t, u.NewChain(nums).
Filter(isEven).
Map(toSquare).
Any(func(n int) bool { return n%64 == 0 }))
Some(func(n int) bool { return n%64 == 0 }))
}
var nums = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}

View file

@ -1,19 +0,0 @@
package underscore
// Chunk splits the input slice into groups of size n.
// If n <= 0, it returns nil. The final chunk may be smaller than n.
func Chunk[T any](values []T, n int) [][]T {
if n <= 0 {
return nil
}
l := len(values)
if l == 0 {
return [][]T{}
}
chunks := make([][]T, 0, (l+n-1)/n)
for i := 0; i < l; i += n {
j := min(i+n, l)
chunks = append(chunks, values[i:j])
}
return chunks
}

View file

@ -1,34 +0,0 @@
package underscore_test
import (
"testing"
"github.com/stretchr/testify/assert"
u "github.com/rjNemo/underscore"
)
func TestChunk(t *testing.T) {
in := []int{1, 2, 3, 4, 5}
got := u.Chunk(in, 2)
want := [][]int{{1, 2}, {3, 4}, {5}}
assert.Equal(t, want, got)
}
func TestChunkLargeSize(t *testing.T) {
in := []int{1, 2, 3}
got := u.Chunk(in, 10)
want := [][]int{{1, 2, 3}}
assert.Equal(t, want, got)
}
func TestChunkInvalidSize(t *testing.T) {
var in []int
assert.Nil(t, u.Chunk(in, 0))
assert.Nil(t, u.Chunk(in, -1))
}
func TestChunkEmpty(t *testing.T) {
got := u.Chunk([]int{}, 1)
assert.Equal(t, 0, len(got))
}

View file

@ -1,13 +1,13 @@
package underscore
import "slices"
import "constraints"
// Contains returns true if the value is present in the slice
func Contains[T comparable](values []T, value T) bool {
return slices.Contains(values, value)
}
// ContainsBy returns true if any element in the slice satisfies the predicate.
func ContainsBy[T any](values []T, predicate func(T) bool) bool {
return slices.ContainsFunc(values, predicate)
func Contains[T constraints.Ordered](values []T, value T) bool {
for _, v := range values {
if v == value {
return true
}
}
return false
}

View file

@ -12,24 +12,3 @@ func TestContains(t *testing.T) {
nums := []int{1, 3, 5, 7, 9}
assert.True(t, u.Contains(nums, 5))
}
func TestNotContains(t *testing.T) {
nums := []int{1, 3, 5, 7, 9}
assert.False(t, u.Contains(nums, 15))
}
func TestContainsBy(t *testing.T) {
nums := []int{1, 3, 5, 8}
assert.True(t, u.ContainsBy(nums, func(n int) bool { return n%2 == 0 }))
assert.False(t, u.ContainsBy(nums, func(n int) bool { return n < 0 }))
}
func TestContainsByStruct(t *testing.T) {
type user struct {
ID int
Name string
}
users := []user{{1, "a"}, {2, "b"}, {3, "c"}}
assert.True(t, u.ContainsBy(users, func(u user) bool { return u.ID == 2 }))
assert.False(t, u.ContainsBy(users, func(u user) bool { return u.Name == "z" }))
}

View file

@ -1,13 +0,0 @@
package underscore
// Count returns the number of elements in the slice that satisfy the predicate.
// example: Count([]int{1,2,3,4,5}, func(n int) bool { return n%2 == 0 }) // 2
func Count[T any](slice []T, predicate func(T) bool) int {
count := 0
for _, item := range slice {
if predicate(item) {
count++
}
}
return count
}

View file

@ -1,65 +0,0 @@
package underscore
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Count_Can_Count_Numbers(t *testing.T) {
numbers := Range(1, 100)
count := Count(numbers, func(n int) bool {
return n%2 == 0
})
assert.Equal(t, 50, count)
}
type People struct {
Name string
Age int
Gender string
}
func Test_Count_Can_Count_People(t *testing.T) {
people := []People{
{Name: "Andy", Age: 43, Gender: "M"},
{Name: "Fred", Age: 33, Gender: "M"},
{Name: "Jack", Age: 23, Gender: "M"},
{Name: "Jill", Age: 43, Gender: "F"},
{Name: "Anna", Age: 33, Gender: "F"},
{Name: "Arya", Age: 23, Gender: "F"},
{Name: "Jane", Age: 13, Gender: "F"},
}
a := Count(people, func(p People) bool {
return strings.HasPrefix(p.Name, "A")
})
assert.Equal(t, 3, a)
females := Count(people, func(p People) bool {
return p.Gender == "F"
})
assert.Equal(t, 4, females)
males := Count(people, func(p People) bool {
return p.Gender == "M"
})
assert.Equal(t, 3, males)
over30 := Count(people, func(p People) bool {
return p.Age > 30
})
assert.Equal(t, 4, over30)
under30 := Count(people, func(p People) bool {
return p.Age < 30
})
assert.Equal(t, 3, under30)
under20 := Count(people, func(p People) bool {
return p.Age < 20
})
assert.Equal(t, 1, under20)
}

View file

@ -1,8 +0,0 @@
package underscore
// Difference Returns a copy of the array with all instances of the values that are not present in the other array.
func Difference[T comparable](slice, other []T) []T {
return Filter(slice, func(n T) bool {
return !Contains(other, n)
})
}

View file

@ -1,17 +0,0 @@
package underscore_test
import (
"testing"
"github.com/stretchr/testify/assert"
u "github.com/rjNemo/underscore"
)
func TestDifference(t *testing.T) {
nums := []int{1, 3, 5, 6, 7, 9}
reject := []int{9, 7, 5, 4}
want := []int{1, 3, 6}
assert.Equal(t, want, u.Difference(nums, reject))
}

View file

@ -0,0 +1,46 @@
html
--color-mode: "light"
--light: #fff
--dark: rgb(28,28,30)
--haze: #f2f5f7
--bubble: rgb(36,36,38)
--accent: var(--haze)
--bg: var(--light)
--code-bg: var(--accent)
--overlay: var(--light)
--text: #111
--font: 'Metropolis', sans-serif
--border-color: #eee
--inline-color: darkgoldenrod
--theme: #209CEE
--ease: ease
@mixin darkmode
--color-mode: "dark"
--theme: #209CEE
--bg: var(--dark)
--text: #eee
--accent: var(--bubble)
--overlay: var(--bubble)
--border-color: transparent
*
box-shadow: none !important
&[data-mode="dark"]
@include darkmode
.color
&_choice
&::after
background-image: url(../icons/moon.svg)
&.dark:not([data-mode="light"])
@media (prefers-color-scheme: dark)
@include darkmode
%narrow
max-width: 750px
margin: 0 auto
blockquote
+ .highlight_wrap
margin-top: 2.25rem

View file

@ -1,25 +1,12 @@
baseURL = "https://underscore.onrender.com/"
title = "underscore"
theme = "hugo-geekdoc"
pluralizeListTitles = false
# Geekdoc required configuration
pygmentsUseClasses = true
pygmentsCodeFences = true
disablePathToLower = true
# Required if you want to render robots.txt template
baseURL = 'http://example.org/'
languageCode = 'en-us'
title = 'Underscore'
theme = "compose" # edit this if you'ld rather use a fork of this repo
enableRobotsTXT = true
# Needed for mermaid shortcodes
[markup]
[markup.goldmark.renderer]
# Needed for mermaid shortcode
unsafe = true
[markup.tableOfContents]
startLevel = 1
endLevel = 9
[taxonomies]
tag = "tags"
[outputs]
home = ["HTML", "RSS","JSON"]
[params]
logo.lightMode = "logo.png"
logo.darkMode = "logo.png"
author.name = "Ruidy"
author.url = "https://github.com/rjNemo"

View file

@ -1,48 +0,0 @@
---
title: _Underscore
---
![License](https://img.shields.io/github/license/rjNemo/underscore?style=for-the-badge)
[![Go version](https://img.shields.io/github/go-mod/go-version/rjNemo/underscore?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/rjNemo/underscore)
![Go report](https://goreportcard.com/badge/github.com/rjNemo/underscore?style=for-the-badge)
![test coverage](https://img.shields.io/codecov/c/github/rjNemo/underscore?style=for-the-badge&logo=codecov)
![underscore](https://socialify.git.ci/rjNemo/underscore/image?description=1&font=KoHo&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FrjNemo%2Funderscore%2Fmain%2Fdocs%2Fstatic%2Flogo.png&owner=1&pattern=Floating%20Cogs&stargazers=1&theme=Dark)
`underscore` is a `Go` library providing useful functional programming helpers without
extending any built-in objects.
It is mostly a port from the `underscore.js` library based on generics available
from `go1.18`.
## Quick Start
Install the library using
```shell
go get github.com/rjNemo/underscore
```
Please check out the [examples](https://github.com/rjNemo/underscore/tree/main/examples)
to see how to use the library.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
// filter even numbers from the slice
evens := u.Filter(numbers, func(n int) bool { return n%2 == 0 })
// square every number in the slice
squares := u.Map(evens, func(n int) int { return n * n })
// reduce to the sum
res := u.Reduce(squares, func(n, acc int) int { return n + acc }, 0)
fmt.Println(res) // 120
}
```

View file

@ -1,22 +0,0 @@
---
title: "All"
date: 2021-12-30T13:24:39-04:00
---
`All` returns true if all the values in the slice pass the predicate truth test.\
Short-circuits and stops traversing the slice if a false element is found.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 3, 5, 7, 9}
isOdd := func(n int) bool { return n%2 != 0 }
fmt.Println(u.All(nums, isOdd)) // true
}
```

View file

@ -1,22 +0,0 @@
---
title: "Any"
date: 2022-03-21T13:26:01-04:00
---
`Any` returns true if any of the values in the slice pass the predicate truth test. Short-circuits and stops traversing
the slice if a true element is found.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 4, 6, 8}
isEven := func(n int) bool { return n%2 == 0 }
fmt.Println(u.Any(nums, isEven)) // true
}
```

View file

@ -1,19 +0,0 @@
---
title: "Chunk"
date: 2025-09-01T00:00:00-00:00
---
`Chunk` splits a slice into groups of size `n`. The last chunk may be smaller.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
fmt.Println(u.Chunk([]int{1,2,3,4,5}, 2)) // [[1 2] [3 4] [5]]
}
```

View file

@ -1,21 +0,0 @@
---
title: "Contains"
date: 2022-03-21T13:30:29-04:00
---
`Contains` returns true if the value is present in the slice.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 3, 5, 7, 9}
fmt.Println(u.Contains(nums, 5)) // true
}
```

View file

@ -1,20 +0,0 @@
---
title: "ContainsBy"
date: 2025-09-01T00:00:00-00:00
---
`ContainsBy` returns true if any element satisfies the predicate.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 3, 5, 8}
fmt.Println(u.ContainsBy(nums, func(n int) bool { return n%2 == 0 })) // true
}
```

View file

@ -1,22 +0,0 @@
---
title: "Difference"
date: 2022-03-21T13:48:21-04:00
---
Returns a copy of the array with all instances of the values that are not present in the other arrays.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 3, 5, 6, 7, 9}
reject := []int{9, 7, 5, 4}
fmt.Println(u.Difference(nums, reject)) // {1, 3, 6}
}
```

View file

@ -1,22 +0,0 @@
---
title: "Drop"
date: 2022-03-21T13:48:21-04:00
---
`Drop` returns the rest of the elements in a slice. Pass an index to return the values of the slice from that index
onward.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 9, 2, 8, 3, 7, 4, 6, 5}
fmt.Println(u.Drop(nums, 3)) // {1, 9, 2, 3, 7, 4, 6, 5}
}
```

View file

@ -1,25 +0,0 @@
---
title: "DropWhile"
date: 2025-01-16T00:00:00-00:00
---
`DropWhile` drops elements from the beginning of the slice while the predicate returns true. It returns the remaining elements starting from the first element where the predicate returns false.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
lessThan5 := func(n int) bool { return n < 5 }
fmt.Println(u.DropWhile(nums, lessThan5)) // [5, 6, 7, 8, 9]
words := []string{"apple", "banana", "cherry", "date"}
shortWords := func(s string) bool { return len(s) < 6 }
fmt.Println(u.DropWhile(words, shortWords)) // ["banana", "cherry", "date"]
}
```

View file

@ -1,25 +0,0 @@
---
title: "Each"
date: 2022-03-21T13:30:59-04:00
---
`Each` iterates over a slice of elements, yielding each in turn to an action function.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
names := []string{"Alice", "Bob", "Charles"}
res := make([]string, 0)
u.Each(names, func(n string) {
res = append(res, fmt.Sprintf("Hi %s", n))
})
fmt.Println(res) // {"Hi Alice", "Hi Bob", "Hi Charles"}
}
```

View file

@ -1,21 +0,0 @@
---
title: "Filter"
date: 2022-03-21T13:31:21-04:00
---
`Filter` looks through each value in the slice, returning a slice of all the values that pass a truth test (predicate).
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
isEven := func(n int) bool { return n%2 == 0 }
fmt.Println(u.Filter(nums, isEven)) // {0, 2, 4, 6, 8}
}
```

View file

@ -1,26 +0,0 @@
---
title: "Find"
date: 2022-03-21T13:31:40-04:00
---
Find looks through each value in the slice, returning the first one that passes a truth test (predicate), or the default
value for the type and an error if no value passes the test. The function returns as soon as it finds an acceptable
element, and doesn't traverse the entire slice.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{2, 4, 5, 6, 8, 0}
isOdd := func(n int) bool { return n%2 != 0 }
n, err := u.Find(nums, isOdd)
fmt.Println(n) // 5
fmt.Println(err) // nil
}
```

View file

@ -1,31 +0,0 @@
---
title: "First"
date: 2025-01-16T00:00:00-00:00
---
`First` returns the first element of the slice. Returns an error if the slice is empty.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3, 4, 5}
first, err := u.First(nums)
if err != nil {
panic(err)
}
fmt.Println(first) // 1
// Handle empty slice
empty := []int{}
_, err = u.First(empty)
if err != nil {
fmt.Println("Error:", err) // Error: underscore: empty slice
}
}
```

View file

@ -1,23 +0,0 @@
---
title: "FirstN"
date: 2025-01-16T00:00:00-00:00
---
`FirstN` returns the first n elements of the slice. If n is greater than the slice length, returns the entire slice. If n is less than or equal to 0, returns an empty slice.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(u.FirstN(nums, 3)) // [1, 2, 3]
fmt.Println(u.FirstN(nums, 0)) // []
fmt.Println(u.FirstN(nums, 10)) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
fmt.Println(u.FirstN(nums, -5)) // []
}
```

View file

@ -1,23 +0,0 @@
---
title: "Flatmap"
date: 2022-08-10T16:49:56+02:00
draft: false
---
Flatmap flattens the input slice element into the new slice. FlatMap maps every element with the help of a mapper function, then flattens the input slice element into the new slice.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3, 4}
mapper := func(n int) []int { return []int{(n - 1) * n, (n) * n} }
res := u.Flatmap(nums, mapper)
fmt.Println(res) // {0, 1, 2, 4, 6, 9, 12, 16}
}
```

View file

@ -1,39 +0,0 @@
---
title: "FoldRight"
date: 2025-01-16T00:00:00-00:00
---
`FoldRight` is like Reduce but processes elements from right to left. Also known as foldr in Haskell. Important for non-associative operations where the order of evaluation matters.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
// Subtraction is non-associative
nums := []int{1, 2, 3}
// FoldRight: 1 - (2 - (3 - 0)) = 1 - (2 - 3) = 1 - (-1) = 2
result := u.FoldRight(nums, 0, func(n, acc int) int { return n - acc })
fmt.Println(result) // 2
// Compare with Reduce (left fold): (0 - 1) - 2 - 3 = -6
leftResult := u.Reduce(nums, func(n, acc int) int { return acc - n }, 0)
fmt.Println(leftResult) // -6
// Building a list in order
buildList := u.FoldRight(nums, []int{}, func(n int, acc []int) []int {
return append([]int{n}, acc...)
})
fmt.Println(buildList) // [1, 2, 3]
// String concatenation
words := []string{"a", "b", "c"}
concat := u.FoldRight(words, "", func(s, acc string) string { return s + acc })
fmt.Println(concat) // "abc"
}
```

View file

@ -1,21 +0,0 @@
---
title: "Group by"
date: 2023-06-07T00:49:56+02:00
---
GroupBy splits a slice into a map[K][]V grouped by the result of the iterator function.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []float64{1.3, 2.1, 2.4}
groupingFunc := func(n float64) int { return int(math.Floor(n)) }
res := u.GroupBy(nums, groupingFunc) // { 1: {1.3}, 2: {2.1, 2.4}}
}
```

View file

@ -1,32 +0,0 @@
---
title: "Init"
date: 2025-01-16T00:00:00-00:00
---
`Init` returns all elements except the last one, and the last element separately. Returns an empty slice and zero value if the input slice is empty. Useful for destructuring lists from the right.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3, 4, 5}
init, last := u.Init(nums)
fmt.Println(init) // [1, 2, 3, 4]
fmt.Println(last) // 5
// Single element
single, val := u.Init([]int{42})
fmt.Println(single) // []
fmt.Println(val) // 42
// Empty slice
empty, zero := u.Init([]int{})
fmt.Println(empty) // []
fmt.Println(zero) // 0
}
```

View file

@ -1,25 +0,0 @@
---
title: "Intersection"
date: 2022-03-27T00:24:11-04:00
---
Intersection computes the list of values that are the intersection of all the slices.
Each value in the result is present in each of the slices.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main(){
a := []int{1, 3, 5, 7, 9}
b := []int{2, 3, 5, 8, 0}
fmt.Println(u.Intersection(a, b)) // {3, 5}
}
```

View file

@ -1,28 +0,0 @@
---
title: "Intersperse"
date: 2025-01-16T00:00:00-00:00
---
`Intersperse` inserts a separator between each element of the slice. Returns an empty slice if the input is empty. Returns the original element if the input has only one element.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3, 4, 5}
fmt.Println(u.Intersperse(nums, 0)) // [1, 0, 2, 0, 3, 0, 4, 0, 5]
// Useful for formatting
words := []string{"apple", "banana", "cherry"}
fmt.Println(u.Intersperse(words, ",")) // ["apple", ",", "banana", ",", "cherry"]
// Single element - no separator added
single := []int{42}
fmt.Println(u.Intersperse(single, 0)) // [42]
}
```

View file

@ -1,28 +0,0 @@
---
title: "Last"
date: 2022-03-21T13:46:24-04:00
---
`Last` returns the last element of the slice. Panics if the slice is empty with a clear error message.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 9, 2, 8, 3, 7, 4, 6, 5}
fmt.Println(u.Last(nums)) // 5
// Single element
single := []int{42}
fmt.Println(u.Last(single)) // 42
// Empty slice panics with clear message
// empty := []int{}
// u.Last(empty) // panic: underscore.Last: empty slice
}
```

View file

@ -1,24 +0,0 @@
---
title: "Map"
date: 2022-03-21T13:32:10-04:00
---
`Map` produces a new slice of values by mapping each value in the slice through a
transform function.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3}
toSquare := func(n int) int {
return n * n
}
fmt.Println(u.Map(nums, toSquare)) // {1, 4, 9}
}
```

View file

@ -1,21 +0,0 @@
---
title: "Max"
date: 2022-03-21T13:32:43-04:00
---
`Max` returns the maximum value in the slice. This function can currently only compare numbers reliably. This function
uses operator `<`.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 9, 2, 8, 3, 7, 4, 6, 5}
fmt.Println(u.Max(nums)) // 9
}
```

View file

@ -1,21 +0,0 @@
---
title: "Min"
date: 2022-03-21T13:33:03-04:00
---
`Min` returns the minimum value in the slice. This function can currently only compare numbers reliably. This function
uses operator `<`.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 9, 2, 8, 3, 7, 4, 6, 5}
fmt.Println(u.Min(nums)) // 1
}
```

View file

@ -1,24 +0,0 @@
---
title: "ParallelFilter"
date: 2025-09-01T00:00:00-00:00
---
`ParallelFilter` filters a slice concurrently with a worker pool, preserves order,
and supports context cancellation.
```go
package main
import (
"context"
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
out, err := u.ParallelFilter(context.Background(), []int{1,2,3,4,5}, 3,
func(ctx context.Context, n int) (bool, error) { return n%2==0, nil },
)
fmt.Println(out, err) // [2 4] <nil>
}
```

View file

@ -1,25 +0,0 @@
---
title: "ParallelMap"
date: 2025-09-01T00:00:00-00:00
---
`ParallelMap` applies a function to each element concurrently using a worker pool,
preserves order, and supports context cancellation.
```go
package main
import (
"context"
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
out, err := u.ParallelMap(context.Background(),
[]int{1,2,3,4}, 4, func(ctx context.Context, n int) (int, error) {
return n*n, nil
})
fmt.Println(out, err) // [1 4 9 16] <nil>
}
```

View file

@ -1,51 +0,0 @@
---
title: "ParallelReduce"
date: 2025-01-16T00:00:00-00:00
---
`ParallelReduce` applies a reduction function in parallel using a worker pool. The operation must be associative and commutative for correct results. If workers <= 0, defaults to GOMAXPROCS. On error, the first error is returned and processing is canceled.
**Note:** This is an experimental function. Order of operations is not guaranteed, so use only with associative and commutative operations (like addition, multiplication, min, max).
```go
package main
import (
"context"
"fmt"
"time"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
ctx := context.Background()
// Parallel sum (safe - addition is associative and commutative)
result, err := u.ParallelReduce(ctx, nums, 4, func(ctx context.Context, n int, acc int) (int, error) {
// Simulate expensive computation
time.Sleep(10 * time.Millisecond)
return n + acc, nil
}, 0)
if err != nil {
panic(err)
}
fmt.Println(result) // Result will vary due to parallel execution
// With context cancellation
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
_, err = u.ParallelReduce(ctx, nums, 4, func(ctx context.Context, n int, acc int) (int, error) {
time.Sleep(100 * time.Millisecond)
return n + acc, nil
}, 0)
if err != nil {
fmt.Println("Operation was cancelled:", err)
}
}
```
**Warning:** Do not use ParallelReduce for non-associative operations like subtraction or division, as the results will be unpredictable due to parallel execution order.

View file

@ -1,25 +0,0 @@
---
title: "Partition"
date: 2022-03-21T13:33:23-04:00
---
`Partition` splits the slice into two slices: one whose elements all satisfy predicate and one whose elements all do not
satisfy predicate.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
isEven := func(n int) bool { return n%2 == 0 }
evens, odds := u.Partition(nums, isEven)
fmt.Println(evens) // {0, 2, 4, 6, 8}
fmt.Println(odds) // {1, 3, 5, 7, 9}
}
```

View file

@ -1,22 +0,0 @@
---
title: "Reduce"
date: 2022-03-21T13:33:52-04:00
---
`Reduce` combine a list of values into a single value. `acc` is the initial state, and each successive step of it should
be returned by the reduction function.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
sum := func(n, acc int) int { return n + acc }
fmt.Println(u.Reduce(nums, sum, 0)) // 45
}
```

View file

@ -1,43 +0,0 @@
---
title: "Replicate"
date: 2025-01-16T00:00:00-00:00
---
`Replicate` creates a slice containing count copies of value. Returns an empty slice if count is less than or equal to 0. Useful for initialization and testing.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
// Basic usage
fmt.Println(u.Replicate(3, "hello"))
// ["hello", "hello", "hello"]
// Numbers
fmt.Println(u.Replicate(5, 0))
// [0, 0, 0, 0, 0]
// Zero count
fmt.Println(u.Replicate(0, 42))
// []
// Negative count
fmt.Println(u.Replicate(-5, "x"))
// []
// Use case: initialize with default values
defaultScores := u.Replicate(10, 100)
fmt.Println(defaultScores)
// [100, 100, 100, 100, 100, 100, 100, 100, 100, 100]
// Use case: creating separators
separator := u.Replicate(40, "-")
fmt.Println(u.Reduce(separator, func(s, acc string) string { return acc + s }, ""))
// ----------------------------------------
}
```

View file

@ -1,37 +0,0 @@
---
title: "Scan"
date: 2025-01-16T00:00:00-00:00
---
`Scan` is like Reduce but returns all intermediate accumulator values. Also known as prefix scan or cumulative fold. Useful for tracking running totals, running maximums, or other cumulative operations.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
// Running sum
nums := []int{1, 2, 3, 4}
add := func(acc, n int) int { return acc + n }
fmt.Println(u.Scan(nums, 0, add)) // [1, 3, 6, 10]
// Running maximum
values := []int{3, 1, 4, 1, 5, 9, 2}
max := func(acc, n int) int {
if n > acc {
return n
}
return acc
}
fmt.Println(u.Scan(values, 0, max)) // [3, 3, 4, 4, 5, 9, 9]
// String concatenation
words := []string{"hello", "world", "!"}
concat := func(acc, s string) string { return acc + s }
fmt.Println(u.Scan(words, "", concat)) // ["hello", "helloworld", "helloworld!"]
}
```

View file

@ -1,43 +0,0 @@
---
title: "Sliding"
date: 2025-01-16T00:00:00-00:00
---
`Sliding` creates a sliding window view of the slice with the specified window size. Returns an empty slice if size is less than or equal to 0 or greater than the slice length. Useful for moving averages, n-grams, and pattern matching.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3, 4, 5}
fmt.Println(u.Sliding(nums, 3)) // [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
// Size 2
fmt.Println(u.Sliding(nums, 2)) // [[1, 2], [2, 3], [3, 4], [4, 5]]
// N-grams for text
words := []string{"the", "quick", "brown", "fox"}
bigrams := u.Sliding(words, 2)
fmt.Println(bigrams) // [["the", "quick"], ["quick", "brown"], ["brown", "fox"]]
// Moving average example
data := []int{10, 20, 30, 40, 50}
windows := u.Sliding(data, 3)
for _, window := range windows {
sum := 0
for _, v := range window {
sum += v
}
avg := sum / len(window)
fmt.Printf("Window: %v, Average: %d\n", window, avg)
}
// Window: [10 20 30], Average: 20
// Window: [20 30 40], Average: 30
// Window: [30 40 50], Average: 40
}
```

View file

@ -1,21 +0,0 @@
---
title: "Sum"
date: 2022-03-21T13:50:29-04:00
---
`Sum` adds elements of the slice.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(u.Sum(nums)) // 45
}
```

View file

@ -1,25 +0,0 @@
---
title: "TakeWhile"
date: 2025-01-16T00:00:00-00:00
---
`TakeWhile` returns elements from the beginning of the slice while the predicate returns true. It stops at the first element where the predicate returns false.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
lessThan5 := func(n int) bool { return n < 5 }
fmt.Println(u.TakeWhile(nums, lessThan5)) // [1, 2, 3, 4]
words := []string{"apple", "banana", "cherry", "date"}
shortWords := func(s string) bool { return len(s) < 6 }
fmt.Println(u.TakeWhile(words, shortWords)) // ["apple"]
}
```

View file

@ -1,47 +0,0 @@
---
title: "Tap"
date: 2025-01-16T00:00:00-00:00
---
`Tap` applies a function to each element for side effects (like debugging or logging) and returns the original slice unchanged. Useful for debugging pipelines without breaking the flow.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
// Debugging a pipeline
nums := []int{1, 2, 3, 4, 5}
result := u.Tap(
u.Map(
u.Filter(nums, func(n int) bool { return n%2 == 0 }),
func(n int) int { return n * 2 },
),
func(n int) {
fmt.Printf("Debug: %d\n", n) // Prints each value
},
)
fmt.Println(result) // [4, 8]
// Counting elements that pass through
count := 0
filtered := u.Tap(
u.Filter(nums, func(n int) bool { return n > 2 }),
func(n int) { count++ },
)
fmt.Printf("Found %d elements: %v\n", count, filtered)
// Found 3 elements: [3 4 5]
// Logging transformations
data := []string{"hello", "world"}
u.Tap(data, func(s string) {
fmt.Printf("Processing: %s\n", s)
})
}
```

View file

@ -1,45 +0,0 @@
---
title: "Transpose"
date: 2025-01-16T00:00:00-00:00
---
`Transpose` flips a matrix over its diagonal, swapping rows and columns. Returns an empty slice if the input is empty. Assumes all rows have the same length (uses the length of the first row).
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
// 2x3 matrix becomes 3x2 matrix
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
}
transposed := u.Transpose(matrix)
fmt.Println(transposed)
// [[1, 4], [2, 5], [3, 6]]
// Square matrix
square := [][]int{
{1, 2},
{3, 4},
}
fmt.Println(u.Transpose(square))
// [[1, 3], [2, 4]]
// Use case: converting rows to columns for processing
data := [][]string{
{"Name", "Age", "City"},
{"Alice", "30", "NYC"},
{"Bob", "25", "LA"},
}
byColumn := u.Transpose(data)
fmt.Println("Names:", byColumn[0]) // [Name Alice Bob]
fmt.Println("Ages:", byColumn[1]) // [Age 30 25]
fmt.Println("Cities:", byColumn[2]) // [City NYC LA]
}
```

View file

@ -1,21 +0,0 @@
---
title: "Unique"
date: 2022-04-12T17:18:04-04:00
---
`Unique` returns a duplicate-free version of the slice. Only the first occurrence of each value is kept.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 4, 2, 5, 3, 1, 5, 2, 8, 9}
fmt.Println(u.Unique(nums)) // 1, 4, 2, 5, 3, 8, 9
}
```

View file

@ -1,24 +0,0 @@
---
title: "UniqueBy"
date: 2025-09-01T00:00:00-00:00
---
`UniqueBy` returns a duplicate-free version of the slice using a key selector.
Order is preserved; the first occurrence of each key is kept.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
type User struct{ ID int; Email string }
func main() {
users := []User{{1, "a@x"}, {2, "b@x"}, {3, "a@x"}}
fmt.Println(u.UniqueBy(users, func(u User) string { return u.Email }))
// [{1 a@x} {2 b@x}]
}
```

View file

@ -1,21 +0,0 @@
---
title: "UniqueInPlace"
date: 2025-09-01T00:00:00-00:00
---
`UniqueInPlace` removes duplicates from a slice in place while preserving order.
Returns the shortened slice.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
xs := []int{1,4,2,5,3,1,5,2}
fmt.Println(u.UniqueInPlace(xs)) // [1 4 2 5 3]
}
```

View file

@ -1,43 +0,0 @@
---
title: "Unzip"
date: 2025-01-16T00:00:00-00:00
---
`Unzip` splits a slice of tuples into two separate slices. The inverse operation of Zip. Useful for separating paired data.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
// Basic usage
pairs := []u.Tuple[int, string]{
{Left: 1, Right: "a"},
{Left: 2, Right: "b"},
{Left: 3, Right: "c"},
}
nums, letters := u.Unzip(pairs)
fmt.Println(nums) // [1, 2, 3]
fmt.Println(letters) // ["a", "b", "c"]
// Use case: separating keys and values
keyValuePairs := []u.Tuple[string, int]{
{Left: "apple", Right: 5},
{Left: "banana", Right: 3},
{Left: "cherry", Right: 8},
}
items, counts := u.Unzip(keyValuePairs)
fmt.Println("Items:", items) // Items: [apple banana cherry]
fmt.Println("Counts:", counts) // Counts: [5 3 8]
// Empty slice
emptyNums, emptyStrs := u.Unzip([]u.Tuple[int, string]{})
fmt.Println(emptyNums, emptyStrs) // [] []
}
```

View file

@ -0,0 +1,12 @@
+++ title = "Underscore"
[data]
baseChartOn = 3
colors = ["#627c62", "#11819b", "#ef7f1a", "#4e1154"]
columnTitles = ["Section", "Status", "Author"]
+++
{{< column >}}
`underscore` is a `Go` library providing useful functional programming helpers without extending any built-in objects.
It is mostly a port from the `underscore.js` library based on generics brought by `go1.18`.

View file

@ -0,0 +1,32 @@
---
title: "Chaining"
date: 2021-12-31T13:11:41-04:00
---
## NewChain
Calling `NewChain` will cause all future method calls to return wrapped objects. When you've finished the computation,
call `Value` to retrieve the final value.
Methods not returning a collection such as `Reduce`, `Every`, `Some`, will break the chain and return `Value` instantly.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
sum := u.NewChain([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}).
// filter even numbers from the slice
Filter(func(n int) bool { return n%2 == 0 }).
// square every number in the slice
Map(func(n int) int { return n * n }).
// reduce to the sum
Reduce(func(n, acc int) int { return n + acc }, 0)
fmt.Println(sum) // 120
}
```

View file

@ -0,0 +1,231 @@
---
title: "Collections"
date: 2021-12-30T13:24:39-04:00
---
## Contains
`Contains` returns true if the value is present in the slice.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 3, 5, 7, 9}
fmt.Println(u.Contains(nums, 5)) // true
}
```
## Each
`Each` iterates over a slice of elements, yielding each in turn to an action function.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
names := []string{"Alice", "Bob", "Charles"}
res := make([]string, 0)
u.Each(names, func(n string) {
res = append(res, fmt.Sprintf("Hi %s", n))
})
fmt.Println(res) // {"Hi Alice", "Hi Bob", "Hi Charles"}
}
```
## Every
`Every` returns true if all the values in the slice pass the predicate truth test.\
Short-circuits and stops traversing the slice if a false element is found.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 3, 5, 7, 9}
isOdd := func(n int) bool { return n%2 != 0 }
fmt.Println(u.Every(nums, isOdd)) // true
}
```
## Filter
`Filter` looks through each value in the slice, returning a slice of all the values that pass a truth test (predicate).
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
isEven := func(n int) bool { return n%2 == 0 }
fmt.Println(u.Filter(nums, isEven)) // {0, 2, 4, 6, 8}
}
```
## Find
Find looks through each value in the slice, returning the first one that passes a truth test (predicate), or the default
value for the type and an error if no value passes the test. The function returns as soon as it finds an acceptable
element, and doesn't traverse the entire slice.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{2, 4, 5, 6, 8, 0}
isOdd := func(n int) bool { return n%2 != 0 }
n, err := u.Find(nums, isOdd)
fmt.Println(n) // 5
fmt.Println(err) // nil
}
```
## Map
`Map` produces a new slice of values by mapping each value in the slice through a transform function.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 3}
toSquare := func(n int) int {
return n * n
}
fmt.Println(u.Map(nums, toSquare)) // {1, 4, 9}
}
```
## Max
`Max` returns the maximum value in the slice. This function can currently only compare numbers reliably. This function
uses operator `<`.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 9, 2, 8, 3, 7, 4, 6, 5}
fmt.Println(u.Max(nums)) // 9
}
```
## Min
`Min` returns the minimum value in the slice. This function can currently only compare numbers reliably. This function
uses operator `<`.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 9, 2, 8, 3, 7, 4, 6, 5}
fmt.Println(u.Min(nums)) // 1
}
```
## Partition
`Partition` splits the slice into two slices: one whose elements all satisfy predicate and one whose elements all do not
satisfy predicate.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
isEven := func(n int) bool { return n%2 == 0 }
evens, odds := u.Partition(nums, isEven)
fmt.Println(evens) // {0, 2, 4, 6, 8}
fmt.Println(odds) // {1, 3, 5, 7, 9}
}
```
## Reduce
`Reduce` combine a list of values into a single value. `acc` is the initial state, and each successive step of it should
be returned by the reduction function.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
sum := func(n, acc int) int { return n + acc }
fmt.Println(u.Reduce(nums, sum, 0)) // 45
}
```
## Some
`Some` returns true if any of the values in the slice pass the predicate truth test. Short-circuits and stops traversing
the slice if a true element is found.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
nums := []int{1, 2, 4, 6, 8}
isEven := func(n int) bool { return n%2 == 0 }
fmt.Println(u.Some(nums, isEven)) // true
}
```

View file

@ -1,6 +0,0 @@
---
title: "Map Helpers"
date: 2025-09-01T00:00:00-00:00
---
Utilities for Go maps provided by the `maps` subpackage.

View file

@ -1,19 +0,0 @@
---
title: "Keys"
date: 2025-09-01T00:00:00-00:00
---
`maps.Keys` returns the keys of a map in unspecified order.
```go
package main
import (
"fmt"
m "github.com/rjNemo/underscore/maps"
)
func main() {
fmt.Println(m.Keys(map[int]string{1:"a",2:"b"})) // e.g., [2 1]
}
```

View file

@ -1,19 +0,0 @@
---
title: "Values"
date: 2025-09-01T00:00:00-00:00
---
`maps.Values` returns the values of a map in unspecified order.
```go
package main
import (
"fmt"
m "github.com/rjNemo/underscore/maps"
)
func main() {
fmt.Println(m.Values(map[int]string{1:"a",2:"b"})) // e.g., ["b" "a"]
}
```

View file

@ -1,32 +0,0 @@
---
title: "NewPipe"
date: 2021-12-31T13:11:41-04:00
---
Calling `NewPipe` will cause all future method calls to return wrapped objects.
When you've finished the computation, call `Value` to retrieve the final value.
Methods not returning a collection such as `Reduce`, `All`, `Any`, will break the
chain and return `Value` instantly.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
sum := u.NewPipe([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}).
// filter even numbers from the slice
Filter(func(n int) bool { return n%2 == 0 }).
// square every number in the slice
Map(func(n int) int { return n * n }).
// reduce to the sum
Reduce(func(n, acc int) int { return n + acc }, 0)
fmt.Println(sum) // 120
}
```

View file

@ -1,74 +0,0 @@
---
title: "Getting Started"
date: 2022-03-21T14:09:01-04:00
---
## Quick Start
Install the library using
```shell
go get github.com/rjNemo/underscore
```
Please check out the [examples](https://github.com/rjNemo/underscore/tree/main/examples)
to see how to use the library.
```go
package main
import (
"fmt"
u "github.com/rjNemo/underscore"
)
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
// filter even numbers from the slice
evens := u.Filter(numbers, func(n int) bool { return n%2 == 0 })
// square every number in the slice
squares := u.Map(evens, func(n int) int { return n * n })
// reduce to the sum
res := u.Reduce(squares, func(n, acc int) int { return n + acc }, 0)
fmt.Println(res) // 120
}
```
## Installation
These instructions will get you a copy of the project up and running on your local
machine for development and testing purposes.
### Prerequisites
You need at least `go1.18` for development. The project is shipped with
a [Dockerfile](https://github.com/rjNemo/underscore/tree/main/Dockerfile) based
on `go1.18`.
If you prefer local development, navigate to the [official
download page](https://go.dev/dl/) and install version `1.18` or beyond.
### Installing
First clone the repository
```shell
git clone https://github.com/rjNemo/underscore.git
```
Install dependencies
```shell
go mod download
```
And that's it.
## Tests
To run the unit tests, you can simply run:
```shell
make test
```

View file

@ -1,6 +0,0 @@
---
header:
- name: GitHub
ref: https://github.com/rjNemo/underscore
icon: gdoc_github
external: true

View file

@ -1,13 +0,0 @@
---
more:
- name: News
ref: "/#"
icon: "gdoc_notification"
- name: Releases
ref: "https://github.com/rjNemo/underscore/releases"
external: true
icon: "gdoc_download"
- name: "View Source"
ref: "https://github.com/rjNemo/underscore"
external: true
icon: "gdoc_github"

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"Target":"css/styles.35cccf4dc83860130d466a273c4bd3f1342dc22b167106c704a21ae7307d2d98f4701138b92f3fe17054f928a248863a30ad7863fe6b3816582123ff58f21da6.css","MediaType":"text/css","Data":{"Integrity":"sha512-NczPTcg4YBMNRmonPEvT8TQtwisWcQbHBKIa5zB9LZj0cBE4uS8/4XBU+SiiSIY6MK14Y/5rOBZYISP/WPIdpg=="}}

1
docs/themes/compose vendored Submodule

@ -0,0 +1 @@
Subproject commit 362af2409158972008f1dff8ee5e887d6ce88257

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Robert Kaussow <mail@thegeeklab.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,44 +0,0 @@
# Geekdoc
[![Build Status](https://img.shields.io/drone/build/thegeeklab/hugo-geekdoc?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/hugo-geekdoc)
[![Hugo Version](https://img.shields.io/badge/hugo-0.83-blue.svg)](https://gohugo.io)
[![GitHub release](https://img.shields.io/github/v/release/thegeeklab/hugo-geekdoc)](https://github.com/thegeeklab/hugo-geekdoc/releases/latest)
[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/hugo-geekdoc)](https://github.com/thegeeklab/hugo-geekdoc/graphs/contributors)
[![License: MIT](https://img.shields.io/github/license/thegeeklab/hugo-geekdoc)](https://github.com/thegeeklab/hugo-geekdoc/blob/main/LICENSE)
Geekdoc is a simple Hugo theme for documentations. It is intentionally designed as a fast and lean theme and may not fit the requirements of complex projects. If a more feature-complete theme is required there are a lot of good alternatives out there. You can find a demo and the full documentation at [https://geekdocs.de](https://geekdocs.de).
![Desktop and mobile preview](https://raw.githubusercontent.com/thegeeklab/hugo-geekdoc/main/images/readme.png)
## Build and release process
This theme is subject to a CI driven build and release process common for software development. During the release build, all necessary assets are automatically built by [webpack](https://webpack.js.org/) and bundled in a release tarball. You can download the latest release from the GitHub [release page](https://github.com/thegeeklab/hugo-geekdoc/releases).
Due to the fact that `webpack` and `npm scripts` are used as pre-processors, the theme cannot be used from the main branch by default. If you want to use the theme from a cloned branch instead of a release tarball you'll need to install `webpack` locally and run the build script once to create all required assets.
```Shell
# install required packages from package.json
npm install
# run the build script to build required assets
npm run build
```
See the [Getting Started Guide](https://geekdocs.de/usage/getting-started/) for details about the different setup options.
## Contributors
Special thanks goes to all [contributors](https://github.com/thegeeklab/hugo-geekdoc/graphs/contributors). If you would like to contribute,
please see the [instructions](https://github.com/thegeeklab/hugo-geekdoc/blob/main/CONTRIBUTING.md).
Geekdoc is inspired and partially based on the [hugo-book](https://github.com/alex-shpak/hugo-book) theme, thanks [Alex Shpak](https://github.com/alex-shpak/) for your work.
## License
This project is licensed under the MIT License - see the [LICENSE](https://github.com/thegeeklab/hugo-geekdoc/blob/main/LICENSE) file for details.
The used SVG icons and generated icon fonts are licensed under the license of the respective icon pack:
- Font Awesome: [CC BY 4.0 License](https://github.com/FortAwesome/Font-Awesome#license)
- IcoMoon Free Pack: [GPL/CC BY 4.0](https://icomoon.io/#icons-icomoon)
- Material Icons: [Apache License 2.0](https://github.com/google/material-design-icons/blob/main/LICENSE)

View file

@ -1 +0,0 @@
v0.27.5

View file

@ -1,7 +0,0 @@
---
title: "{{ .Name | humanize | title }}"
weight: 1
# geekdocFlatSection: false
# geekdocToc: 6
# geekdocHidden: false
---

View file

@ -1,4 +0,0 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
---

View file

@ -1,7 +0,0 @@
{{- $searchDataFile := printf "search/%s.data.json" .Language.Lang -}}
{{- $searchData := resources.Get "search/data.json" | resources.ExecuteAsTemplate $searchDataFile . | resources.Minify -}}
{
"dataFile": {{ $searchData.RelPermalink | jsonify }},
"indexConfig": {{ .Site.Params.GeekdocSearchConfig | jsonify }},
"showParent": {{ if .Site.Params.GeekdocSearchShowParent }}true{{ else }}false{{ end }}
}

View file

@ -1,12 +0,0 @@
[
{{ range $index, $page := (where .Site.Pages "Params.GeekdocProtected" "ne" true) }}
{{ if ne $index 0 }},{{ end }}
{
"id": {{ $index }},
"href": "{{ $page.RelPermalink }}",
"title": {{ (partial "utils/title" $page) | jsonify }},
"parent": {{ with $page.Parent }}{{ (partial "utils/title" .) | jsonify }}{{ else }}""{{ end }},
"content": {{ $page.Plain | jsonify }}
}
{{ end }}
]

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,498 +0,0 @@
{
"main.js": {
"src": "js/main-16c4e2c7.bundle.min.js",
"integrity": "sha512-bl5BOPf5WY/7pmXqUsQyvFycRJFfeZGgTzHV9XxKmZMxNxJgaqb7wy+G8iYQplX1UhLN3HS3dKKJiQHVnuxq9Q=="
},
"mermaid.js": {
"src": "js/mermaid-7da67bd0.bundle.min.js",
"integrity": "sha512-pZFKbl/sNohDgJ9NTRxpF9x065WAywo1wxK9SEgzi0FwIpHS/V3Uc/uko4PR3/q5LGKOcJSIeMKuR/FT1VCx/w=="
},
"katex.js": {
"src": "js/katex-0ad34d3f.bundle.min.js",
"integrity": "sha512-UGv62QBVmXuhby0d13uDRPPcnbT0SOI5uo0IsPUlW3+1/Q/v5K/IxyRD2XSDVa56y7WOWQokf3JVHdiXmLBHOg=="
},
"search.js": {
"src": "js/search-1c4cfb2d.bundle.min.js",
"integrity": "sha512-wK0vKlf8b3uje5GnRC4RN0aL7HzWDu7slqHlbfqCmCbHW30ede6D5H4rc/VqrpOx/Pf1ZfTV5L4Tx4pka1TwXQ=="
},
"js/273-3d271af5.chunk.min.js": {
"src": "js/273-3d271af5.chunk.min.js",
"integrity": "sha512-Pc+5FgoiesOdrtnJsehp3vIAqM8ePmAaRfYUsUnhh5Kf2wWxeKc+0MsqxB7yW7K42/BJBiM6xlKH/Gd6ftQrOQ=="
},
"js/116-d8286ca2.chunk.min.js": {
"src": "js/116-d8286ca2.chunk.min.js",
"integrity": "sha512-H5/JS5Ohg/a01zLV9GZ+AM6NgxzY5svTSCaF7VKp+cDlyiop2b6kgJ45tpBRJPQTuMILX2f65IxZRY9/ogY+BQ=="
},
"fonts/LiberationSans-Italic.woff": {
"src": "fonts/LiberationSans-Italic.woff",
"integrity": "sha512-3rg7qqlEgAeip3NcoxqNNKeVrPvkXCxHbybcidDz8/aKmNhtp9LG45K20dOaOxvWrB+XbjM6bBPnRuzJj8Pltw=="
},
"fonts/LiberationSans-BoldItalic.woff": {
"src": "fonts/LiberationSans-BoldItalic.woff",
"integrity": "sha512-l+QH9jdBUO/jvAiX27bbZvr5vCiPwBt1IJqfTy3545wRaqGOP2qeFNvolbaj7kIS7d0rc841Lgf7NACrcMFCmQ=="
},
"fonts/LiberationSans-Bold.woff": {
"src": "fonts/LiberationSans-Bold.woff",
"integrity": "sha512-dcvCYm+u+bCFKnERGNyS94DBqaNaaXr7TdD6cNXNvCwNV1jk7mOnRXub3rjX2hoIEcyMSBbeIny9nP5QCBij2g=="
},
"fonts/LiberationSans.woff": {
"src": "fonts/LiberationSans.woff",
"integrity": "sha512-X8iWtp7gsJFHLyWhQzM8IZMH97LUsxhB5Hzv9smPHsqmRrDhl/S5xClHq3lUEtupVjCxcthMXl2qQvXcM3XVkA=="
},
"favicon/apple-touch-startup-image-2732x2048.png": {
"src": "favicon/apple-touch-startup-image-2732x2048.png",
"integrity": "sha512-520ICJWMPSzTibQuHKpyYHgznwGlf3T95MTLoETOEpTzuxSYelNYNJhcYIhyv6I0r+PtLXNDfxMaIGAJewU2Dw=="
},
"favicon/apple-touch-startup-image-2048x2732.png": {
"src": "favicon/apple-touch-startup-image-2048x2732.png",
"integrity": "sha512-zSQs1F7Hz5qUzsyvq/kOycugg2k1t42QxIPAQIHXC87EwzjPPPewWinVDkqi+GIuGSa1xqzHv3srOCpKmgE0QA=="
},
"fonts/LiberationMono.woff": {
"src": "fonts/LiberationMono.woff",
"integrity": "sha512-fP8icFlpIzR+72w2iaQLQAImsyFi7T1hjZhT4102/kw2k0EJ8Q4iufSfjxhlKyeh7EAzF8OaEsOKeOmA7MfHVA=="
},
"favicon/apple-touch-startup-image-2224x1668.png": {
"src": "favicon/apple-touch-startup-image-2224x1668.png",
"integrity": "sha512-WXbw/s6qbt56l3Id4IFpLFhVx+otXWyFN/EwpJ5TU+hY5JGA8Ro3f+vOFYXP04Mwdubg5kEqzw/4HzD6uZRMVw=="
},
"favicon/apple-touch-startup-image-1668x2224.png": {
"src": "favicon/apple-touch-startup-image-1668x2224.png",
"integrity": "sha512-WCqMLLCyfHzTprEAoaPFaeHwJYEsusnb12rdoLXXbMbXA6v6KKUYDfziu2Z4HQ/34MpGSf7wvxfZss76rrHivg=="
},
"favicon/apple-touch-startup-image-2388x1668.png": {
"src": "favicon/apple-touch-startup-image-2388x1668.png",
"integrity": "sha512-xrkXrwGMnitt+kmv275t0MBE6S6+zxBZIYo87N3JdEVoq1HdG3PpO1gJZ4FaDWDWBH/1OF6/pYVmtF4QoTActQ=="
},
"favicon/apple-touch-startup-image-2160x1620.png": {
"src": "favicon/apple-touch-startup-image-2160x1620.png",
"integrity": "sha512-MMXrqQtIn50RcyBDPW/a9lTQ55/ad0cz6x5Ilv3Fv5JRZDgzGwlzJnD6YcNyvod2PCsKgoCAF+KqHa/odyJccQ=="
},
"favicon/apple-touch-startup-image-1668x2388.png": {
"src": "favicon/apple-touch-startup-image-1668x2388.png",
"integrity": "sha512-iZGmjl4HG8f2LKnAIsJpoplLTPbwbpydO+2V/3mI73rSY5haG5JhcdU5ZGzqeuGjSdm11MK5j7w4IZ9VRpyyhQ=="
},
"favicon/apple-touch-startup-image-1620x2160.png": {
"src": "favicon/apple-touch-startup-image-1620x2160.png",
"integrity": "sha512-D2Ad9ZPnEd1Ld5t6u5ljx3mdRy/VOyR11Qeee4rk1QVTThehfgx2slFfH86o36B6CBHaDZYMrWlMyXS6H8DI0A=="
},
"fonts/LiberationSans-Italic.woff2": {
"src": "fonts/LiberationSans-Italic.woff2",
"integrity": "sha512-boZm4ZsUNEmYS85TJvhuBiOUS18gpj0+9WbFgBpAQbCWdU5yde32bVS6rP0YvNvZMuS/R92y+e/bKbcgbMGDtg=="
},
"fonts/LiberationSans-BoldItalic.woff2": {
"src": "fonts/LiberationSans-BoldItalic.woff2",
"integrity": "sha512-5MVxBiZI9GlXK/F6eeZnwsLBYOMzoQ+ncAmSIoBa+kkrYnMfWaEHJaJO9tA6ml44ety3gt4e9tNmYZULvO86ug=="
},
"fonts/LiberationSans-Bold.woff2": {
"src": "fonts/LiberationSans-Bold.woff2",
"integrity": "sha512-msH61PCwMuCScUPTyVOjuQgZBhYICioAyJxifpioqircJqe1voESkLNzFz6NBmhewRZvfwJHKzwAne1cxg7mpQ=="
},
"favicon/apple-touch-startup-image-2048x1536.png": {
"src": "favicon/apple-touch-startup-image-2048x1536.png",
"integrity": "sha512-JL85dQr6+4HH6oukUWxPs1rbTKe2ZZE+t148UJBE6B4BGy8JYdtDZ8RMnks6vfzDNP7mk58GF1K6vEPZD/O/CQ=="
},
"fonts/LiberationSans.woff2": {
"src": "fonts/LiberationSans.woff2",
"integrity": "sha512-/se1p5pF9DbDIpOqEIdjqpr1J3v84dQAHPFdMsK1ZiojTlOWQJuqCH4jZ+oZh2K7TtOJa8lyY14RIHTvGh3+SQ=="
},
"favicon/apple-touch-startup-image-1536x2048.png": {
"src": "favicon/apple-touch-startup-image-1536x2048.png",
"integrity": "sha512-zd8Wn/2cJh9AFShTRz9iMIPIuCHnxOyabursGmH17EbbyEsL66xYP/F2FggCD6vc4/KOk73NQc8UFXLd6ZFx3A=="
},
"fonts/LiberationMono.woff2": {
"src": "fonts/LiberationMono.woff2",
"integrity": "sha512-p5oGo6T78XQ6SECsAez1Sc9HBw0SvLJlhndS+pJ0KyauzBdilh7/8/M/V8ivTjbJKU+rJHtIjHtMUVhPQjXq+g=="
},
"favicon/apple-touch-startup-image-2208x1242.png": {
"src": "favicon/apple-touch-startup-image-2208x1242.png",
"integrity": "sha512-Oei3vVNEzEvSajRyWJV0ZzFPwULEdbGklMa9s59PtFwCEfBU87HCzQfNxGEG7lze2TKftuLZqIqj0N15ZZ8JcA=="
},
"favicon/apple-touch-startup-image-1242x2208.png": {
"src": "favicon/apple-touch-startup-image-1242x2208.png",
"integrity": "sha512-rX1o6UJqKhO11hs6wQWOVNns6aafDqcVlPxuLx6TCCgIN4+evdu0M1X/7ZZalxaH4HHcDb3F72OjMw0JrzC8DA=="
},
"favicon/apple-touch-startup-image-2688x1242.png": {
"src": "favicon/apple-touch-startup-image-2688x1242.png",
"integrity": "sha512-M0IjR8gLlbqqql5/qjIhYyfm/poMOz70jRQIQyL1wYQBCquD4N7G3lm9Mqc3bJP0yvmBGgrflV9fg8sikxjWdw=="
},
"favicon/apple-touch-startup-image-1242x2688.png": {
"src": "favicon/apple-touch-startup-image-1242x2688.png",
"integrity": "sha512-xigXQLupadhWPZNsRFDri8f/Cm4J20shvo/wIM3P+qHIxAcj/kqgRfON/UHr2Lrn6eA11uEGrg8CkngM+F8zbw=="
},
"favicon/apple-touch-startup-image-2436x1125.png": {
"src": "favicon/apple-touch-startup-image-2436x1125.png",
"integrity": "sha512-Q6kqiD5/zHEEM/XYaSf+VjpBw++Jg3KSSqycIBiVFj2UGwArcxxesmzuCvfbtUmOoKAtso+Bjh38sXrEcrYD/A=="
},
"favicon/apple-touch-startup-image-1125x2436.png": {
"src": "favicon/apple-touch-startup-image-1125x2436.png",
"integrity": "sha512-uDKdJPnbrR3sjbZxVTnhUwWFgc02uUgG/Oj4G0sb0jJtcyVShsPBCefDdV1EalLkhZiVrHnSkiiM1hOIQ9aJjg=="
},
"favicon/apple-touch-startup-image-1792x828.png": {
"src": "favicon/apple-touch-startup-image-1792x828.png",
"integrity": "sha512-SclwE8AAOyR81/CdPU5XjiybQ9sQhmPht+Sz4/d7PR7gZhpoLEKBj0ovrWNV4xsiVKPhJCsdmkl17UIHftlWHg=="
},
"favicon/apple-touch-startup-image-828x1792.png": {
"src": "favicon/apple-touch-startup-image-828x1792.png",
"integrity": "sha512-UkZGpIoAUN4QBxO8q2qv/dsmyexeOmatgoz5W0sYrcwMyANSXdh/AzkUCATEur+2nDNa7FpycVZ+H7ox9teiww=="
},
"favicon/apple-touch-startup-image-750x1334.png": {
"src": "favicon/apple-touch-startup-image-750x1334.png",
"integrity": "sha512-fXVcGmV8nj/H4wZRG24ZgUOPO3qjMNMtMPGArIjsdtFC7MugmT2oZlmlZJTN1v6JIOoQPfDEmRBC2OY+83aOFg=="
},
"favicon/apple-touch-startup-image-1334x750.png": {
"src": "favicon/apple-touch-startup-image-1334x750.png",
"integrity": "sha512-8XSFf8v/KZW7sETjasY2xo7QOjF4rIAyKVlMg0ln3f6ltia/PgMmT2uyZtpfEmVjxhKzCE5sBprWWQMPgCnB5A=="
},
"favicon/apple-touch-startup-image-640x1136.png": {
"src": "favicon/apple-touch-startup-image-640x1136.png",
"integrity": "sha512-XXoL6TF7XiLsGSozR/i/rHSLxq4+EYSuJy1yVXkuYD1Z5pKLE79mEixZtIAlFAUH4vp5/jDnqUeLZEF0WKj3Fg=="
},
"favicon/apple-touch-icon-1024x1024.png": {
"src": "favicon/apple-touch-icon-1024x1024.png",
"integrity": "sha512-24xfiS1TIVCTRTPPBBFqdDquj1YjC5Uv4/27/X6rXavl3EFm8jvyKHJoNNBZnADuPDnNUp3fZ3w8YjFjh/72eg=="
},
"favicon/apple-touch-startup-image-1136x640.png": {
"src": "favicon/apple-touch-startup-image-1136x640.png",
"integrity": "sha512-sCGiDX6KSnVLTN3SxgxU3idna/C4kSpxEg1e0LDd5Va9GKGU9Pwxsxbfztovdoa4dCzUQor7bNkP9AV31ZUhHw=="
},
"main.scss": {
"src": "main-daf4fc22.min.css",
"integrity": "sha512-ICTDnZuVeCwwq6BHUYs+d23U+hlJunNMSSmaXKpEe3q61n95owNKjclyNCtq0f4xafM9st+bT7XL+xZ/bt/Wkw=="
},
"favicon/android-chrome-512x512.png": {
"src": "favicon/android-chrome-512x512.png",
"integrity": "sha512-4LwQNKmVInikOHD2/rQlGO+YsQ20ty8OPlvY1ZkCTW6z79PzYu7sxBKChoRWZz29Qu+5pswP4gcnlFJM8h16Ig=="
},
"fonts/KaTeX_AMS-Regular.woff": {
"src": "fonts/KaTeX_AMS-Regular.woff",
"integrity": "sha512-9OTmXDiUyTZC10JExwbsf9iq5LBx+9l7D9C4/6i+l0df+q4VmoRuBkqtOGsQJq6Ak3lnikurNrXbfpooemNRWw=="
},
"favicon/favicon.ico": {
"src": "favicon/favicon.ico",
"integrity": "sha512-eiPeWA9BpWCHB8RTkHgjSniPpdfHwX28K4PwZRbsFvw/iSg643dh0kzSxoP9PM7TP7HOTtsTjhjkivaLucn8fg=="
},
"fonts/KaTeX_Main-Regular.woff": {
"src": "fonts/KaTeX_Main-Regular.woff",
"integrity": "sha512-e/R6E/kxpe/ZJOoelEE/Up1luI+TGgnZk7sqD5WEgZni3mT7SuJIXeg+Tds6aQVW3EU5OdrzkQgy7SKvgmlwNw=="
},
"favicon/android-chrome-384x384.png": {
"src": "favicon/android-chrome-384x384.png",
"integrity": "sha512-z6jq3E8UfsKnmAvAqe3f/6zU3G4J64Si2leW1zd+aOXeEmOit/TLX+95PP+nt8RccwNZLSdcvxSSbnO3QOvgiA=="
},
"fonts/KaTeX_Main-Bold.woff": {
"src": "fonts/KaTeX_Main-Bold.woff",
"integrity": "sha512-+UGGXn4fqiTI5j9vbHK7pGg1ArRyP1KjM10tTyRjdEXUXl8VYS8VFuKcZm7TYzSAUbKCyMROpMarzCPNdhwUYA=="
},
"favicon/firefox_app_512x512.png": {
"src": "favicon/firefox_app_512x512.png",
"integrity": "sha512-t7wJcQ8LAHmPf6wFzun/zUzG9Ul3VDyzIibk26esgRzt9YAxXQ4QURKjKbdSX+Chozn+hHgbGdwGHUhZCDeIkw=="
},
"fonts/KaTeX_AMS-Regular.woff2": {
"src": "fonts/KaTeX_AMS-Regular.woff2",
"integrity": "sha512-gAE8LJexY6Fb4a8zluSx/+2E4uy09m2cU4S2aUbdJVMhYine4XXka/ehaMYIPso+KvEjy22Nu9LicCgefbF/gg=="
},
"fonts/KaTeX_Main-Regular.woff2": {
"src": "fonts/KaTeX_Main-Regular.woff2",
"integrity": "sha512-G/qfHSw59EYNIAQD8uKjJ9K5ZLpOANYUlemDOCbMgEFEt1NoYHBPdBaUk12AWMo1BYb5fMsxnlfRRyMQD0iGIA=="
},
"fonts/KaTeX_Main-Bold.woff2": {
"src": "fonts/KaTeX_Main-Bold.woff2",
"integrity": "sha512-H+N2wqGFzd+GKbPWL2b/P2EMC2x9xHwWWkv2qsb56vSMQZA+sxRpebHebFddNq3kSXdlE7bhofyupEO+H7oPlg=="
},
"favicon/mstile-310x310.png": {
"src": "favicon/mstile-310x310.png",
"integrity": "sha512-QMpRgeWeAmOnY+5kV7ko2T90q5Ssf/BdkDlils8RA/os/00+s85+LqCv75LA6x0mURQBRslXAYTvlExXQc8nnQ=="
},
"katex.css": {
"src": "katex-e264b2b5.min.css",
"integrity": "sha512-0PbhgeD1jHmOFaX8COKUmrYjQ9kzoDbEi4fLK2AlpNfWy80qeQqrxV77SDoqkSisg58hon2BOe8NS/uNPA9qFQ=="
},
"fonts/KaTeX_Main-Italic.woff": {
"src": "fonts/KaTeX_Main-Italic.woff",
"integrity": "sha512-OjmWMTSIDlTf1ZGhOQiZsZAQ1cySo4EizhqshTvbuqZkgbUtV0291hC/QN+o4+VFc7OBxaKWYaJgjFRszpQnuA=="
},
"fonts/KaTeX_Main-BoldItalic.woff": {
"src": "fonts/KaTeX_Main-BoldItalic.woff",
"integrity": "sha512-AXQBZ7CFEPmUhTEmD290away1CMsXG+6B1M2c0kKewQFg5ynwIe/FryXq4dNvcTh6Cjt4PqMMss8oi95k0itSw=="
},
"fonts/KaTeX_Math-Italic.woff": {
"src": "fonts/KaTeX_Math-Italic.woff",
"integrity": "sha512-TUy17s9hPgsK0he3aJxEtpu4tdrXIgAwSR0wJnkr4b0BNKSEAap1orh7MA2QgT/KOV5ob6ZOWO7HqbwwQ9GVcg=="
},
"fonts/KaTeX_Math-BoldItalic.woff": {
"src": "fonts/KaTeX_Math-BoldItalic.woff",
"integrity": "sha512-vOUuWrtWrswqo6byaXpdKXUyJVAQjZdovxjXMqt2d6077hOXP4buD9+CEGzgiJdFOLXgVyt663Qg24V6tq7q0g=="
},
"favicon/android-chrome-256x256.png": {
"src": "favicon/android-chrome-256x256.png",
"integrity": "sha512-hrtqFFkYWcGSiynPdzkSpODgNSTLfpzDvmYou56Qzi8t3HhZ3jyMDE9g0JcWJ1I6SbaWSfZdkAHXDj2v56x1Og=="
},
"fonts/KaTeX_Main-Italic.woff2": {
"src": "fonts/KaTeX_Main-Italic.woff2",
"integrity": "sha512-SNdgxBdi/h31Ew67zOq7HpN4HMSa4vcCObb4qEywoA1tsMO8V+FQjZMrzVggok/ZIOK54mYOZBwNHPxiJLwhlw=="
},
"fonts/KaTeX_Main-BoldItalic.woff2": {
"src": "fonts/KaTeX_Main-BoldItalic.woff2",
"integrity": "sha512-R8cMx8fydyMLYJCaCwtZOVO5dDNFXr7+HQ4k3anhEEwt1D/apo8SHVx6P9z+0b1WiCB3HayQkQmWWgW39rKstQ=="
},
"fonts/KaTeX_Math-Italic.woff2": {
"src": "fonts/KaTeX_Math-Italic.woff2",
"integrity": "sha512-LRyb4qX7MDVXzDJUxajFm8w3ycI/0r+z6F4qY8c/YbZUYFx246I2HiNeQfpsTCa6nsCtrF4Uah6G0rzJhZR+uA=="
},
"fonts/KaTeX_Math-BoldItalic.woff2": {
"src": "fonts/KaTeX_Math-BoldItalic.woff2",
"integrity": "sha512-sR3fQuYKVLlf6OGCMP7Fk/S8itLYLDZY/yN9YADDDPEbY0Ii3XaOAR0ytOTUgL8nehwjBdaCw+iMXMwT0rtKqQ=="
},
"fonts/Metropolis.woff": {
"src": "fonts/Metropolis.woff",
"integrity": "sha512-fqZj5Y6hMExrGIb+OuLPY4hnQ+/ILiPON6MpAc77iKhzTWNn7KvSdfwS2NY5hmAYGfggxl55cYRUgT6F/W/RjQ=="
},
"fonts/KaTeX_Typewriter-Regular.woff": {
"src": "fonts/KaTeX_Typewriter-Regular.woff",
"integrity": "sha512-B1qvhsqVeoK6poFsOdVkSoAG3RcVLila6oTE8GeRxtrRsGTF2eWKwdY3C6Td1Cijbh6UvjkunBI/2pWW6mCaXg=="
},
"fonts/KaTeX_SansSerif-Bold.woff": {
"src": "fonts/KaTeX_SansSerif-Bold.woff",
"integrity": "sha512-x8XDvU1FWtV3VSVxwu9zpioBXeiCPU5ePRJintoxp1HuPg3LBF0XC0X9fOnObO6VHEtQtKwzKfevNLhrQWzi9w=="
},
"fonts/KaTeX_SansSerif-Italic.woff": {
"src": "fonts/KaTeX_SansSerif-Italic.woff",
"integrity": "sha512-WNH/1HTzqy+zZJp9UuT4yMSK/ynD0f2Wd4aY3w0mKUezWJtXn9uUaa3tAdw5rE7finAD2STy24CzwVku+lc5xg=="
},
"fonts/KaTeX_Typewriter-Regular.woff2": {
"src": "fonts/KaTeX_Typewriter-Regular.woff2",
"integrity": "sha512-S0dhh+bWsw9RVuG+u7wuf+MKUhB6FozZMooTc172VAUU+g1jjXAki8d+/7ecphrmo2/4uumB2bbMdWbvU5u/oA=="
},
"fonts/KaTeX_Fraktur-Bold.woff": {
"src": "fonts/KaTeX_Fraktur-Bold.woff",
"integrity": "sha512-bbDj1QAzneCTF9//oni1rIQ3wKjtMX2kGbSrYaVNJOCOPOHWD+Mn8ZCACQAKzsVnWX9IB8X1Uwazyjz95kmPAg=="
},
"fonts/KaTeX_Fraktur-Regular.woff": {
"src": "fonts/KaTeX_Fraktur-Regular.woff",
"integrity": "sha512-bphDtaXYbimBkkS8AIyw0aBbDWoMVkeEIwAnz+Ra1LrcrZxPCaqiV615P3g8zRvWCUifq1fNBAqvHYLqkhd1TA=="
},
"favicon/android-chrome-192x192.png": {
"src": "favicon/android-chrome-192x192.png",
"integrity": "sha512-Tb4H9uC/7OYYBQxRJMO1SIDOvlBS6jHMENnsoKROAUCCjbsptwwMTR0xisgwkJdDkgEH88s9yLj/sy55OJqwtA=="
},
"fonts/KaTeX_SansSerif-Regular.woff": {
"src": "fonts/KaTeX_SansSerif-Regular.woff",
"integrity": "sha512-O4mHHzWemAibL1YVBTG0lPZWRdcNQ/Qbn2/SvQ5gz9CREHr3pWQgrLI7VuqScudj74azd1v0S4YUiNuoFjwfoA=="
},
"fonts/KaTeX_SansSerif-Bold.woff2": {
"src": "fonts/KaTeX_SansSerif-Bold.woff2",
"integrity": "sha512-VWDeiG3/j21h8nr6IlK3IcD9ST9gTGHTAaDC0hFMIqCqWztrzO6H7bVJ2GWOlp9seqrFCQvkrcoEKULdYBxSEg=="
},
"fonts/KaTeX_SansSerif-Italic.woff2": {
"src": "fonts/KaTeX_SansSerif-Italic.woff2",
"integrity": "sha512-hZ3bBm8cZVRGJ8lu3C9HAvGbBBWc3a5gK7PZj5BcUPmP0zHxCnUQwAjw5M90j26Nu49DmGHd6BpUC3tGRdgSVw=="
},
"favicon/apple-touch-icon-180x180.png": {
"src": "favicon/apple-touch-icon-180x180.png",
"integrity": "sha512-G/pMzRUISFGsqqFUi+3GgVi2TXN2PmPbpCiYXo9YSi+Rn1dtPmeGDY5GAz4rRzf6kIAlXThmSKTS/rpbKgObuA=="
},
"favicon/apple-touch-icon-precomposed.png": {
"src": "favicon/apple-touch-icon-precomposed.png",
"integrity": "sha512-G/pMzRUISFGsqqFUi+3GgVi2TXN2PmPbpCiYXo9YSi+Rn1dtPmeGDY5GAz4rRzf6kIAlXThmSKTS/rpbKgObuA=="
},
"favicon/apple-touch-icon.png": {
"src": "favicon/apple-touch-icon.png",
"integrity": "sha512-G/pMzRUISFGsqqFUi+3GgVi2TXN2PmPbpCiYXo9YSi+Rn1dtPmeGDY5GAz4rRzf6kIAlXThmSKTS/rpbKgObuA=="
},
"fonts/KaTeX_Fraktur-Bold.woff2": {
"src": "fonts/KaTeX_Fraktur-Bold.woff2",
"integrity": "sha512-y7piX+9FWnsdHdn+ZeIbANy0v3KeCMwb0Ygq8IKnZq9tCtyFyEk9NSmqSc+thy0MSyQUhHYhdPCkjSHijHbJgA=="
},
"fonts/KaTeX_Fraktur-Regular.woff2": {
"src": "fonts/KaTeX_Fraktur-Regular.woff2",
"integrity": "sha512-scyZ311eKDPdtO9dnq+j8r21ahTaiygTrpRE14e7YxUs/7tjMv61mCA+cpLAz0+lzrDaZoyMuCr8yTvqgsSX5Q=="
},
"fonts/Metropolis.woff2": {
"src": "fonts/Metropolis.woff2",
"integrity": "sha512-oS5Y/tXC8/vG4f7KiHpDicy0yE4zs1TMps9Mzfk3M8O7/QNeC9Q7ZcsjnncuGo0vQB5RXU8g460XpSUB5Luc4Q=="
},
"favicon/firefox_app_128x128.png": {
"src": "favicon/firefox_app_128x128.png",
"integrity": "sha512-NV/H3Ya562iH3lsTWu3+nE1RZ+wxKKYEMxkYPCH4JLqaesUQfefWavIN0PUzB4TQ0ONmmp4fwkAtXmMh4hplHQ=="
},
"fonts/KaTeX_Script-Regular.woff": {
"src": "fonts/KaTeX_Script-Regular.woff",
"integrity": "sha512-GnZ6z38QaaRNmfOLaikoe5x0HgxQ0qhfi5eGO6QzdHTXdGiTWxV+RjYPrvoW/vc9Bk/BdVH9vBcIODhXtwsYZg=="
},
"fonts/KaTeX_SansSerif-Regular.woff2": {
"src": "fonts/KaTeX_SansSerif-Regular.woff2",
"integrity": "sha512-E9Kz6Ra6gXiS2dEGdOw6t9bDwwqYaLGplO0sYwtsh9aveV9xu/j0kwSkr0VZJ+otqrSPzox6sJKTvzlVjQtQsQ=="
},
"favicon/apple-touch-icon-167x167.png": {
"src": "favicon/apple-touch-icon-167x167.png",
"integrity": "sha512-03qCnveVmQRddor+JMS5JGMGqdkcbCc+rUuXqQGhB34lanb92p2Ipigqa1FINeyYc64DJRrQkzRkWorEqPom6A=="
},
"fonts/KaTeX_Script-Regular.woff2": {
"src": "fonts/KaTeX_Script-Regular.woff2",
"integrity": "sha512-/jhfsi53uEpLeJpQXaN1nzNSIuRztZkiF6tZ16/KKJ631DD61mQBX80CEnFaQ6+t7t2cuqTUH2xR/WoY8xvvOw=="
},
"favicon/apple-touch-icon-152x152.png": {
"src": "favicon/apple-touch-icon-152x152.png",
"integrity": "sha512-AZqdsbtWe2Kccqa1Q8gE/dUCTGo2ZlkdG0rGiamZ3XdynYBL5GnEguQDJjiMWnkblEmlQ8CWE5pxoOQ1TVnQqA=="
},
"favicon/mstile-150x150.png": {
"src": "favicon/mstile-150x150.png",
"integrity": "sha512-JJCSnHo3cpid9GAXaJz3/PMXcjlWzVYgmKlUwTC7+NJ4vUXdQ6bjHtomGhZMcHOHKy6bT6bwxBip3ngVoAlzNw=="
},
"favicon/apple-touch-icon-144x144.png": {
"src": "favicon/apple-touch-icon-144x144.png",
"integrity": "sha512-sxApsYMBq0EyzbVYkxKtKTau+noTtKH65s9UEm5LVbeFjMlR5XDTxsEbYNesz/p/DHEg/oeNXAOG1QvCdV+8yw=="
},
"favicon/android-chrome-144x144.png": {
"src": "favicon/android-chrome-144x144.png",
"integrity": "sha512-GHmf/LdyneSuxyqoiRP4en4ZDfyU/vJOd5mLK1cW95Hk5Pi+3rvk/R3Cqtkdd4E6tyIwZGHNh+WHL+D6eoOxiA=="
},
"favicon/mstile-144x144.png": {
"src": "favicon/mstile-144x144.png",
"integrity": "sha512-GHmf/LdyneSuxyqoiRP4en4ZDfyU/vJOd5mLK1cW95Hk5Pi+3rvk/R3Cqtkdd4E6tyIwZGHNh+WHL+D6eoOxiA=="
},
"fonts/KaTeX_Caligraphic-Bold.woff": {
"src": "fonts/KaTeX_Caligraphic-Bold.woff",
"integrity": "sha512-dfUme9QtfyLoXukghFSnTuTlLsQ/8O6YC0QnfbcJermS4BcnS4yWkZT1MEkDPL7/OUnVHcBSuLlvPbQQviJLGw=="
},
"fonts/KaTeX_Caligraphic-Regular.woff": {
"src": "fonts/KaTeX_Caligraphic-Regular.woff",
"integrity": "sha512-31Lt1ryrvtQyFxwrABw2xZiojGGAxgDf1ojQFVy11mzmlJfn38QVSwxISnudZVBrslsHyahoDuZK6wBrayJ/LA=="
},
"favicon/apple-touch-icon-120x120.png": {
"src": "favicon/apple-touch-icon-120x120.png",
"integrity": "sha512-SxZdzj6QHtW/aA1qzqxFt+7ukVK1bzTsileyHR8xhVyk7U10DhLH+lkX7/04jctPFQ16p1/aW1XYfus0Az7mxw=="
},
"favicon/mstile-310x150.png": {
"src": "favicon/mstile-310x150.png",
"integrity": "sha512-iby/HgTBJo85KRrZdnhz7cb7ilVeD5sFCeKcCoTf/HAcVqJACSWyRi8zjYVp8QrtjCL2yi9yZ0sUsKfzsKIJdQ=="
},
"fonts/KaTeX_Caligraphic-Bold.woff2": {
"src": "fonts/KaTeX_Caligraphic-Bold.woff2",
"integrity": "sha512-Ljf53JaOUtVkASUsf8k9tpv6UeNL81MK9LR5Zco6qeIZ7l7oL3heh4SgT5mljccYcPeQ3+qB0JG8GLOiyXcNAg=="
},
"fonts/KaTeX_Caligraphic-Regular.woff2": {
"src": "fonts/KaTeX_Caligraphic-Regular.woff2",
"integrity": "sha512-lxXHdk259ffCje8TkPwi9v0jtJzlnd2O4FKYgzmdPOGHFdEzdM+7FiAumS0ClHPuqU90I2hTSmpDcpmIT/P8yQ=="
},
"favicon/apple-touch-icon-114x114.png": {
"src": "favicon/apple-touch-icon-114x114.png",
"integrity": "sha512-LGiIXYDx+ERXCQDJCktzcb03hmvkGZdRBh2X9SX9esa/A9GXQyWIwN4+6KxKD+Wtvy4YxzaJK98HZs3538CDGg=="
},
"fonts/KaTeX_Size1-Regular.woff": {
"src": "fonts/KaTeX_Size1-Regular.woff",
"integrity": "sha512-XkOjZvm8Ok63zQ6QfZMMFYV0/WG59qxy8+n8Iu6Vqzbo9S+HhvsUpoQDEKLOS/BlLmvWQxjKoDJRdwuht5XP7A=="
},
"fonts/KaTeX_Size2-Regular.woff": {
"src": "fonts/KaTeX_Size2-Regular.woff",
"integrity": "sha512-8lcSsq+0OUHQ1txGOO+hbXlRT9HjF0XmcVXU1trgKtDJx+fq2ejI00wJX/Jd+qw7e70BsOIuhvhY/9Og5wGxiw=="
},
"fonts/KaTeX_Size4-Regular.woff": {
"src": "fonts/KaTeX_Size4-Regular.woff",
"integrity": "sha512-Yn3rCn0/wh7IJxqTkxIMQmE2R1WuooBx1NXlsrmmodUYljKoXDiY5V/ENfqav4ZaVoHfuahfqQyvwQ8GyMgtLQ=="
},
"favicon/android-chrome-96x96.png": {
"src": "favicon/android-chrome-96x96.png",
"integrity": "sha512-Yf4VS4jjOTKPur035TkNhAybS3p2x+jrNli0lyHZJRbfNy2Csi0a7ilwhsVCUl4LFp/JxwFmXFxprI7WwW8o4Q=="
},
"fonts/KaTeX_Size1-Regular.woff2": {
"src": "fonts/KaTeX_Size1-Regular.woff2",
"integrity": "sha512-LWpGCcgzMsmdUrmsBIN5OaYPFpbpYpEn+YmrnLfiCbdq8s7aNTtNqdyUxmExvdZ2Vo2Bz87pfzjvN+xtL4+JTg=="
},
"fonts/KaTeX_Size2-Regular.woff2": {
"src": "fonts/KaTeX_Size2-Regular.woff2",
"integrity": "sha512-BHXH2ZEkl2rojultow0zLPFK6z4CAw4f7zYtMCTNhaXYnmELwHufinI8V3Mc45cLgD+IPwsA9hoinugMQimuwQ=="
},
"fonts/KaTeX_Size4-Regular.woff2": {
"src": "fonts/KaTeX_Size4-Regular.woff2",
"integrity": "sha512-F6rlkx62QlxNW+vGsxhQWgaktNw+Gzb6fmNF9fEzx5O/FWE+6L7711Q5jKjzMQVeegEdiSeSqvncYFE3j81Dzw=="
},
"fonts/GeekdocIcons.woff": {
"src": "fonts/GeekdocIcons.woff",
"integrity": "sha512-gLi2npju9RoGz3vjD8f/1/BcETm7HAWEjaI+2zeNnpEfq+FPOZ1FU7GadIiCYL3kHkS13ioojftzI8TZmGlS8A=="
},
"favicon/firefox_app_60x60.png": {
"src": "favicon/firefox_app_60x60.png",
"integrity": "sha512-YrL6darERXRvnGbTP7MWZFaIqiXEurdbwsg6HeWl+0wlZPC6Wy9DHUcTfjtixv+1+DAVal8YLoIqXTAyYcN+lw=="
},
"fonts/KaTeX_Size3-Regular.woff": {
"src": "fonts/KaTeX_Size3-Regular.woff",
"integrity": "sha512-Eo35y+ZReFpmRgZv0N2dKAc5ggcXGE3wnrCf28qnu5G1+Ikvew0IFkdCYN1I6FnHsPDTnQzjJZU7+F7oBYvHKQ=="
},
"favicon/android-chrome-72x72.png": {
"src": "favicon/android-chrome-72x72.png",
"integrity": "sha512-PFMzr2iXImbdQDiZVNugaYzoQAUvLCsNrnm0pV0zLxIuczytBfu1nGKjfJZwyOfMzKQpQfYi4z0cdgit9bJ5IQ=="
},
"favicon/apple-touch-icon-76x76.png": {
"src": "favicon/apple-touch-icon-76x76.png",
"integrity": "sha512-klHJxEbcTgx7V1TBM/gByA6vzXK5MUkaiQ1gTybEp6g0sMzpIdO9XTIrsDeelKGI1aHtSBEWPLqF1rYp5T1Oiw=="
},
"favicon/apple-touch-icon-72x72.png": {
"src": "favicon/apple-touch-icon-72x72.png",
"integrity": "sha512-UHXFta5GyLLQ5IoVQBJGP4yzOVwkRG6m6YTrhw/ABwjgsKX4P3u6h2VnbKpGyRgf5hdORwUFyYrJBgQpIzpNWQ=="
},
"favicon/mstile-70x70.png": {
"src": "favicon/mstile-70x70.png",
"integrity": "sha512-Wj4a4NiSy1axSDENK/8G13PQPQuaEaFPxOBCj+iVmZ0ifk2UMPbtZDo2fdpCTtwS7BWiHCyv97Kvg4sqAZbRjA=="
},
"fonts/GeekdocIcons.woff2": {
"src": "fonts/GeekdocIcons.woff2",
"integrity": "sha512-Hqii30nEsUIwZYZWg+hjz5sLoPU+EJuwmf//aQo7IOVHDiGEST3RwEt8OBNgKQr9mUv4vE5uhhtcOqANKvqEcQ=="
},
"fonts/KaTeX_Size3-Regular.woff2": {
"src": "fonts/KaTeX_Size3-Regular.woff2",
"integrity": "sha512-avq+5YU1Cd9KtJ0U6hujFkh4fMNVZC59Y/HPlqUXkxeUlWca4Fmqn1YA8WK1188GG59qRHBtoZzvRgC9k9fGZA=="
},
"favicon/favicon-48x48.png": {
"src": "favicon/favicon-48x48.png",
"integrity": "sha512-rs5vrXU7NuGuRCv1RimyERFr1DzyQBviAoYmJxD8uHjl0y57SAleg14A0piuXkRUDL1FE0ZyVA/INPz+8GQjpg=="
},
"favicon/apple-touch-icon-60x60.png": {
"src": "favicon/apple-touch-icon-60x60.png",
"integrity": "sha512-YTKtyy5p2t+jz9cFS1c9kFm0LMH95s37ZRuL5AS0Lhpkf8B+xSokR/v+3xxYskT3QRSx5vcyHai8ia44X6xT5Q=="
},
"favicon/apple-touch-icon-57x57.png": {
"src": "favicon/apple-touch-icon-57x57.png",
"integrity": "sha512-2yW78pw4eDZ7hEUoWvNaEEeXOf30rUaKXoZwSvrilX8xBvqij/opEMPF0OHHU2lQziDIGGN9YsUnASQbhd95bw=="
},
"favicon/android-chrome-48x48.png": {
"src": "favicon/android-chrome-48x48.png",
"integrity": "sha512-UTXsN/aHnuTWAyYmp+/Ov0H1ML3HnIfUvYuwPyeWTwRs/8bZETHUYsj4scx48YkAJ+fhRnobXYqwr0swY7cXeQ=="
},
"favicon/favicon-32x32.png": {
"src": "favicon/favicon-32x32.png",
"integrity": "sha512-cT9VQkceXZYw+3yDSljXGTmfHdp6Gh+ncc7mdtKoB3AK4a6MgMR1YTeGpC0IOm5EmMQoPICXwvMPWskD9YSUAA=="
},
"favicon/android-chrome-36x36.png": {
"src": "favicon/android-chrome-36x36.png",
"integrity": "sha512-uIOaCXbCeY2tIMUPros0wfBdDIh842crzDQ/4NjTeEqzFhwrK3HlTsdFXYNcqg9OOuO+aATZKwIGYbgaA8vQbA=="
},
"favicon/manifest.json": {
"src": "favicon/manifest.json",
"integrity": "sha512-UncSB3MQSXZlaaxiclpQvvZDDYew4CITJ7JTlLcb8kZpyB3YgbqdHBIechH3HIQ1uJsYhgQyItxG3VMxOLfzKw=="
},
"mobile.scss": {
"src": "mobile-1d04b92f.min.css",
"integrity": "sha512-KvfM8WyFGmpvx5di6Mf6XMOxOF3PWH9H7qmrD/SSt7TT971j2vUyppZabtUcCCXme6MUDBRQApwIwCRTpTBbOQ=="
},
"favicon/favicon-16x16.png": {
"src": "favicon/favicon-16x16.png",
"integrity": "sha512-lQ+H0RYy3ZlksL5zUaV2WcH2PQdG6imd5hr1KfQOK4o1LXm7JAHvyjOSN3E+HC+AN1pCuoaITo6UI3SpW+CHNA=="
},
"print.scss": {
"src": "print-19966b38.min.css",
"integrity": "sha512-xpNQeJp9e4SbqEv+pFoGrOehV+RABxosG+toy6+HJ6SGFLxJNgG4+/RwPYdg3BxBvRXfkTmwf+iArAT3/a3+3g=="
},
"favicon/browserconfig.xml": {
"src": "favicon/browserconfig.xml",
"integrity": "sha512-RDr7E4dJmkJdQMyNa4dtxx3iYnrSnFHlifwV1LriUChccTz+aB0gNe0CRL94GXHGd1DiUU+QgEXXNC2Mupn9aw=="
},
"favicon/manifest.webapp": {
"src": "favicon/manifest.webapp",
"integrity": "sha512-XPr/eyO6YOVNkn3FS0wAMxe2FPIQmzn5YvV0E2kJ+feOQG4pzZVL09aa35gSUjLR18BVenKZTTtJy4Yh+reRTQ=="
},
"custom.css": {
"src": "custom.css",
"integrity": "sha512-1kALo+zc1L2u1rvyxPIew+ZDPWhnIA1Ei2rib3eHHbskQW+EMxfI9Ayyva4aV+YRrHvH0zFxvPSFIuZ3mfsbRA=="
}
}

View file

@ -1,45 +0,0 @@
---
edit_page: Seite bearbeiten
nav_navigation: Navigation
nav_tags: Tags
nav_more: Weitere
nav_top: Nach oben
form_placeholder_search: Suchen
error_page_title: Verlaufen? Keine Sorge
error_message_title: Verlaufen?
error_message_code: Fehler 404
error_message_text: >
Wir können die Seite nach der Du gesucht hast leider nicht finden. Keine Sorge,
wir bringen Dich zurück zur <a class="gdoc-error__link" href="{{ . }}">Startseite</a>.
button_toggle_dark: Wechsel zwischen Dunkel/Hell/Auto Modus
button_nav_open: Navigation öffnen
button_nav_close: Navigation schließen
button_menu_open: Menüband öffnen
button_menu_close: Menüband schließen
button_homepage: Zurück zur Startseite
title_anchor_prefix: "Link zu:"
posts_read_more: Ganzen Artikel lesen
posts_read_time:
one: "Eine Minute Lesedauer"
other: "{{ . }} Minuten Lesedauer"
posts_update_prefix: Aktualisiert am
posts_count:
one: "Ein Artikel"
other: "{{ . }} Artikel"
posts_tagged_with: Alle Artikel mit dem Tag '{{ . }}'
footer_build_with: >
Entwickelt mit <a href="https://gohugo.io/" class="gdoc-footer__link">Hugo</a> und
<svg class="icon gdoc_heart"><use xlink:href="#gdoc_heart"></use></svg>
footer_legal_notice: Impressum
footer_privacy_policy: Datenschutzerklärung
footer_content_license_prefix: >
Inhalt lizensiert unter
language_switch_no_tranlation_prefix: "Seite nicht übersetzt:"

View file

@ -1,45 +0,0 @@
---
edit_page: Edit page
nav_navigation: Navigation
nav_tags: Tags
nav_more: More
nav_top: Back to top
form_placeholder_search: Search
error_page_title: Lost? Don't worry
error_message_title: Lost?
error_message_code: Error 404
error_message_text: >
Seems like what you are looking for can't be found. Don't worry, we can
bring you back to the <a class="gdoc-error__link" href="{{ . }}">homepage</a>.
button_toggle_dark: Toggle Dark/Light/Auto mode
button_nav_open: Open Navigation
button_nav_close: Close Navigation
button_menu_open: Open Menu Bar
button_menu_close: Close Menu Bar
button_homepage: Back to homepage
title_anchor_prefix: "Anchor to:"
posts_read_more: Read full post
posts_read_time:
one: "One minute to read"
other: "{{ . }} minutes to read"
posts_update_prefix: Updated on
posts_count:
one: "One post"
other: "{{ . }} posts"
posts_tagged_with: All posts tagged with '{{ . }}'
footer_build_with: >
Built with <a href="https://gohugo.io/" class="gdoc-footer__link">Hugo</a> and
<svg class="icon gdoc_heart"><use xlink:href="#gdoc_heart"></use></svg>
footer_legal_notice: Legal Notice
footer_privacy_policy: Privacy Policy
footer_content_license_prefix: >
Content licensed under
language_switch_no_tranlation_prefix: "Page not translated:"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

View file

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}">
<head>
{{ partial "head/meta" . }}
<title>{{ i18n "error_page_title" }}</title>
{{ partial "head/favicons" . }}
{{ partial "head/others" . }}
</head>
<body>
{{ partial "svg-icon-symbols" . }}
<div class="wrapper">
<input type="checkbox" class="hidden" id="menu-header-control" />
{{ partial "site-header" (dict "Root" . "MenuEnabled" false) }}
<main class="gdoc-error flex-even">
<div class="flex align-center justify-center">
<div class="gdoc-error__icon">
<svg class="icon gdoc_cloud_off"><use xlink:href="#gdoc_cloud_off"></use></svg>
</div>
<div class="gdoc-error__message">
<div class="gdoc-error__line gdoc-error__title">{{ i18n "error_message_title" }}</div>
<div class="gdoc-error__line gdoc-error__code">{{ i18n "error_message_code" }}</div>
<div class="gdoc-error__line gdoc-error__help">
{{ i18n "error_message_text" .Site.BaseURL | safeHTML }}
</div>
</div>
</div>
</main>
{{ partial "site-footer" . }}
</div>
</body>
</html>

View file

@ -1,21 +0,0 @@
{{- $showAnchor := (and (default true .Page.Params.GeekdocAnchor) (default true .Page.Site.Params.GeekdocAnchor)) -}}
<!-- prettier-ignore-start -->
{{- if $showAnchor -}}
<div class="gdoc-page__anchorwrap">
<h{{ .Level }} id="{{ .Anchor | safeURL }}">
{{ .Text | safeHTML }}
<a data-clipboard-text="{{ .Page.Permalink }}#{{ .Anchor | safeURL }}" class="gdoc-page__anchor gdoc-page__anchor--right clip" title="{{ i18n "title_anchor_prefix" }} {{ .Text | safeHTML }}" aria-label="{{ i18n "title_anchor_prefix" }} {{ .Text | safeHTML }}" href="#{{ .Anchor | safeURL }}">
<svg class="icon gdoc_link"><use xlink:href="#gdoc_link"></use></svg>
</a>
</h{{ .Level }}>
</div>
{{- else -}}
<div class="gdoc-page__anchorwrap">
<h{{ .Level }} id="{{ .Anchor | safeURL }}">
{{ .Text | safeHTML }}
</h{{ .Level }}>
</div>
{{- end -}}
<!-- prettier-ignore-end -->

View file

@ -1,6 +0,0 @@
<img
src="{{ .Destination | safeURL }}"
alt="{{ .Text }}"
{{ with .Title }}title="{{ . }}"{{ end }}
/>
{{- /* Drop trailing newlines */ -}}

View file

@ -1,14 +0,0 @@
{{- $raw := or (hasPrefix .Text "<img") (hasPrefix .Text "<figure") -}}
{{- $code := hasPrefix .Text "<code" -}}
<a
class="gdoc-markdown__link{{ if $raw -}}
--raw
{{- else if $code -}}
--code
{{- end }}"
href="{{ .Destination | safeURL }}"
{{ with .Title }}title="{{ . }}"{{ end }}
>
{{- .Text | safeHTML -}}
</a>
{{- /* Drop trailing newlines */ -}}

View file

@ -1,56 +0,0 @@
<!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}" class="color-toggle-hidden">
<head>
{{ partial "head/meta" . }}
<title>
{{- if eq .Kind "home" -}}
{{ .Site.Title }}
{{- else -}}
{{ printf "%s | %s" (partial "utils/title" .) .Site.Title }}
{{- end -}}
</title>
{{ partial "head/favicons" . }}
{{ partial "head/rel-me" . }}
{{ partial "head/microformats" . }}
{{ partial "head/others" . }}
{{ partial "head/custom" . }}
</head>
<body itemscope itemtype="https://schema.org/WebPage">
{{ partial "svg-icon-symbols" . }}
<div
class="wrapper {{ if default false .Site.Params.GeekdocDarkModeDim }}dark-mode-dim{{ end }}"
>
<input type="checkbox" class="hidden" id="menu-control" />
<input type="checkbox" class="hidden" id="menu-header-control" />
{{ $navEnabled := default true .Page.Params.GeekdocNav }}
{{ partial "site-header" (dict "Root" . "MenuEnabled" $navEnabled) }}
<main class="container flex flex-even">
{{ if $navEnabled }}
<aside class="gdoc-nav">
{{ partial "menu" . }}
</aside>
{{ end }}
<div class="gdoc-page">
{{ template "main" . }}
<div class="gdoc-page__footer flex flex-wrap justify-between">
{{ partial "menu-nextprev" . }}
</div>
</div>
</main>
{{ partial "site-footer" . }}
</div>
{{ partial "foot" . }}
</body>
</html>

View file

@ -1,11 +0,0 @@
{{ define "main" }}
{{ partial "page-header" . }}
<article
class="gdoc-markdown gdoc-markdown__align--{{ default "left" (.Page.Params.GeekdocAlign | lower) }}"
>
<h1>{{ partial "utils/title" . }}</h1>
{{ partial "utils/content" . }}
</article>
{{ end }}

Some files were not shown because too many files have changed in this diff Show more