Skip to content

Commit 49c6144

Browse files
committed
Hello world
0 parents  commit 49c6144

15 files changed

Lines changed: 1531 additions & 0 deletions

‎.github/dependabot.yml‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: gomod
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
open-pull-requests-limit: 10
8+
cooldown:
9+
default-days: 7
10+
11+
- package-ecosystem: github-actions
12+
directory: /
13+
schedule:
14+
interval: weekly
15+
open-pull-requests-limit: 5
16+
cooldown:
17+
default-days: 7

‎.github/workflows/ci.yml‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions: {}
10+
11+
jobs:
12+
test:
13+
runs-on: ${{ matrix.os }}
14+
strategy:
15+
matrix:
16+
os: [ubuntu-latest, macos-latest, windows-latest]
17+
go-version: ['1.25']
18+
19+
steps:
20+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21+
with:
22+
persist-credentials: false
23+
24+
- name: Set up Go
25+
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
26+
with:
27+
go-version: ${{ matrix.go-version }}
28+
cache: false
29+
30+
- name: Build without CGo
31+
run: go build -v ./...
32+
env:
33+
CGO_ENABLED: 0
34+
35+
- name: Test
36+
run: go test -v -race -cover ./...
37+
38+
lint:
39+
runs-on: ubuntu-latest
40+
steps:
41+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
42+
with:
43+
persist-credentials: false
44+
45+
- name: Set up Go
46+
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
47+
with:
48+
go-version: '1.25'
49+
cache: false
50+
51+
- name: golangci-lint
52+
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9
53+
with:
54+
version: latest

‎LICENSE‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Andrew Nesbitt
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6+
associated documentation files (the "Software"), to deal in the Software without restriction, including
7+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9+
following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all copies or substantial
12+
portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18+
USE OR OTHER DEALINGS IN THE SOFTWARE.

‎NOTICE‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
The content signature matching in signatures.go is adapted from
2+
src/net/http/sniff.go in Go 1.26.5. The original code is licensed as follows:
3+
4+
Copyright 2009 The Go Authors.
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions are
8+
met:
9+
10+
* Redistributions of source code must retain the above copyright
11+
notice, this list of conditions and the following disclaimer.
12+
* Redistributions in binary form must reproduce the above
13+
copyright notice, this list of conditions and the following disclaimer
14+
in the documentation and/or other materials provided with the
15+
distribution.
16+
* Neither the name of Google LLC nor the names of its
17+
contributors may be used to endorse or promote products derived from
18+
this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
The adapted matching rules come from the pattern-matching algorithm and the
33+
rules for identifying unknown MIME types in sections 6 and 7 of the WHATWG
34+
MIME Sniffing Standard:
35+
36+
https://mimesniff.spec.whatwg.org/

‎README.md‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# magic
2+
3+
Pure Go content detection for files and bounded file prefixes. The package
4+
reports a physical format, MIME type, text encoding, and whether a prefix needs
5+
more bytes. It has no CGO, native runtime, filesystem, or third-party module
6+
dependency.
7+
8+
## Install
9+
10+
```bash
11+
go get github.com/git-pkgs/magic
12+
```
13+
14+
## Use
15+
16+
`Detect` treats its byte slice as the complete file:
17+
18+
```go
19+
result := magic.Detect(data)
20+
21+
switch result.Kind {
22+
case magic.KindText:
23+
fmt.Println(result.Format, result.MIME, result.Encoding)
24+
case magic.KindBinary:
25+
fmt.Println(result.Format, result.MIME)
26+
case magic.KindUnknown:
27+
fmt.Println(result.Reason)
28+
}
29+
```
30+
31+
Call `DetectPrefix` when the bytes came from a bounded read:
32+
33+
```go
34+
result := magic.DetectPrefix(prefix)
35+
if result.Reason == magic.ReasonNeedMore {
36+
// More bytes could change the result.
37+
}
38+
```
39+
40+
A complete binary signature can finish detection from a prefix. Text remains
41+
provisional because later bytes can contain a NUL, an invalid encoding, or a
42+
binary signature. `NeedBytes` is reserved for a known minimum total length and
43+
is zero in the first release.
44+
45+
Both functions are safe for concurrent use. They retain no input and use no
46+
mutable package state.
47+
48+
## Results
49+
50+
`Kind` is `text`, `binary`, or `unknown`. `Format` and `MIME` describe the
51+
physical content. `Encoding` is set for accepted UTF-8, UTF-16LE, or UTF-16BE
52+
text and never appears as a MIME charset parameter.
53+
54+
The first format registry contains:
55+
56+
- ZIP, TAR, gzip, bzip2, xz, PDF, CFBF, PNG, JPEG, and GIF
57+
- plain text, HTML, XML, and SVG
58+
59+
Detection uses bytes only. ZIP-based package types such as JAR, wheel, and
60+
NuGet remain `zip`, and compressed payloads are not opened. A caller can
61+
combine the result with filename or domain rules when it needs a semantic
62+
type.
63+
64+
Text accepts valid UTF-8 and BOM-marked UTF-16. Tab, line feed, form feed,
65+
carriage return, and escape are the permitted C0 controls. Other C0 controls
66+
classify the input as binary. Invalid UTF-8 without a NUL is unknown with
67+
`ReasonInvalidText`; callers that need Latin-1 can apply their own fallback.
68+
69+
HTML, XML, and SVG signatures supply format metadata before the shared text
70+
rules run. The metadata remains present if malformed or control-bearing input
71+
is classified as unknown or binary.
72+
73+
## Performance
74+
75+
The detector performs no allocations for the supplied fixtures. On an Apple
76+
M1 Pro with Go 1.26.5, a 4 KiB text input takes about 1.5 microseconds, the
77+
mixed 4 KiB fixture corpus averages about 0.77 microseconds per call, and a
78+
1 MiB text input takes about 0.35 milliseconds. Importing and calling the
79+
package adds 16,640 bytes to a stripped minimal binary.
80+
81+
Run the package benchmarks on the target machine:
82+
83+
```bash
84+
go test -run '^$' -bench . -benchmem
85+
```
86+
87+
The implementation scans at most 512 bytes for registered signatures. Text
88+
validation is linear in the supplied byte count and uses fixed auxiliary
89+
memory.
90+
91+
## Provenance
92+
93+
The signature matcher is adapted from Go 1.26.5's
94+
`net/http.DetectContentType` and the WHATWG MIME Sniffing Standard. The
95+
registry is intentionally limited to the formats listed above. [NOTICE](NOTICE)
96+
contains the source and license details.
97+
98+
## License
99+
100+
MIT

‎benchmark_test.go‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package magic
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
var benchmarkResult Result
9+
10+
var benchmarkText1MiB = bytes.Repeat([]byte{'x'}, 1<<20)
11+
12+
var benchmarkCorpus = [][]byte{
13+
padFixture([]byte("package magic\n\nfunc Detect(data []byte) Result { return Result{} }\n")),
14+
padFixture([]byte("\xef\xbb\xbfUnicode text: héllo, 世界\n")),
15+
padFixture([]byte("<?xml version=\"1.0\"?><svg >")),
16+
padFixture([]byte("PK\x03\x04")),
17+
padFixture([]byte("\x89PNG\r\n\x1a\n")),
18+
padFixture([]byte{0xff}),
19+
}
20+
21+
func BenchmarkDetect4KiBText(b *testing.B) {
22+
input := benchmarkCorpus[0]
23+
b.ReportAllocs()
24+
b.SetBytes(int64(len(input)))
25+
b.ResetTimer()
26+
for range b.N {
27+
benchmarkResult = Detect(input)
28+
}
29+
}
30+
31+
func BenchmarkDetect4KiBCorpus(b *testing.B) {
32+
b.ReportAllocs()
33+
b.SetBytes(4096)
34+
b.ResetTimer()
35+
36+
index := 0
37+
for range b.N {
38+
benchmarkResult = Detect(benchmarkCorpus[index])
39+
index++
40+
if index == len(benchmarkCorpus) {
41+
index = 0
42+
}
43+
}
44+
}
45+
46+
func BenchmarkDetect4KiBParallel(b *testing.B) {
47+
input := benchmarkCorpus[0]
48+
b.ReportAllocs()
49+
b.SetBytes(int64(len(input)))
50+
b.RunParallel(func(parallel *testing.PB) {
51+
var result Result
52+
for parallel.Next() {
53+
result = Detect(input)
54+
}
55+
benchmarkResult = result
56+
})
57+
}
58+
59+
func BenchmarkDetect1MiBText(b *testing.B) {
60+
input := benchmarkText1MiB
61+
b.ReportAllocs()
62+
b.SetBytes(int64(len(input)))
63+
b.ResetTimer()
64+
for range b.N {
65+
benchmarkResult = Detect(input)
66+
}
67+
}
68+
69+
func padFixture(prefix []byte) []byte {
70+
if len(prefix) >= 4096 {
71+
return prefix[:4096]
72+
}
73+
return append(bytes.Clone(prefix), bytes.Repeat([]byte{'x'}, 4096-len(prefix))...)
74+
}

‎example_test.go‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package magic_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/git-pkgs/magic"
7+
)
8+
9+
func ExampleDetect() {
10+
result := magic.Detect([]byte("package main\n"))
11+
fmt.Println(result.Kind, result.Format, result.MIME, result.Encoding)
12+
13+
// Output:
14+
// text text text/plain utf-8
15+
}
16+
17+
func ExampleDetectPrefix() {
18+
result := magic.DetectPrefix([]byte("hello"))
19+
fmt.Println(result.Kind, result.Reason)
20+
21+
// Output:
22+
// text need-more
23+
}

0 commit comments

Comments
 (0)