doc/Storage | 4 ++++ go.mod | 2 +- hr-pkg.tmpl | 2 ++ list.go | 15 +++++++++++++++ list.tmpl | 2 +- main.go | 2 +- metadata.go | 1 + refresh.go | 22 ++++++++++++++++++++++ diff --git a/doc/Storage b/doc/Storage index 481073cc7941b5043972839d30f6a46b75a71be567fd6e1573337c86701e1ad9..76d21a944375f8cc34fe702e5995409ce664339fb10c4c3029fdaf8c26e2bfca 100644 --- a/doc/Storage +++ b/doc/Storage @@ -9,12 +9,14 @@ | +- public-package-0.1.1.tar.gz.blake2b_256 | +- public-package-0.2.tar.gz | +- public-package-0.2.tar.gz.sha256 | +- public-package-0.2.tar.gz.blake2b_256 + | +- public-package-0.2.tar.gz.reqpy +-- private-package | +- .internal | +- .metadata.rec | +- private-package-0.1.tar.gz | +- private-package-0.1.tar.gz.sha256 | +- private-package-0.1.tar.gz.blake2b_256 + | +- private-package-0.1.tar.gz.reqpy |... Each directory is a normalized package name. When you try to list non @@ -46,3 +48,5 @@ PyPI. You have to create it manually. Each packages release file has mtime set to its upload time. Package's serial is a sum of mtime's of the directory and .metadata.rec (if it exists). + +Optional .reqpy file contains the PEP-345's Requires-Python string. diff --git a/go.mod b/go.mod index 206ccdae71dc10da4a43e320e33bfb842152e071be262befae6268193cb483dd..03aeb3de87e5ac8188d89e428db1eb47aa1bded40f90be34f32126e7dde21b57 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module go.stargrave.org/gocheese/v5 +module go.stargrave.org/gocheese/v6 go 1.24.0 diff --git a/hr-pkg.tmpl b/hr-pkg.tmpl index 16fb5478078761f682b214767e25c98a2d05dd74bd73468ae15130cb91a48c7b..389f61859f24aeb5ed679c22badc5f1d39245274512ec1d1f5b5af7e1f7105ed 100644 --- a/hr-pkg.tmpl +++ b/hr-pkg.tmpl @@ -58,6 +58,7 @@ + @@ -66,6 +67,7 @@ {{range .Releases}}{{if .Size}}
Filename VersionPython Uploaded Size Digests
{{.Filename}} {{.Version}} + {{.ReqPy}} {{.UploadTimeISO8601}} {{.Size}}
    {{range $a, $d := .Digests}} diff --git a/list.go b/list.go index 2da3dae76c6f4ada6a51d398f38477034963c72a8a287fbbabbf178325d3e4dd..d55c27561bdad1162427af4312659fae7b79028d9e46faa8a02026dc3d4d1032 100644 --- a/list.go +++ b/list.go @@ -182,6 +182,21 @@ } release.Digests[algo] = hex.EncodeToString(digest) } } + for fn := range files { + if !strings.HasSuffix(fn, ReqPyExt) { + continue + } + delete(files, fn) + var reqPy []byte + reqPy, err = os.ReadFile(filepath.Join(dirPath, fn)) + if err != nil { + return 0, nil, err + } + fnClean := strings.TrimSuffix(fn, ReqPyExt) + if release := releaseFiles[fnClean]; release != nil { + release.ReqPy = string(reqPy) + } + } releases := make([]*PkgReleaseInfo, 0, len(releaseFiles)) for _, release := range releaseFiles { releases = append(releases, release) diff --git a/list.tmpl b/list.tmpl index 472c3145eafbecca892480196f9ad0472ce8ddfde16988efd717f247c105576a..6a6fd6d3ed740a77054b0dba9e05658e5507fdde28bb0fa66c5f30c4d9b6ee49 100644 --- a/list.tmpl +++ b/list.tmpl @@ -6,7 +6,7 @@ Links for {{.PkgName}} {{$Refresh := .RefreshURLPath}}{{$PkgName := .PkgName}}{{range .Releases}} {{.Filename}}
    + #{{range $a, $d := .Digests}}{{$a}}={{$d}}{{end -}}"{{with .ReqPy}} data-requires-python="{{.}}"{{end}}>{{.Filename}}
    {{- end}} diff --git a/main.go b/main.go index 3023f1937242127726f24f769f0fc9afcd8494565b3b7aabd17c3b54c0e19bad..edf9f9c47ea497cd771b6acc5661b12a86a1381e3385c959e38985c572e94114 100644 --- a/main.go +++ b/main.go @@ -42,7 +42,7 @@ "golang.org/x/net/netutil" ) const ( - Version = "5.0.0" + Version = "6.0.0" UserAgent = "GoCheese/" + Version ) diff --git a/metadata.go b/metadata.go index 4b71edd3cd4e671da8d1b66ed4de9eca3653a7d53defda6bed753b72068c079b..333c2dfddd001d4610506095da25dbe79d4e8180da8d24138cfe17540577afcc 100644 --- a/metadata.go +++ b/metadata.go @@ -141,6 +141,7 @@ Digests map[string]string `json:"digests"` Filename string `json:"filename"` Version string `json:"version"` UploadTimeISO8601 string `json:"upload_time_iso_8601"` + ReqPy string `json:"requires_python"` Size int64 `json:"size"` HasSig bool `json:"has_sig"` } diff --git a/refresh.go b/refresh.go index 24d5793ed60296fef5e8294631e0b46ff23571badb22a16f59aa7f1b086e8f2e..3ae8e5e221b2ac207b4079ea721e7c1fb3e7ffc611da2b3329dc36132a24e163 100644 --- a/refresh.go +++ b/refresh.go @@ -25,6 +25,7 @@ "encoding/hex" "encoding/json" "errors" "hash" + "html" "io" "io/fs" "log" @@ -46,10 +47,12 @@ HashAlgoSHA256 = "sha256" HashAlgoSHA512 = "sha512" HashAlgoMD5 = "md5" InternalFlag = ".internal" + ReqPyExt = ".reqpy" ) var ( PkgPyPI = regexp.MustCompile(`^.*]*>(.+).*$`) + DataRequiresPy = regexp.MustCompile(`data-requires-python="([^"]+)"`) PyPIURLParsed *url.URL PyPIHTTPTransport http.Transport KnownHashAlgos []string = []string{ @@ -339,6 +342,11 @@ log.Println("error", r.RemoteAddr, "refresh", uri, err) http.Error(w, err.Error(), http.StatusBadGateway) return false } + var reqPy string + submatches = DataRequiresPy.FindStringSubmatch(string(lineRaw)) + if len(submatches) != 0 { + reqPy = html.UnescapeString(submatches[1]) + } var hashAlgo string var hasherNew func() hash.Hash @@ -568,6 +576,20 @@ log.Println(r.RemoteAddr, "pypi", filename, "touch") if err = os.Chtimes(path, mtime, mtime); err != nil { log.Println("error", r.RemoteAddr, "pypi", filename, err) http.Error(w, err.Error(), http.StatusInternalServerError) + } + } + } + + if reqPy != "" { + reqPyPath := path + ReqPyExt + got, _ := os.ReadFile(reqPyPath) + if reqPy != string(got) { + if err = WriteFileSync( + dirPath, reqPyPath, []byte(reqPy), mtime, + ); err != nil { + log.Println("error", r.RemoteAddr, "pypi", path, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return false } } }