Skip to content

Commit 93ce5a5

Browse files
committed
Initial Release
0 parents  commit 93ce5a5

File tree

5 files changed

+268
-0
lines changed

5 files changed

+268
-0
lines changed

.github/workflows/releases.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Release binary
2+
3+
on:
4+
release:
5+
types:
6+
- created
7+
8+
permissions:
9+
contents: write
10+
packages: write
11+
jobs:
12+
build:
13+
name: Release Go Binary
14+
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
goos:
18+
- linux
19+
- windows
20+
goarch:
21+
- amd64
22+
- arm64
23+
steps:
24+
- name: Check out code into the Go module directory
25+
uses: actions/checkout@v3
26+
- name: Setup go and release
27+
uses: wangyoucao577/go-release-action@v1.49
28+
with:
29+
github_token: ${{ secrets.GITHUB_TOKEN }}
30+
goos: ${{ matrix.goos }}
31+
goarch: ${{ matrix.goarch }}
32+
md5sum: false

README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# About
2+
3+
This is a quick script to produce the available versions of Python in a JSON format.
4+
5+
6+
# Usage
7+
8+
```
9+
Usage of ./py-versions:
10+
-o string
11+
Filename to save JSON output (optional; if not provided, output to console)
12+
-url string
13+
Python FTP Mirror URL (default "https://www.python.org/ftp/python/")
14+
```
15+
16+
17+
## Example Integration
18+
19+
This can be combined with other scripts to alert when a new version is available. This is especially useful when working with embedded and compiled executables.
20+
21+
```bash
22+
#!/bin/bash
23+
24+
# Define constants
25+
ALERT_URL="https://api.example.com/v1/project-key"
26+
TOKEN="tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2"
27+
28+
# Log function
29+
log() {
30+
echo "$(date +"%Y-%m-%d %T") $1"
31+
}
32+
33+
# Get the target version from the user input, defaulting to 3.9.19 if no input is provided
34+
if [ -z "$1" ]; then
35+
target_version="3.9.19"
36+
else
37+
target_version="$1"
38+
fi
39+
40+
# Extract the minor version from the target version
41+
target_minor_version=$(echo "$target_version" | cut -d. -f1,2)
42+
43+
# Get the latest version of Python
44+
version=$(./py-versions | grep -oP "(?<= \"$target_minor_version\":\{\"latest\":\")[^\"]+")
45+
46+
if [[ -z "$version" ]]; then
47+
log "Error: Failed to retrieve the latest version of Python."
48+
exit 1
49+
fi
50+
51+
# Check if the latest version has a different minor version
52+
if [[ "$version" =~ ^$target_minor_version\. ]]; then
53+
# Prepare alert message
54+
alert_message="There is a new version of Python $target_minor_version available: $version"
55+
56+
# Send an alert with the version number using curl
57+
response=$(curl -s -H "Authorization: Bearer $TOKEN" -d "$alert_message" "$ALERT_URL")
58+
59+
# Check if alert was successfully sent
60+
if [[ "$response" != "success" ]]; then
61+
log "Error: Failed to send alert. Response: $response"
62+
exit 1
63+
fi
64+
65+
log "Alert sent successfully: $alert_message"
66+
else
67+
log "No new version of Python $target_minor_version available."
68+
fi
69+
```
70+
71+
The above could then be combined with cron jobs to alert when a new version is available through:
72+
73+
```bash
74+
0 8 * * * /path/to/example-script.sh 3.9.19
75+
```
76+
77+
The `3.9.19` argument is optional and defaults to `3.9.19` if not provided. The API endpoint may trigger an action, such as sending a Slack message or initating a new build with CI/CD pipelines.

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module git.dmoruzzi.com/py-versions
2+
3+
go 1.22.2

go.sum

Whitespace-only changes.

py-versions.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"log"
9+
"net/http"
10+
"os"
11+
"regexp"
12+
)
13+
14+
type Version struct {
15+
Major int `json:"major"`
16+
Minor int `json:"minor"`
17+
Patch int `json:"patch"`
18+
}
19+
20+
func NewVersion(versionStr string) Version {
21+
var major, minor, patch int
22+
fmt.Sscanf(versionStr, "%d.%d.%d", &major, &minor, &patch)
23+
return Version{Major: major, Minor: minor, Patch: patch}
24+
}
25+
26+
func (v Version) String() string {
27+
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
28+
}
29+
30+
func (v Version) LessThan(other Version) bool {
31+
if v.Major != other.Major {
32+
return v.Major < other.Major
33+
}
34+
if v.Minor != other.Minor {
35+
return v.Minor < other.Minor
36+
}
37+
return v.Patch < other.Patch
38+
}
39+
40+
type Versions []Version
41+
42+
func (v Versions) Len() int {
43+
return len(v)
44+
}
45+
46+
func (v Versions) Swap(i, j int) {
47+
v[i], v[j] = v[j], v[i]
48+
}
49+
50+
func (v Versions) Less(i, j int) bool {
51+
return v[i].LessThan(v[j])
52+
}
53+
54+
func fetchHTML(url string) (string, error) {
55+
resp, err := http.Get(url)
56+
if err != nil {
57+
return "", err
58+
}
59+
defer resp.Body.Close()
60+
61+
if resp.StatusCode != http.StatusOK {
62+
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
63+
}
64+
65+
body, err := io.ReadAll(resp.Body)
66+
if err != nil {
67+
return "", err
68+
}
69+
70+
return string(body), nil
71+
}
72+
73+
func extractVersions(html string) map[string]map[string]interface{} {
74+
re := regexp.MustCompile(`href="(\d+\.\d+\.\d+)/"`) // e.g. href="3.9.0/"
75+
matches := re.FindAllStringSubmatch(html, -1)
76+
versions := make(map[string]map[string]interface{})
77+
78+
for _, match := range matches {
79+
if len(match) == 2 {
80+
version := NewVersion(match[1])
81+
pyVersion := fmt.Sprintf("%d.%d", version.Major, version.Minor)
82+
83+
if _, ok := versions[pyVersion]; !ok {
84+
versions[pyVersion] = make(map[string]interface{})
85+
versions[pyVersion]["latest"] = ""
86+
versions[pyVersion]["versions"] = []string{}
87+
}
88+
89+
// Update latest version
90+
latest := versions[pyVersion]["latest"].(string)
91+
if latest == "" || compareVersions(match[1], latest) > 0 {
92+
versions[pyVersion]["latest"] = match[1]
93+
}
94+
95+
// Update versions list
96+
versionsList := versions[pyVersion]["versions"].([]string)
97+
versionsList = append(versionsList, match[1])
98+
versions[pyVersion]["versions"] = versionsList
99+
}
100+
}
101+
102+
return versions
103+
}
104+
105+
func compareVersions(v1, v2 string) int {
106+
version1 := NewVersion(v1)
107+
version2 := NewVersion(v2)
108+
109+
if version1.Major != version2.Major {
110+
return version1.Major - version2.Major
111+
}
112+
if version1.Minor != version2.Minor {
113+
return version1.Minor - version2.Minor
114+
}
115+
return version1.Patch - version2.Patch
116+
}
117+
118+
func writeJSONFile(data interface{}, filename string) error {
119+
if filename == "" {
120+
encoder := json.NewEncoder(os.Stdout)
121+
encoder.SetIndent("", "") // Disable indentation for console output
122+
if err := encoder.Encode(data); err != nil {
123+
return err
124+
}
125+
} else {
126+
file, err := os.Create(filename)
127+
if err != nil {
128+
return err
129+
}
130+
defer file.Close()
131+
132+
encoder := json.NewEncoder(file)
133+
encoder.SetIndent("", " ")
134+
if err := encoder.Encode(data); err != nil {
135+
return err
136+
}
137+
}
138+
return nil
139+
}
140+
141+
func main() {
142+
var url, jsonFilename string
143+
flag.StringVar(&url, "url", "https://www.python.org/ftp/python/", "Python FTP Mirror URL")
144+
flag.StringVar(&jsonFilename, "o", "", "Filename to save JSON output (optional; if not provided, output to console)")
145+
flag.Parse()
146+
147+
html, err := fetchHTML(url)
148+
if err != nil {
149+
log.Fatalf("failed to fetch HTML: %v", err)
150+
}
151+
152+
versions := extractVersions(html)
153+
if err := writeJSONFile(versions, jsonFilename); err != nil {
154+
log.Fatalf("failed to write JSON file: %v", err)
155+
}
156+
}

0 commit comments

Comments
 (0)