mirror of
https://github.com/rjNemo/underscore
synced 2026-06-06 02:26:42 +00:00
Compare commits
88 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a29f64b700 | |||
| f33e86d502 | |||
| c8b01aacc2 | |||
| 85f73f63a9 | |||
| 4f02db2da7 | |||
| 260c48e051 | |||
| 5240c27fcd | |||
| 0bf04c224e | |||
| b35a87e50c | |||
| 3617c2de8f | |||
| bcb4dd1e9d | |||
| 2651a3331a | |||
| d622c8cba8 | |||
| 6576c4fea7 | |||
| 07d05425bb | |||
| 75eddcdde5 | |||
| b04e545d03 | |||
| 40ac16261e | |||
| 106b713cc5 | |||
| a1943556b4 | |||
| 46d52e3cfa | |||
| 7caa23e082 | |||
| 92b64630dc | |||
| 7580836815 | |||
| c53d46816f | |||
| 39be9420c4 | |||
| 9cf61ec6c5 | |||
| 1031038d42 | |||
|
|
8c78743f1a | ||
| fbf58eff42 | |||
|
|
fbbd0398c9 | ||
|
|
acf26bbaf9 | ||
| 539f8c518a | |||
| 507e604582 | |||
| 88dce52921 | |||
| e0fe93efaa | |||
| 78b90acc67 | |||
| eda818c6e0 | |||
|
|
bfac048fb4 | ||
| 541e707861 | |||
| d5426ad3cb | |||
| 64fcfee748 | |||
| 054679fb18 | |||
|
|
291df4fe4e | ||
|
|
0bc3a54efd | ||
|
|
4042208482 | ||
|
|
8c2f92f202 | ||
|
|
d1da6fbab0 | ||
|
|
e5b3ad8ef1 | ||
|
|
7fef1562f2 | ||
|
|
f6af583408 | ||
|
|
1166c701ac | ||
|
|
6a1a4c157b | ||
|
|
d9c1c152ba | ||
|
|
6b377350d6 | ||
|
|
eebeb65114 | ||
|
|
235b36b7f6 | ||
|
|
d19fa7b8c9 | ||
|
|
c00d9af59f | ||
|
|
fb517f3b04 | ||
|
|
edb3c09e43 | ||
|
|
e156992382 | ||
|
|
3bfe1aca18 | ||
|
|
bbb199ce62 | ||
|
|
482e553263 | ||
|
|
28f85bdc94 | ||
|
|
7d422c59d3 | ||
|
|
f73905ddb0 | ||
|
|
c0a8ecea0c | ||
|
|
c9e54a11cb | ||
|
|
6395049991 | ||
|
|
6ebf1e98db | ||
|
|
a900b4ff64 | ||
|
|
4aa4caa2aa | ||
|
|
a0f894fccf | ||
|
|
335dcb53b5 | ||
|
|
75f5b7e5ed | ||
|
|
c65e3ceba6 | ||
|
|
1fa71877c5 | ||
|
|
8cb444fa3f | ||
|
|
81c98e8bf5 | ||
|
|
7460ba86a8 | ||
|
|
b82129f655 | ||
|
|
73ed4021a6 | ||
|
|
2d05b6c518 | ||
|
|
321b5f7844 | ||
|
|
7b6eb23e3d | ||
|
|
290698569f |
371 changed files with 7565 additions and 598 deletions
8
.dockerignore
Normal file
8
.dockerignore
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.DS_Store
|
||||||
|
/docs
|
||||||
|
*.md
|
||||||
|
.git*
|
||||||
|
.idea/
|
||||||
|
.golangci.yml
|
||||||
|
coverage.out
|
||||||
|
.trivycache/
|
||||||
5
.github/FUNDING.yml
vendored
5
.github/FUNDING.yml
vendored
|
|
@ -1,4 +1 @@
|
||||||
# These are supported funding model platforms
|
github: rjNemo
|
||||||
|
|
||||||
github: rjNemo # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
||||||
|
|
||||||
|
|
|
||||||
39
.github/workflows/codeql-analysis.yml
vendored
39
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -1,25 +1,11 @@
|
||||||
# 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"
|
name: "CodeQL"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '24 15 * * 6'
|
- cron: '24 15 * * 6'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze
|
name: Analyze
|
||||||
|
|
@ -28,43 +14,18 @@ jobs:
|
||||||
actions: read
|
actions: read
|
||||||
contents: read
|
contents: read
|
||||||
security-events: write
|
security-events: write
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
language: [ 'go' ]
|
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:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v1
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
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
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
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
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v1
|
||||||
|
|
|
||||||
23
.github/workflows/unit-test.yml
vendored
Normal file
23
.github/workflows/unit-test.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
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
7
.gitignore
vendored
|
|
@ -57,3 +57,10 @@ Icon
|
||||||
Network Trash Folder
|
Network Trash Folder
|
||||||
Temporary Items
|
Temporary Items
|
||||||
.apdisk
|
.apdisk
|
||||||
|
docs/public
|
||||||
|
.trivycache/
|
||||||
|
.vscode/launch.json
|
||||||
|
.claude
|
||||||
|
AGENTS.md
|
||||||
|
bench*txt
|
||||||
|
ACTION_PLAN.md
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "docs/themes/compose"]
|
|
||||||
path = docs/themes/compose
|
|
||||||
url = https://github.com/onweru/compose/
|
|
||||||
|
|
@ -1,42 +1,54 @@
|
||||||
skip-dirs-use-default: true
|
version: "2"
|
||||||
|
|
||||||
run:
|
|
||||||
timeout: 5m
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- deadcode
|
|
||||||
- depguard
|
- depguard
|
||||||
- dogsled
|
- dogsled
|
||||||
- errcheck
|
|
||||||
- errorlint
|
- errorlint
|
||||||
- exportloopref
|
|
||||||
- gocritic
|
- gocritic
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosimple
|
|
||||||
- gosec
|
- gosec
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- misspell
|
- misspell
|
||||||
- noctx
|
- noctx
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- prealloc
|
- prealloc
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- structcheck
|
|
||||||
- stylecheck
|
|
||||||
- typecheck
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- unused
|
|
||||||
- varcheck
|
|
||||||
- whitespace
|
- whitespace
|
||||||
fast: true
|
exclusions:
|
||||||
|
generated: lax
|
||||||
linters-settings:
|
presets:
|
||||||
goimports:
|
- comments
|
||||||
local-prefixes: github.com/rjNemo/underscore
|
- 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$
|
||||||
|
|
|
||||||
183
CLAUDE.md
Normal file
183
CLAUDE.md
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
# 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
|
||||||
14
Dockerfile
14
Dockerfile
|
|
@ -1,10 +1,14 @@
|
||||||
FROM golang:1.18beta1-bullseye
|
FROM golang:1.24.2-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
ENV CGO_ENABLED 0
|
||||||
|
ENV GOOS linux
|
||||||
|
ENV GOARCH amd64
|
||||||
|
|
||||||
|
RUN apk upgrade --no-cache
|
||||||
|
|
||||||
|
WORKDIR /lib
|
||||||
|
|
||||||
COPY go.* ./
|
COPY go.* ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
COPY *.go ./
|
COPY . ./
|
||||||
|
|
||||||
RUN go test ./... -cover
|
|
||||||
|
|
|
||||||
21
Dockerfile.doc
Normal file
21
Dockerfile.doc
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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
|
||||||
25
Makefile
25
Makefile
|
|
@ -1,5 +1,22 @@
|
||||||
test:
|
TEST=go test ./...
|
||||||
docker build .
|
COVER=-coverpkg=./... -coverprofile cov.out -covermode=count; go tool cover -func cov.out; rm cov.out
|
||||||
|
IMAGE=underscore
|
||||||
|
|
||||||
doc:
|
build:
|
||||||
cd docs && hugo server -D
|
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
|
||||||
|
|
|
||||||
167
README.md
167
README.md
|
|
@ -1,20 +1,27 @@
|
||||||
# Underscore
|
# \_Underscore
|
||||||
|
|
||||||

|

|
||||||

|
[](https://pkg.go.dev/github.com/rjNemo/underscore)
|
||||||
|

|
||||||
|

|
||||||
|
[](https://www.bestpractices.dev/projects/9726)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
`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 `go1.18`.
|
It is mostly a port from the `underscore.js` library based on generics brought by
|
||||||
|
`Go 1.18`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
📚 Follow this link for the [documentation](https://underscore.onrender.com/).
|
||||||
|
|
||||||
Install the library using
|
Install the library using
|
||||||
|
|
||||||
```shell
|
```sh
|
||||||
go get github.com/rjNemo/underscore
|
go get github.com/rjNemo/underscore@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Please check out the [examples](examples) to see how to use the library.
|
Please check out the [examples](examples) to see how to use the library.
|
||||||
|
|
@ -23,49 +30,47 @@ Please check out the [examples](examples) to see how to use the library.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
u "github.com/rjNemo/underscore"
|
u "github.com/rjNemo/underscore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||||
// filter even numbers from the slice
|
// filter even numbers from the slice
|
||||||
evens := u.Filter(numbers, func(n int) bool { return n%2 == 0 })
|
evens := u.Filter(numbers, func(n int) bool { return n%2 == 0 })
|
||||||
// square every number in the slice
|
// square every number in the slice
|
||||||
squares := u.Map(evens, func(n int) int { return n * n })
|
squares := u.Map(evens, func(n int) int { return n * n })
|
||||||
// reduce to the sum
|
// reduce to the sum
|
||||||
res := u.Reduce(squares, func(n, acc int) int { return n + acc }, 0)
|
res := u.Reduce(squares, func(n, acc int) int { return n + acc }, 0)
|
||||||
|
|
||||||
fmt.Println(res) // 120
|
fmt.Println(res) // 120
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
These instructions will get you a copy of the project up and running on your local machine for development and testing
|
These instructions will get you a copy of the project up and running on your local
|
||||||
purposes.
|
machine for development and testing purposes.
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
You need at least `go1.18` for development. The project is shipped with a [Dockerfile](Dockerfile) based on `go1.18`. If
|
You need at least `go1.24` for development. The project is shipped with a [Dockerfile](Dockerfile)
|
||||||
you prefer local development, at the moment the easiest way to do it:
|
based on `go1.24`.
|
||||||
|
|
||||||
```shell script
|
If you prefer local development, navigate to the [official
|
||||||
go install golang.org/dl/go1.18beta1@latest
|
download page](https://go.dev/dl/) and install version `1.24` or beyond.
|
||||||
go1.18beta1 download
|
|
||||||
```
|
|
||||||
|
|
||||||
### Installing
|
### Installing
|
||||||
|
|
||||||
First clone the repository
|
First clone the repository
|
||||||
|
|
||||||
```shell
|
```sh
|
||||||
git clone https://github.com/rjNemo/underscore.git
|
git clone https://github.com/rjNemo/underscore.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Install dependencies
|
Install dependencies
|
||||||
|
|
||||||
```shell
|
```sh
|
||||||
go mod download
|
go mod download
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -73,36 +78,108 @@ And that's it.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Building the docker image will run the tests automatically. Otherwise, you can simply run:
|
To run the unit tests, you can simply run:
|
||||||
|
|
||||||
```shell
|
```sh
|
||||||
go test ./...
|
make test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
`underscore` provides 100s of functions that support your favorite functional helpers
|
`underscore` provides many of functions that support your favorite functional helpers
|
||||||
|
|
||||||
### Collections
|
### Collections
|
||||||
|
|
||||||
- `Contains` (only numerics values at the moment)
|
- `All`
|
||||||
|
- `Any`
|
||||||
|
- `Chunk`
|
||||||
|
- `Contains`
|
||||||
|
- `ContainsBy`
|
||||||
|
- `Count`
|
||||||
|
- `Difference`
|
||||||
|
- `Drop`
|
||||||
- `Each`
|
- `Each`
|
||||||
- `Every`
|
|
||||||
- `Filter`
|
- `Filter`
|
||||||
- `Find`
|
- `Find`
|
||||||
|
- `Flatmap`
|
||||||
|
- `GroupBy`
|
||||||
|
- `Intersection`
|
||||||
|
- `Join` / `JoinProject`
|
||||||
|
- `Last`
|
||||||
- `Map`
|
- `Map`
|
||||||
- `Max`
|
- `Max`
|
||||||
- `Min`
|
- `Min`
|
||||||
|
- `OrderBy`
|
||||||
- `Partition`
|
- `Partition`
|
||||||
|
- `Range`
|
||||||
- `Reduce`
|
- `Reduce`
|
||||||
- `Some`
|
- `RemoveAt`
|
||||||
|
- `Sum` / `SumMap`
|
||||||
|
- `Unique`
|
||||||
|
- `UniqueBy`
|
||||||
|
- `UniqueInPlace`
|
||||||
|
- `Zip`
|
||||||
|
|
||||||
### Chaining
|
### Pipe
|
||||||
|
|
||||||
Calling `NewChain` will cause all future method calls to return wrapped objects. When you've finished the computation,
|
Calling `NewPipe` will cause all future method calls to return wrapped values. When
|
||||||
call `Value` to retrieve the final value.
|
you've finished the computation, call `Value` to retrieve the final value.
|
||||||
|
|
||||||
Methods not returning a slice such as `Reduce`, `Every`, `Some`, will break the chain and return `Value` instantly.
|
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
|
||||||
|
|
||||||
## Built With
|
## Built With
|
||||||
|
|
||||||
|
|
@ -110,8 +187,8 @@ Methods not returning a slice such as `Reduce`, `Every`, `Some`, will break the
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull
|
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct,
|
||||||
requests to us.
|
and the process for submitting pull requests to us.
|
||||||
|
|
||||||
## Versioning
|
## Versioning
|
||||||
|
|
||||||
|
|
@ -122,13 +199,15 @@ the [tags on this repository](https://github.com/rjNemo/underscore/tags).
|
||||||
|
|
||||||
- **Ruidy** - _Initial work_ - [Ruidy](https://github.com/rjNemo)
|
- **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
|
## 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
|
## Acknowledgments
|
||||||
|
|
||||||
This project is largely inspired by [Underscore.js](https://underscorejs.org/#) library. Check out the original project
|
This project is largely inspired by [Underscore.js](https://underscorejs.org/#)
|
||||||
if you don't already know it.
|
library. Check out the original project if you don't already know it.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package underscore
|
package underscore
|
||||||
|
|
||||||
// Every returns true if all the values in the slice pass the predicate truth test.
|
// 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.
|
// Short-circuits and stops traversing the slice if a false element is found.
|
||||||
func Every[T any](values []T, predicate func(T) bool) bool {
|
func All[T any](values []T, predicate func(T) bool) bool {
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
if !predicate(v) {
|
if !predicate(v) {
|
||||||
return false
|
return false
|
||||||
21
all_test.go
Normal file
21
all_test.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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
Normal file
9
any.go
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
23
any_test.go
Normal file
23
any_test.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
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))
|
||||||
|
}
|
||||||
19
chunk.go
Normal file
19
chunk.go
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
34
chunk_test.go
Normal file
34
chunk_test.go
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
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))
|
||||||
|
}
|
||||||
16
contains.go
16
contains.go
|
|
@ -1,13 +1,13 @@
|
||||||
package underscore
|
package underscore
|
||||||
|
|
||||||
import "constraints"
|
import "slices"
|
||||||
|
|
||||||
// Contains returns true if the value is present in the slice
|
// Contains returns true if the value is present in the slice
|
||||||
func Contains[T constraints.Ordered](values []T, value T) bool {
|
func Contains[T comparable](values []T, value T) bool {
|
||||||
for _, v := range values {
|
return slices.Contains(values, value)
|
||||||
if v == value {
|
}
|
||||||
return true
|
|
||||||
}
|
// ContainsBy returns true if any element in the slice satisfies the predicate.
|
||||||
}
|
func ContainsBy[T any](values []T, predicate func(T) bool) bool {
|
||||||
return false
|
return slices.ContainsFunc(values, predicate)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,24 @@ func TestContains(t *testing.T) {
|
||||||
nums := []int{1, 3, 5, 7, 9}
|
nums := []int{1, 3, 5, 7, 9}
|
||||||
assert.True(t, u.Contains(nums, 5))
|
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" }))
|
||||||
|
}
|
||||||
|
|
|
||||||
13
count.go
Normal file
13
count.go
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
65
count_test.go
Normal file
65
count_test.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
8
difference.go
Normal file
8
difference.go
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
17
difference_test.go
Normal file
17
difference_test.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,12 +1,25 @@
|
||||||
baseURL = 'http://example.org/'
|
baseURL = "https://underscore.onrender.com/"
|
||||||
languageCode = 'en-us'
|
title = "underscore"
|
||||||
title = 'Underscore'
|
theme = "hugo-geekdoc"
|
||||||
theme = "compose" # edit this if you'ld rather use a fork of this repo
|
|
||||||
|
pluralizeListTitles = false
|
||||||
|
|
||||||
|
# Geekdoc required configuration
|
||||||
|
pygmentsUseClasses = true
|
||||||
|
pygmentsCodeFences = true
|
||||||
|
disablePathToLower = true
|
||||||
|
|
||||||
|
# Required if you want to render robots.txt template
|
||||||
enableRobotsTXT = true
|
enableRobotsTXT = true
|
||||||
[outputs]
|
|
||||||
home = ["HTML", "RSS","JSON"]
|
# Needed for mermaid shortcodes
|
||||||
[params]
|
[markup]
|
||||||
logo.lightMode = "logo.png"
|
[markup.goldmark.renderer]
|
||||||
logo.darkMode = "logo.png"
|
# Needed for mermaid shortcode
|
||||||
author.name = "Ruidy"
|
unsafe = true
|
||||||
author.url = "https://github.com/rjNemo"
|
[markup.tableOfContents]
|
||||||
|
startLevel = 1
|
||||||
|
endLevel = 9
|
||||||
|
|
||||||
|
[taxonomies]
|
||||||
|
tag = "tags"
|
||||||
48
docs/content/_index.md
Normal file
48
docs/content/_index.md
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
title: _Underscore
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
[](https://pkg.go.dev/github.com/rjNemo/underscore)
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
`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
|
||||||
|
}
|
||||||
|
```
|
||||||
22
docs/content/collections/all.md
Normal file
22
docs/content/collections/all.md
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
22
docs/content/collections/any.md
Normal file
22
docs/content/collections/any.md
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
19
docs/content/collections/chunk.md
Normal file
19
docs/content/collections/chunk.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
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]]
|
||||||
|
}
|
||||||
|
```
|
||||||
21
docs/content/collections/contains.md
Normal file
21
docs/content/collections/contains.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
20
docs/content/collections/containsby.md
Normal file
20
docs/content/collections/containsby.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
22
docs/content/collections/difference.md
Normal file
22
docs/content/collections/difference.md
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
```
|
||||||
22
docs/content/collections/drop.md
Normal file
22
docs/content/collections/drop.md
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
```
|
||||||
25
docs/content/collections/dropwhile.md
Normal file
25
docs/content/collections/dropwhile.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
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"]
|
||||||
|
}
|
||||||
|
```
|
||||||
25
docs/content/collections/each.md
Normal file
25
docs/content/collections/each.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
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"}
|
||||||
|
}
|
||||||
|
```
|
||||||
21
docs/content/collections/filter.md
Normal file
21
docs/content/collections/filter.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
```
|
||||||
26
docs/content/collections/find.md
Normal file
26
docs/content/collections/find.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
31
docs/content/collections/first.md
Normal file
31
docs/content/collections/first.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
23
docs/content/collections/firstn.md
Normal file
23
docs/content/collections/firstn.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
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)) // []
|
||||||
|
}
|
||||||
|
```
|
||||||
23
docs/content/collections/flatmap.md
Normal file
23
docs/content/collections/flatmap.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
```
|
||||||
39
docs/content/collections/foldright.md
Normal file
39
docs/content/collections/foldright.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
```
|
||||||
21
docs/content/collections/groupby.md
Normal file
21
docs/content/collections/groupby.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
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}}
|
||||||
|
}
|
||||||
|
```
|
||||||
32
docs/content/collections/init.md
Normal file
32
docs/content/collections/init.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
25
docs/content/collections/intersection.md
Normal file
25
docs/content/collections/intersection.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
28
docs/content/collections/intersperse.md
Normal file
28
docs/content/collections/intersperse.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
```
|
||||||
28
docs/content/collections/last.md
Normal file
28
docs/content/collections/last.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
24
docs/content/collections/map.md
Normal file
24
docs/content/collections/map.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
```
|
||||||
21
docs/content/collections/max.md
Normal file
21
docs/content/collections/max.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
21
docs/content/collections/min.md
Normal file
21
docs/content/collections/min.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
24
docs/content/collections/parallel_filter.md
Normal file
24
docs/content/collections/parallel_filter.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
```
|
||||||
25
docs/content/collections/parallel_map.md
Normal file
25
docs/content/collections/parallel_map.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
```
|
||||||
51
docs/content/collections/parallel_reduce.md
Normal file
51
docs/content/collections/parallel_reduce.md
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
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.
|
||||||
25
docs/content/collections/partition.md
Normal file
25
docs/content/collections/partition.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
```
|
||||||
22
docs/content/collections/reduce.md
Normal file
22
docs/content/collections/reduce.md
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
43
docs/content/collections/replicate.md
Normal file
43
docs/content/collections/replicate.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
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 }, ""))
|
||||||
|
// ----------------------------------------
|
||||||
|
}
|
||||||
|
```
|
||||||
37
docs/content/collections/scan.md
Normal file
37
docs/content/collections/scan.md
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
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!"]
|
||||||
|
}
|
||||||
|
```
|
||||||
43
docs/content/collections/sliding.md
Normal file
43
docs/content/collections/sliding.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
21
docs/content/collections/sum.md
Normal file
21
docs/content/collections/sum.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
25
docs/content/collections/takewhile.md
Normal file
25
docs/content/collections/takewhile.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
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"]
|
||||||
|
}
|
||||||
|
```
|
||||||
47
docs/content/collections/tap.md
Normal file
47
docs/content/collections/tap.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
---
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
45
docs/content/collections/transpose.md
Normal file
45
docs/content/collections/transpose.md
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
```
|
||||||
21
docs/content/collections/unique.md
Normal file
21
docs/content/collections/unique.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
24
docs/content/collections/unique_by.md
Normal file
24
docs/content/collections/unique_by.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
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}]
|
||||||
|
}
|
||||||
|
```
|
||||||
21
docs/content/collections/unique_in_place.md
Normal file
21
docs/content/collections/unique_in_place.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
```
|
||||||
43
docs/content/collections/unzip.md
Normal file
43
docs/content/collections/unzip.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
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) // [] []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
+++ 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`.
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
---
|
|
||||||
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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
---
|
|
||||||
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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
6
docs/content/maps/_index.md
Normal file
6
docs/content/maps/_index.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
title: "Map Helpers"
|
||||||
|
date: 2025-09-01T00:00:00-00:00
|
||||||
|
---
|
||||||
|
|
||||||
|
Utilities for Go maps provided by the `maps` subpackage.
|
||||||
19
docs/content/maps/keys.md
Normal file
19
docs/content/maps/keys.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
```
|
||||||
19
docs/content/maps/values.md
Normal file
19
docs/content/maps/values.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
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"]
|
||||||
|
}
|
||||||
|
```
|
||||||
32
docs/content/pipe/new_pipe.md
Normal file
32
docs/content/pipe/new_pipe.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
74
docs/content/usage/start.md
Normal file
74
docs/content/usage/start.md
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
```
|
||||||
6
docs/data/menu/extra.yaml
Normal file
6
docs/data/menu/extra.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
header:
|
||||||
|
- name: GitHub
|
||||||
|
ref: https://github.com/rjNemo/underscore
|
||||||
|
icon: gdoc_github
|
||||||
|
external: true
|
||||||
13
docs/data/menu/more.yaml
Normal file
13
docs/data/menu/more.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
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
|
|
@ -1 +0,0 @@
|
||||||
{"Target":"css/styles.35cccf4dc83860130d466a273c4bd3f1342dc22b167106c704a21ae7307d2d98f4701138b92f3fe17054f928a248863a30ad7863fe6b3816582123ff58f21da6.css","MediaType":"text/css","Data":{"Integrity":"sha512-NczPTcg4YBMNRmonPEvT8TQtwisWcQbHBKIa5zB9LZj0cBE4uS8/4XBU+SiiSIY6MK14Y/5rOBZYISP/WPIdpg=="}}
|
|
||||||
1
docs/themes/compose
vendored
1
docs/themes/compose
vendored
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 362af2409158972008f1dff8ee5e887d6ce88257
|
|
||||||
21
docs/themes/hugo-geekdoc/LICENSE
vendored
Normal file
21
docs/themes/hugo-geekdoc/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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.
|
||||||
44
docs/themes/hugo-geekdoc/README.md
vendored
Normal file
44
docs/themes/hugo-geekdoc/README.md
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Geekdoc
|
||||||
|
|
||||||
|
[](https://drone.thegeeklab.de/thegeeklab/hugo-geekdoc)
|
||||||
|
[](https://gohugo.io)
|
||||||
|
[](https://github.com/thegeeklab/hugo-geekdoc/releases/latest)
|
||||||
|
[](https://github.com/thegeeklab/hugo-geekdoc/graphs/contributors)
|
||||||
|
[](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).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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)
|
||||||
1
docs/themes/hugo-geekdoc/VERSION
vendored
Normal file
1
docs/themes/hugo-geekdoc/VERSION
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
v0.27.5
|
||||||
7
docs/themes/hugo-geekdoc/archetypes/docs.md
vendored
Normal file
7
docs/themes/hugo-geekdoc/archetypes/docs.md
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
title: "{{ .Name | humanize | title }}"
|
||||||
|
weight: 1
|
||||||
|
# geekdocFlatSection: false
|
||||||
|
# geekdocToc: 6
|
||||||
|
# geekdocHidden: false
|
||||||
|
---
|
||||||
4
docs/themes/hugo-geekdoc/archetypes/posts.md
vendored
Normal file
4
docs/themes/hugo-geekdoc/archetypes/posts.md
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: "{{ replace .Name "-" " " | title }}"
|
||||||
|
date: {{ .Date }}
|
||||||
|
---
|
||||||
7
docs/themes/hugo-geekdoc/assets/search/config.json
vendored
Normal file
7
docs/themes/hugo-geekdoc/assets/search/config.json
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{{- $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 }}
|
||||||
|
}
|
||||||
12
docs/themes/hugo-geekdoc/assets/search/data.json
vendored
Normal file
12
docs/themes/hugo-geekdoc/assets/search/data.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[
|
||||||
|
{{ 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 }}
|
||||||
|
]
|
||||||
1
docs/themes/hugo-geekdoc/assets/sprites/geekdoc.svg
vendored
Normal file
1
docs/themes/hugo-geekdoc/assets/sprites/geekdoc.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
498
docs/themes/hugo-geekdoc/data/assets.json
vendored
Normal file
498
docs/themes/hugo-geekdoc/data/assets.json
vendored
Normal file
|
|
@ -0,0 +1,498 @@
|
||||||
|
{
|
||||||
|
"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=="
|
||||||
|
}
|
||||||
|
}
|
||||||
45
docs/themes/hugo-geekdoc/i18n/de.yaml
vendored
Normal file
45
docs/themes/hugo-geekdoc/i18n/de.yaml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
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:"
|
||||||
45
docs/themes/hugo-geekdoc/i18n/en.yaml
vendored
Normal file
45
docs/themes/hugo-geekdoc/i18n/en.yaml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
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:"
|
||||||
BIN
docs/themes/hugo-geekdoc/images/readme.png
vendored
Normal file
BIN
docs/themes/hugo-geekdoc/images/readme.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 KiB |
BIN
docs/themes/hugo-geekdoc/images/screenshot.png
vendored
Normal file
BIN
docs/themes/hugo-geekdoc/images/screenshot.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 508 KiB |
BIN
docs/themes/hugo-geekdoc/images/tn.png
vendored
Normal file
BIN
docs/themes/hugo-geekdoc/images/tn.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
40
docs/themes/hugo-geekdoc/layouts/404.html
vendored
Normal file
40
docs/themes/hugo-geekdoc/layouts/404.html
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!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>
|
||||||
21
docs/themes/hugo-geekdoc/layouts/_default/_markup/render-heading.html
vendored
Normal file
21
docs/themes/hugo-geekdoc/layouts/_default/_markup/render-heading.html
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{{- $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 -->
|
||||||
6
docs/themes/hugo-geekdoc/layouts/_default/_markup/render-image.html
vendored
Normal file
6
docs/themes/hugo-geekdoc/layouts/_default/_markup/render-image.html
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<img
|
||||||
|
src="{{ .Destination | safeURL }}"
|
||||||
|
alt="{{ .Text }}"
|
||||||
|
{{ with .Title }}title="{{ . }}"{{ end }}
|
||||||
|
/>
|
||||||
|
{{- /* Drop trailing newlines */ -}}
|
||||||
14
docs/themes/hugo-geekdoc/layouts/_default/_markup/render-link.html
vendored
Normal file
14
docs/themes/hugo-geekdoc/layouts/_default/_markup/render-link.html
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{{- $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 */ -}}
|
||||||
56
docs/themes/hugo-geekdoc/layouts/_default/baseof.html
vendored
Normal file
56
docs/themes/hugo-geekdoc/layouts/_default/baseof.html
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<!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>
|
||||||
11
docs/themes/hugo-geekdoc/layouts/_default/list.html
vendored
Normal file
11
docs/themes/hugo-geekdoc/layouts/_default/list.html
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{{ 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 }}
|
||||||
11
docs/themes/hugo-geekdoc/layouts/_default/single.html
vendored
Normal file
11
docs/themes/hugo-geekdoc/layouts/_default/single.html
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{{ 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
Loading…
Reference in a new issue