How this was measured
110 | $ cd $(go env GOPATH)/src/github.com/go-enry/go-license-detector/v4/licensedb
111 | $ mkdir dataset && cd dataset
112 | $ unzip ../dataset.zip
113 | $ # go-enry/go-license-detector
114 | $ time license-detector * \
115 | | grep -Pzo '\n[-0-9a-zA-Z]+\n\tno license' | grep -Pa '\tno ' | wc -l
116 | $ # benbalter/licensee
117 | $ time ls -1 | xargs -n1 -P4 licensee \
118 | | grep -E "^License: Other" | wc -l
119 | $ # google/licenseclassifier
120 | $ time find -type f -print | xargs -n1 -P4 identify_license \
121 | | cut -d/ -f2 | sort | uniq | wc -l
122 | $ # boyter/lc
123 | $ time lc . \
124 | | grep -vE 'NOASSERTION|----|Directory' | cut -d" " -f1 | sort | uniq | wc -l
125 | $ # amzn/askalono
126 | $ echo '#!/bin/sh
127 | result=$(askalono id "$1")
128 | echo "$1
129 | $result"' > ../askalono.wrapper
130 | $ time find -type f -print | xargs -n1 -P4 sh ../askalono.wrapper | grep -Pzo '.*\nLicense: .*\n' askalono.txt | grep -av "License: " | cut -d/ -f 2 | sort | uniq | wc -l
131 | $ # LiD
132 | $ time license-identifier -I dataset -F csv -O lid
133 | $ cat lid_*.csv | cut -d, -f1 | cut -d"'" -f 2 | grep / | cut -d/ -f2 | sort | uniq | wc -l
134 |
135 |
136 |
137 | ## Regenerate binary data
138 |
139 | The SPDX licenses are included into the binary. To update them, run
140 | ```
141 | # go install github.com/go-bindata/go-bindata/...
142 | make licensedb/internal/assets/bindata.go
143 | ```
144 |
145 | ## Contributions
146 |
147 | ...are welcome, see [CONTRIBUTING.md](CONTRIBUTING.md) and [code of conduct](CODE_OF_CONDUCT.md).
148 |
149 | ## License
150 |
151 | Apache 2.0, see [LICENSE.md](LICENSE.md).
152 |
--------------------------------------------------------------------------------
/cmd/license-detector/main.go:
--------------------------------------------------------------------------------
1 | // license-detector prints the most probable licenses for a repository
2 | // given either its path in the local file system or a URL pointing to
3 | // the repository.
4 | package main
5 |
6 | import (
7 | "encoding/json"
8 | "fmt"
9 | "io"
10 | "log"
11 | "os"
12 |
13 | "github.com/go-enry/go-license-detector/v4/licensedb"
14 | "github.com/spf13/pflag"
15 | )
16 |
17 | func main() {
18 | format := pflag.StringP("format", "f", "text", "Output format: json, text")
19 | pflag.Usage = func() {
20 | fmt.Fprintln(os.Stderr, "Usage: license-detector path ...")
21 | pflag.PrintDefaults()
22 | }
23 | pflag.Parse()
24 | if (*format != "json" && *format != "text") || pflag.NArg() == 0 {
25 | pflag.Usage()
26 | os.Exit(1)
27 | }
28 | detect(pflag.Args(), *format, os.Stdout)
29 | }
30 |
31 | // detect runs license analysis on each item in `args`` and outputs
32 | // the results in the specified `format` to `writer`.
33 | func detect(args []string, format string, writer io.Writer) {
34 | results := licensedb.Analyse(args...)
35 |
36 | switch format {
37 | case "text":
38 | for _, res := range results {
39 | fmt.Fprintln(writer, res.Arg)
40 | if res.ErrStr != "" {
41 | fmt.Fprintf(writer, "\t%v\n", res.ErrStr)
42 | continue
43 | }
44 | for _, m := range res.Matches {
45 | fmt.Fprintf(writer, "\t%1.f%%\t%s\n", 100*m.Confidence, m.License)
46 | }
47 | }
48 | case "json":
49 | b, err := json.MarshalIndent(results, "", "\t")
50 | if err != nil {
51 | log.Fatalf("could not encode result to JSON: %v", err)
52 | }
53 | fmt.Fprintf(writer, "%s\n", b)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/cmd/license-detector/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "testing"
7 |
8 | "github.com/go-enry/go-license-detector/v4/licensedb"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestCmdMain(t *testing.T) {
13 | buffer := &bytes.Buffer{}
14 | detect([]string{"../..", "."}, "json", buffer)
15 | var r []licensedb.Result
16 | err := json.Unmarshal(buffer.Bytes(), &r)
17 | assert.NoError(t, err)
18 | assert.Len(t, r, 2)
19 | assert.Equal(t, "../..", r[0].Arg)
20 | assert.Equal(t, ".", r[1].Arg)
21 | assert.Len(t, r[0].Matches, 4)
22 | assert.Len(t, r[1].Matches, 0)
23 | assert.Equal(t, "", r[0].ErrStr)
24 | assert.Equal(t, "no license file was found", r[1].ErrStr)
25 | assert.Equal(t, "Apache-2.0", r[0].Matches[0].License)
26 | assert.InDelta(t, 0.9877, r[0].Matches[0].Confidence, 0.002)
27 | assert.Equal(t, "ECL-2.0", r[0].Matches[1].License)
28 | assert.InDelta(t, 0.9047, r[0].Matches[1].Confidence, 0.002)
29 | buffer.Reset()
30 | detect([]string{"../..", "."}, "text", buffer)
31 | assert.Equal(t, `../..
32 | 99% Apache-2.0
33 | 90% ECL-2.0
34 | 81% SHL-0.51
35 | 81% SHL-0.5
36 | .
37 | no license file was found
38 | `, buffer.String())
39 | }
40 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-enry/go-license-detector/v4
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/ekzhu/minhash-lsh v0.0.0-20190924033628-faac2c6342f8
7 | github.com/go-git/go-git/v5 v5.4.2
8 | github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b
9 | github.com/jdkato/prose v1.2.1
10 | github.com/pkg/errors v0.9.1
11 | github.com/russross/blackfriday/v2 v2.1.0
12 | github.com/sergi/go-diff v1.2.0
13 | github.com/spf13/pflag v1.0.5
14 | github.com/stretchr/testify v1.8.0
15 | golang.org/x/exp v0.0.0-20221006183845-316c7553db56
16 | golang.org/x/net v0.0.0-20221004154528-8021a29435af
17 | golang.org/x/text v0.3.7
18 | gonum.org/v1/gonum v0.8.2
19 | )
20 |
21 | require (
22 | github.com/Microsoft/go-winio v0.6.0 // indirect
23 | github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad // indirect
24 | github.com/acomagu/bufpipe v1.0.3 // indirect
25 | github.com/cloudflare/circl v1.2.0 // indirect
26 | github.com/davecgh/go-spew v1.1.1 // indirect
27 | github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect
28 | github.com/dgryski/go-minhash v0.0.0-20190315135803-ad340ca03076 // indirect
29 | github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2 // indirect
30 | github.com/emirpasic/gods v1.18.1 // indirect
31 | github.com/go-git/gcfg v1.5.0 // indirect
32 | github.com/go-git/go-billy/v5 v5.3.1 // indirect
33 | github.com/imdario/mergo v0.3.13 // indirect
34 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
35 | github.com/kevinburke/ssh_config v1.2.0 // indirect
36 | github.com/mitchellh/go-homedir v1.1.0 // indirect
37 | github.com/montanaflynn/stats v0.6.6 // indirect
38 | github.com/pmezard/go-difflib v1.0.0 // indirect
39 | github.com/shogo82148/go-shuffle v1.0.1 // indirect
40 | github.com/xanzy/ssh-agent v0.3.2 // indirect
41 | golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b // indirect
42 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
43 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
44 | golang.org/x/tools v0.1.12 // indirect
45 | gopkg.in/neurosnap/sentences.v1 v1.0.7 // indirect
46 | gopkg.in/warnings.v0 v0.1.2 // indirect
47 | gopkg.in/yaml.v3 v3.0.1 // indirect
48 | )
49 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
2 | github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
3 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
4 | github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
5 | github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
6 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
7 | github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad h1:QeeqI2zxxgZVe11UrYFXXx6gVxPVF40ygekjBzEg4XY=
8 | github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
9 | github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
10 | github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
11 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
12 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
13 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
14 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
15 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
16 | github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
17 | github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
18 | github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
19 | github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec=
20 | github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk=
21 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
22 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
24 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
25 | github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8=
26 | github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
27 | github.com/dgryski/go-minhash v0.0.0-20190315135803-ad340ca03076 h1:EB7M2v8Svo3kvIDy+P1YDE22XskDQP+TEYGzeDwPAN4=
28 | github.com/dgryski/go-minhash v0.0.0-20190315135803-ad340ca03076/go.mod h1:VBi0XHpFy0xiMySf6YpVbRqrupW4RprJ5QTyN+XvGSM=
29 | github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2 h1:lx1ZQgST/imDhmLpYDma1O3Cx9L+4Ie4E8S2RjFPQ30=
30 | github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
31 | github.com/ekzhu/minhash-lsh v0.0.0-20190924033628-faac2c6342f8 h1:+Tje+xk1lmGKSJjYNtgCFsU1HtQzz0kCm1DFbKlvFBo=
32 | github.com/ekzhu/minhash-lsh v0.0.0-20190924033628-faac2c6342f8/go.mod h1:yEtCVi+QamvzjEH4U/m6ZGkALIkF2xfQnFp0BcKmIOk=
33 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
34 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
35 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
36 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
37 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
38 | github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
39 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
40 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
41 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
42 | github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
43 | github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
44 | github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
45 | github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
46 | github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
47 | github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
48 | github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
49 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
50 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
51 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
52 | github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b h1:Jdu2tbAxkRouSILp2EbposIb8h4gO+2QuZEn3d9sKAc=
53 | github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b/go.mod h1:HmaZGXHdSwQh1jnUlBGN2BeEYOHACLVGzYOXCbsLvxY=
54 | github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
55 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
56 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
57 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
58 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
59 | github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU=
60 | github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA=
61 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
62 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
63 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
64 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
65 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
66 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
67 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
68 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
69 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
70 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
71 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
72 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
73 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
74 | github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
75 | github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
76 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
77 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
78 | github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
79 | github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ=
80 | github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
81 | github.com/neurosnap/sentences v1.0.6 h1:iBVUivNtlwGkYsJblWV8GGVFmXzZzak907Ci8aA0VTE=
82 | github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
83 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
84 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
85 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
86 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
87 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
88 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
89 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
90 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
91 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
92 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
93 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
94 | github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
95 | github.com/shogo82148/go-shuffle v1.0.1 h1:4swIpHXLMAz14DE4YTgakgadpRN0n1wE1dieGnOTVFU=
96 | github.com/shogo82148/go-shuffle v1.0.1/go.mod h1:HQPjVgUUZ9TNgm4/K/iXRuAdhPsQrXnAGgtk/9kqbBY=
97 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
98 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
99 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
100 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
101 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
102 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
103 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
104 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
105 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
106 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
107 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
108 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
109 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
110 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
111 | github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
112 | github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM=
113 | github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
114 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
115 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
116 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
117 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
118 | golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
119 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
120 | golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
121 | golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
122 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
123 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
124 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
125 | golang.org/x/exp v0.0.0-20221006183845-316c7553db56 h1:BrYbdKcCNjLyrN6aKqXy4hPw9qGI8IATkj4EWv9Q+kQ=
126 | golang.org/x/exp v0.0.0-20221006183845-316c7553db56/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
127 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
128 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
129 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
130 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
131 | golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
132 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
133 | golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
134 | golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
135 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
136 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
137 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
138 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
139 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
140 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
141 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
142 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
143 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
144 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
145 | golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
146 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
147 | golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
148 | golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
149 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
150 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
151 | golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
152 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
153 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
154 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
155 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
156 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
157 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
158 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
159 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
160 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
161 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
162 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
163 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
164 | gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
165 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
166 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
167 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
168 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
169 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
170 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
171 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
172 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
173 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
174 | gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
175 | gopkg.in/neurosnap/sentences.v1 v1.0.7 h1:gpTUYnqthem4+o8kyTLiYIB05W+IvdQFYR29erfe8uU=
176 | gopkg.in/neurosnap/sentences.v1 v1.0.7/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
177 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
178 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
179 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
180 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
181 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
182 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
183 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
184 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
185 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
186 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
187 |
--------------------------------------------------------------------------------
/licensedb/analysis.go:
--------------------------------------------------------------------------------
1 | package licensedb
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "os"
7 | "sort"
8 | "sync"
9 |
10 | "github.com/go-enry/go-license-detector/v4/licensedb/filer"
11 | )
12 |
13 | // Analyse runs license analysis on each item in `args`
14 | func Analyse(args ...string) []Result {
15 | nargs := len(args)
16 | results := make([]Result, nargs)
17 | var wg sync.WaitGroup
18 | wg.Add(nargs)
19 | for i, arg := range args {
20 | go func(i int, arg string) {
21 | defer wg.Done()
22 | matches, err := process(arg)
23 | res := Result{Arg: arg, Matches: matches}
24 | if err != nil {
25 | res.ErrStr = err.Error()
26 | }
27 | results[i] = res
28 | }(i, arg)
29 | }
30 | wg.Wait()
31 |
32 | return results
33 | }
34 |
35 | // Result gathers license detection results for a project path
36 | type Result struct {
37 | Arg string `json:"project,omitempty"`
38 | Matches []Match `json:"matches,omitempty"`
39 | ErrStr string `json:"error,omitempty"`
40 | }
41 |
42 | // Match describes the level of confidence for the detected License
43 | type Match struct {
44 | License string `json:"license"`
45 | Confidence float32 `json:"confidence"`
46 | File string `json:"file"`
47 | }
48 |
49 | func process(arg string) ([]Match, error) {
50 | newFiler := filer.FromDirectory
51 | if _, err := os.Stat(arg); err != nil {
52 | if !os.IsNotExist(err) {
53 | return nil, err
54 | }
55 |
56 | if _, err := url.Parse(arg); err == nil {
57 | newFiler = filer.FromGitURL
58 | } else {
59 | return nil, fmt.Errorf("arg should be a valid path or a URL")
60 | }
61 | }
62 |
63 | resolvedFiler, err := newFiler(arg)
64 | if err != nil {
65 | return nil, err
66 | }
67 |
68 | ls, err := Detect(resolvedFiler)
69 | if err != nil {
70 | return nil, err
71 | }
72 |
73 | var matches []Match
74 | for k, v := range ls {
75 | matches = append(matches, Match{k, v.Confidence, v.File})
76 | }
77 | sort.Slice(matches, func(i, j int) bool { return matches[i].Confidence > matches[j].Confidence })
78 | return matches, nil
79 | }
80 |
--------------------------------------------------------------------------------
/licensedb/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // Match is a detection result of a license with a confidence (0.0 - 1.0)
4 | // and a mapping of files to confidence.
5 | type Match struct {
6 | Files map[string]float32
7 | Confidence float32
8 | File string
9 | }
10 |
--------------------------------------------------------------------------------
/licensedb/dataset.projects.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/go-enry/go-license-detector/e0d6f0187f3a3aaeb8236f9860337ffb92438723/licensedb/dataset.projects.gz
--------------------------------------------------------------------------------
/licensedb/dataset.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/go-enry/go-license-detector/e0d6f0187f3a3aaeb8236f9860337ffb92438723/licensedb/dataset.zip
--------------------------------------------------------------------------------
/licensedb/dataset_test.go:
--------------------------------------------------------------------------------
1 | package licensedb
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "sync"
7 | "testing"
8 |
9 | "github.com/go-enry/go-license-detector/v4/licensedb/api"
10 | "github.com/go-enry/go-license-detector/v4/licensedb/filer"
11 |
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func TestDataset(t *testing.T) {
16 | rootFiler, err := filer.FromZIP("dataset.zip")
17 | assert.Nil(t, err)
18 | defer rootFiler.Close()
19 | projects, err := rootFiler.ReadDir("")
20 | assert.Nil(t, err)
21 | licenses := map[string]map[string]api.Match{}
22 | mutex := sync.Mutex{}
23 | wg := sync.WaitGroup{}
24 | wg.Add(len(projects))
25 | for _, project := range projects {
26 | go func(project filer.File) {
27 | defer wg.Done()
28 | myLicenses, _ := Detect(filer.NestFiler(rootFiler, project.Name))
29 | if len(myLicenses) > 0 {
30 | mutex.Lock()
31 | licenses[project.Name] = myLicenses
32 | mutex.Unlock()
33 | }
34 | }(project)
35 | }
36 | wg.Wait()
37 | assert.True(t, len(licenses) >= 893)
38 | // the rest len(projects) - 902 do not contain any license information
39 | fmt.Printf("%d %d %d%%\n", len(licenses), 902, (100*len(licenses))/902)
40 | if os.Getenv("LICENSE_TEST_DEBUG") != "" {
41 | for _, project := range projects {
42 | if _, exists := licenses[project.Name]; !exists {
43 | println(project.Name)
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/licensedb/filer/filer.go:
--------------------------------------------------------------------------------
1 | package filer
2 |
3 | import (
4 | "archive/zip"
5 | "bytes"
6 | "io/fs"
7 | "io/ioutil"
8 | "os"
9 | xpath "path"
10 | "path/filepath"
11 | "strings"
12 |
13 | git "github.com/go-git/go-git/v5"
14 | "github.com/go-git/go-git/v5/plumbing"
15 | "github.com/go-git/go-git/v5/plumbing/filemode"
16 | "github.com/go-git/go-git/v5/plumbing/object"
17 | "github.com/go-git/go-git/v5/storage/memory"
18 | "github.com/pkg/errors"
19 | )
20 |
21 | // File represents a file in the virtual file system: every node is either a regular file
22 | // or a directory. Symlinks are dereferenced in the implementations.
23 | type File struct {
24 | Name string
25 | IsDir bool
26 | }
27 |
28 | // A Filer provides a list of files.
29 | type Filer interface {
30 | // ReadFile returns the contents of a file given it's path.
31 | ReadFile(path string) (content []byte, err error)
32 | // ReadDir lists a directory.
33 | ReadDir(path string) ([]File, error)
34 | // Close frees all the resources allocated by this Filer.
35 | Close()
36 | // PathsAreAlwaysSlash indicates whether the path separator is platform-independent ("/") or
37 | // OS-specific.
38 | PathsAreAlwaysSlash() bool
39 | }
40 |
41 | // FromDirectory returns a Filer that allows accessing over all the files contained in a directory.
42 | func FromDirectory(path string) (Filer, error) {
43 | fi, err := os.Stat(path)
44 | if err != nil {
45 | return nil, errors.Wrapf(err, "cannot create Filer from %s", path)
46 | }
47 | if !fi.IsDir() {
48 | return nil, errors.New("not a directory")
49 | }
50 | return FromFS(os.DirFS(path)), nil
51 | }
52 |
53 | // FromFS returns a Filer that allows accessing all files in the given file system.
54 | func FromFS(fsys fs.FS) Filer {
55 | return fsFiler{fsys}
56 | }
57 |
58 | type fsFiler struct{ fs fs.FS }
59 |
60 | func (fsys fsFiler) ReadFile(name string) ([]byte, error) {
61 | buf, err := fs.ReadFile(fsys.fs, name)
62 | if err != nil {
63 | return nil, errors.Wrapf(err, "cannot read file %s", name)
64 | }
65 | return buf, nil
66 | }
67 |
68 | func (fsys fsFiler) ReadDir(name string) ([]File, error) {
69 | if name == "" {
70 | name = "."
71 | }
72 | entries, err := fs.ReadDir(fsys.fs, name)
73 | if err != nil {
74 | return nil, errors.Wrapf(err, "cannot read directory %s", name)
75 | }
76 | files := make([]File, len(entries))
77 | for i, e := range entries {
78 | files[i] = File{
79 | Name: e.Name(),
80 | IsDir: e.IsDir(),
81 | }
82 | }
83 | return files, nil
84 | }
85 |
86 | func (fsys fsFiler) Close() {}
87 |
88 | func (fsys fsFiler) PathsAreAlwaysSlash() bool {
89 | return true
90 | }
91 |
92 | type gitFiler struct {
93 | root *object.Tree
94 | }
95 |
96 | // FromGitURL returns a Filer that allows to access all the files in a Git repository's default branch given its URL.
97 | // It keeps a shallow single-branch clone of the repository in memory.
98 | func FromGitURL(url string) (Filer, error) {
99 | repo, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{URL: url, Depth: 1})
100 | if err != nil {
101 | return nil, errors.Wrapf(err, "could not clone repo from %s", url)
102 | }
103 | return FromGit(repo, "")
104 | }
105 |
106 | // FromGit returns a Filer that allows accessing all the files in a Git repository
107 | func FromGit(repo *git.Repository, headRef plumbing.ReferenceName) (Filer, error) {
108 | var head *plumbing.Reference
109 | var err error
110 | if headRef == "" {
111 | head, err = repo.Head()
112 | } else {
113 | head, err = repo.Reference(headRef, true)
114 | }
115 | if err != nil {
116 | return nil, errors.Wrap(err, "could not fetch HEAD from repo")
117 | }
118 | commit, err := repo.CommitObject(head.Hash())
119 | if err != nil {
120 | return nil, errors.Wrap(err, "could not fetch commit for HEAD")
121 | }
122 | tree, err := commit.Tree()
123 | if err != nil {
124 | return nil, errors.Wrap(err, "could not fetch root for HEAD commit")
125 | }
126 | return &gitFiler{root: tree}, nil
127 | }
128 |
129 | func (filer gitFiler) ReadFile(path string) ([]byte, error) {
130 | entry, err := filer.root.FindEntry(path)
131 | if err != nil {
132 | return nil, errors.Wrapf(err, "cannot find file %s", path)
133 | }
134 | if entry.Mode == filemode.Symlink {
135 | file, err := filer.root.File(path)
136 | if err != nil {
137 | return nil, errors.Wrapf(err, "cannot find file %s", path)
138 | }
139 | path, err = file.Contents()
140 | if err != nil {
141 | return nil, errors.Wrapf(err, "cannot read file %s", path)
142 | }
143 | }
144 | file, err := filer.root.File(path)
145 | if err != nil {
146 | return nil, errors.Wrapf(err, "cannot read file %s", path)
147 | }
148 | reader, err := file.Reader()
149 | if err != nil {
150 | return nil, errors.Wrapf(err, "cannot read file %s", path)
151 | }
152 | defer func() { err = reader.Close() }()
153 |
154 | buf := new(bytes.Buffer)
155 | if _, err = buf.ReadFrom(reader); err != nil {
156 | return nil, errors.Wrapf(err, "cannot read file %s", path)
157 | }
158 | return buf.Bytes(), err
159 | }
160 |
161 | func (filer *gitFiler) ReadDir(path string) ([]File, error) {
162 | var tree *object.Tree
163 | if path != "" {
164 | var err error
165 | tree, err = filer.root.Tree(path)
166 | if err != nil {
167 | return nil, errors.Wrapf(err, "cannot read directory %s", path)
168 | }
169 | } else {
170 | tree = filer.root
171 | }
172 | result := make([]File, 0, len(tree.Entries))
173 | for _, entry := range tree.Entries {
174 | switch entry.Mode {
175 | case filemode.Dir:
176 | result = append(result, File{
177 | Name: entry.Name,
178 | IsDir: true,
179 | })
180 | case filemode.Regular, filemode.Executable, filemode.Deprecated, filemode.Symlink:
181 | result = append(result, File{
182 | Name: entry.Name,
183 | IsDir: false,
184 | })
185 | }
186 | }
187 | return result, nil
188 | }
189 |
190 | func (filer *gitFiler) Close() {
191 | filer.root = nil
192 | }
193 |
194 | func (filer *gitFiler) PathsAreAlwaysSlash() bool {
195 | return true
196 | }
197 |
198 | type zipNode struct {
199 | children map[string]*zipNode
200 | file *zip.File
201 | }
202 |
203 | type zipFiler struct {
204 | arch *zip.ReadCloser
205 | tree *zipNode
206 | }
207 |
208 | // FromZIP returns a Filer that allows accessing all the files in a ZIP archive given its path.
209 | func FromZIP(path string) (Filer, error) {
210 | arch, err := zip.OpenReader(path)
211 | if err != nil {
212 | return nil, errors.Wrapf(err, "cannot read ZIP archive %s", path)
213 | }
214 | root := &zipNode{children: map[string]*zipNode{}}
215 | for _, f := range arch.File {
216 | path := strings.Split(f.Name, "/") // zip always has "/"
217 | node := root
218 | for _, part := range path {
219 | if part == "" {
220 | continue
221 | }
222 | child := node.children[part]
223 | if child == nil {
224 | child = &zipNode{children: map[string]*zipNode{}}
225 | node.children[part] = child
226 | }
227 | node = child
228 | }
229 | node.file = f
230 | }
231 | return &zipFiler{arch: arch, tree: root}, nil
232 | }
233 |
234 | func (filer *zipFiler) ReadFile(path string) ([]byte, error) {
235 | parts := strings.Split(path, string("/"))
236 | node := filer.tree
237 | for _, part := range parts {
238 | if part == "" {
239 | continue
240 | }
241 | node = node.children[part]
242 | if node == nil {
243 | return nil, errors.Errorf("does not exist: %s", path)
244 | }
245 | }
246 | reader, err := node.file.Open()
247 | if err != nil {
248 | return nil, errors.Wrapf(err, "cannot open %s", path)
249 | }
250 | defer reader.Close()
251 | buffer, err := ioutil.ReadAll(reader)
252 | if err != nil {
253 | return nil, errors.Wrapf(err, "cannot read %s", path)
254 | }
255 | return buffer, nil
256 | }
257 |
258 | func (filer *zipFiler) ReadDir(path string) ([]File, error) {
259 | parts := strings.Split(path, string("/"))
260 | node := filer.tree
261 | for _, part := range parts {
262 | if part == "" {
263 | continue
264 | }
265 | node = node.children[part]
266 | if node == nil {
267 | return nil, errors.Errorf("does not exist: %s", path)
268 | }
269 | }
270 | if path != "" && !node.file.FileInfo().IsDir() {
271 | return nil, errors.Errorf("not a directory: %s", path)
272 | }
273 | result := make([]File, 0, len(node.children))
274 | for name, child := range node.children {
275 | result = append(result, File{
276 | Name: name,
277 | IsDir: child.file.FileInfo().IsDir(),
278 | })
279 | }
280 | return result, nil
281 | }
282 |
283 | func (filer *zipFiler) Close() {
284 | filer.arch.Close()
285 | }
286 |
287 | func (filer *zipFiler) PathsAreAlwaysSlash() bool {
288 | return true
289 | }
290 |
291 | type nestedFiler struct {
292 | origin Filer
293 | offset string
294 | }
295 |
296 | // NestFiler wraps an existing Filer. It prepends the specified prefix to every path.
297 | func NestFiler(filer Filer, prefix string) Filer {
298 | return &nestedFiler{origin: filer, offset: prefix}
299 | }
300 |
301 | func (filer *nestedFiler) ReadFile(path string) ([]byte, error) {
302 | var fullPath string
303 | if filer.origin.PathsAreAlwaysSlash() {
304 | fullPath = xpath.Join(filer.offset, path)
305 | } else {
306 | fullPath = filepath.Join(filer.offset, path)
307 | }
308 | return filer.origin.ReadFile(fullPath)
309 | }
310 |
311 | func (filer *nestedFiler) ReadDir(path string) ([]File, error) {
312 | var fullPath string
313 | if filer.origin.PathsAreAlwaysSlash() {
314 | fullPath = xpath.Join(filer.offset, path)
315 | } else {
316 | fullPath = filepath.Join(filer.offset, path)
317 | }
318 | return filer.origin.ReadDir(fullPath)
319 | }
320 |
321 | func (filer *nestedFiler) Close() {
322 | filer.origin.Close()
323 | }
324 |
325 | func (filer *nestedFiler) PathsAreAlwaysSlash() bool {
326 | return filer.origin.PathsAreAlwaysSlash()
327 | }
328 |
--------------------------------------------------------------------------------
/licensedb/filer/filer_test.go:
--------------------------------------------------------------------------------
1 | package filer
2 |
3 | import (
4 | "sort"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func testFiler(t *testing.T, filer Filer) {
11 | defer filer.Close()
12 | files, err := filer.ReadDir("")
13 | sort.Slice(files, func(i int, j int) bool {
14 | return files[i].Name < files[j].Name
15 | })
16 | assert.Nil(t, err)
17 | assert.Len(t, files, 2)
18 | assert.Equal(t, "one", files[0].Name)
19 | assert.False(t, files[0].IsDir)
20 | assert.Equal(t, "two", files[1].Name)
21 | assert.True(t, files[1].IsDir)
22 | content, err := filer.ReadFile("one")
23 | assert.Nil(t, err)
24 | assert.Equal(t, "hello\n", string(content))
25 | files, err = filer.ReadDir("two")
26 | assert.Nil(t, err)
27 | assert.Len(t, files, 1)
28 | assert.Equal(t, "three", files[0].Name)
29 | assert.False(t, files[0].IsDir)
30 | content, err = filer.ReadFile("two/three")
31 | assert.Nil(t, err)
32 | assert.Equal(t, "world\n", string(content))
33 |
34 | files, err = filer.ReadDir("..")
35 | assert.Nil(t, files)
36 | assert.NotNil(t, err)
37 |
38 | files, err = filer.ReadDir("two/three")
39 | assert.Nil(t, files)
40 | assert.NotNil(t, err)
41 |
42 | content, err = filer.ReadFile("two/four")
43 | assert.Nil(t, content)
44 | assert.NotNil(t, err)
45 | }
46 |
47 | func TestLocalFiler(t *testing.T) {
48 | filer, err := FromDirectory("test_data/local")
49 | assert.Nil(t, err)
50 | testFiler(t, filer)
51 | filer, err = FromDirectory("test_data/local2")
52 | assert.Nil(t, filer)
53 | assert.NotNil(t, err)
54 | filer, err = FromDirectory("test_data/local/one")
55 | assert.Nil(t, filer)
56 | assert.NotNil(t, err)
57 | }
58 |
59 | func TestGitFiler(t *testing.T) {
60 | filer, err := FromGitURL("test_data/git")
61 | assert.Nil(t, err)
62 | testFiler(t, filer)
63 | filer, err = FromGitURL("test_data/local2.git")
64 | assert.Nil(t, filer)
65 | assert.NotNil(t, err)
66 | }
67 |
68 | func TestZipFiler(t *testing.T) {
69 | filer, err := FromZIP("test_data/local.zip")
70 | assert.Nil(t, err)
71 | testFiler(t, filer)
72 | filer, err = FromZIP("test_data/local2.zip")
73 | assert.Nil(t, filer)
74 | assert.NotNil(t, err)
75 | }
76 |
77 | func TestNestedFiler(t *testing.T) {
78 | filer, err := FromDirectory("test_data/local")
79 | assert.Nil(t, err)
80 | filer2 := NestFiler(filer, "two")
81 | defer filer2.Close()
82 | files, err := filer2.ReadDir("")
83 | assert.Nil(t, err)
84 | assert.Len(t, files, 1)
85 | assert.Equal(t, "three", files[0].Name)
86 | assert.False(t, files[0].IsDir)
87 | content, err := filer2.ReadFile("three")
88 | assert.Nil(t, err)
89 | assert.Equal(t, "world\n", string(content))
90 | }
91 |
--------------------------------------------------------------------------------
/licensedb/filer/test_data/git/COMMIT_EDITMSG:
--------------------------------------------------------------------------------
1 | init
2 | # Please enter the commit message for your changes. Lines starting
3 | # with '#' will be ignored, and an empty message aborts the commit.
4 | #
5 | # On branch master
6 | #
7 | # Initial commit
8 | #
9 | # Changes to be committed:
10 | # new file: one
11 | # new file: two/three
12 | #
13 |
--------------------------------------------------------------------------------
/licensedb/filer/test_data/git/HEAD:
--------------------------------------------------------------------------------
1 | ref: refs/heads/master
2 |
--------------------------------------------------------------------------------
/licensedb/filer/test_data/git/config:
--------------------------------------------------------------------------------
1 | [core]
2 | repositoryformatversion = 0
3 | filemode = true
4 | bare = false
5 | logallrefupdates = true
6 |
--------------------------------------------------------------------------------
/licensedb/filer/test_data/git/description:
--------------------------------------------------------------------------------
1 | Unnamed repository; edit this file 'description' to name the repository.
2 |
--------------------------------------------------------------------------------
/licensedb/filer/test_data/git/info/exclude:
--------------------------------------------------------------------------------
1 | # git ls-files --others --exclude-from=.git/info/exclude
2 | # Lines that start with '#' are comments.
3 | # For a project mostly in C, the following would be a good set of
4 | # exclude patterns (uncomment them if you want to use them):
5 | # *.[oa]
6 | # *~
7 |
--------------------------------------------------------------------------------
/licensedb/filer/test_data/git/logs/HEAD:
--------------------------------------------------------------------------------
1 | 0000000000000000000000000000000000000000 334a82b19a7c893d3807ea52ba35ff2170c296cc Vadim Markovtsev