1+ # --*- coding: utf-8 --*-
2+ """Fetch all Elixir versions from GitHub API and save to a local file."""
3+
14import json
5+ import re
26import requests
37from packaging import version
48
9+
510# fetch version: -> https://api.github.com/repos/elixir-lang/elixir/tags?per_page=100&sort=pushed
611# github api has rate limt
712# prefer use local version file
813def update_all_version_from_github_api ():
14+ """Fetch all Elixir version tags from GitHub API and save to local JSON file.
15+
16+ Makes paginated requests to GitHub API to retrieve all available Elixir version tags.
17+ The fetched data is saved to 'elixir_versions_from_github_api.json' for local use
18+ to avoid hitting GitHub API rate limits on subsequent runs.
19+
20+ Note:
21+ GitHub API has rate limits, so prefer using the local version file when possible.
22+ """
923 all_version = []
10- for page in range (1 ,10 ):
11- url = f"https://api.github.com/repos/elixir-lang/elixir/tags?per_page=100&sort=pushed&page={ page } "
12- response = requests .get (url )
24+ for page in range (1 , 10 ):
25+ url = (
26+ f"https://api.github.com/repos/elixir-lang/elixir/tags"
27+ f"?per_page=100&sort=pushed&page={ page } "
28+ )
29+ response = requests .get (url , timeout = 30 )
1330 if response .status_code != 200 :
1431 print ("Failed to fetch data from github api" )
1532 return
@@ -18,45 +35,93 @@ def update_all_version_from_github_api():
1835 data = response .json ()
1936 all_version = all_version + data
2037
21-
22- with open ("elixir_versions_from_github_api.json" , 'w' , encoding = "utf-8" ) as file :
38+ with open ("elixir_versions_from_github_api.json" , "w" , encoding = "utf-8" ) as file :
2339 json .dump (all_version , file , indent = 4 )
2440
41+
2542def get_all_version ():
43+ """Extract all Elixir version numbers from GitHub API data.
44+
45+ Reads the local JSON file containing GitHub API response data and extracts
46+ version numbers from tarball URLs that contain 'refs/tags/v' pattern.
47+
48+ Returns:
49+ set: A set of version strings extracted from the GitHub API data.
50+ """
2651 version_set = set ()
27- with open ("elixir_versions_from_github_api.json" , 'r' , encoding = "utf-8" ) as file :
52+ with open ("elixir_versions_from_github_api.json" , "r" , encoding = "utf-8" ) as file :
2853 data = json .load (file )
2954 for item in data :
3055 if "refs/tags/" not in item ["tarball_url" ]:
3156 continue
3257 split_info = item ["tarball_url" ].split ("refs/tags/v" )
3358 if len (split_info ) > 1 :
34- version = split_info [1 ]
35- version_set .add (version )
59+ version_str = split_info [1 ]
60+ version_set .add (version_str )
3661 return version_set
3762
38- def parse_version (ver ):
63+
64+ def parse_version (version_string ):
65+ """Parse a version string and return parsing result with metadata.
66+
67+ Attempts to parse a version string using the packaging library's version parser.
68+ Returns a tuple containing the parsed version (or original string if invalid),
69+ a boolean indicating parsing success, and the original version string.
70+
71+ Args:
72+ version_string (str): The version string to parse.
73+
74+ Returns:
75+ tuple: A 3-tuple containing:
76+ - Parsed version object (or original string if invalid)
77+ - Boolean indicating if parsing was successful
78+ - Original version string
79+ """
3980 try :
40- return version .parse (ver ), True
81+ return version .parse (version_string ), True , version_string
4182 except version .InvalidVersion :
42- print (f"Invalid version: { ver } " )
43- return ver , False
83+ print (f"Invalid version: { version_string } " )
84+ return version_string , False , version_string
85+
86+
87+ def custom_version_sort_key (ver_tuple ):
88+ """Custom sorting key, prioritize semantic versions, sort invalid versions by string"""
89+ parsed_ver , is_valid_ver , original_ver = ver_tuple
90+ if is_valid_ver :
91+ # Valid semantic version, return (0, parsed_version) to ensure it comes first
92+ return (0 , parsed_ver )
93+
94+ # Invalid version, try to extract numeric part for sorting
95+ # Try to match version pattern, e.g. "1.18-latest" -> "1.18"
96+ match = re .match (r"^(\d+(?:\.\d+)*)" , original_ver )
97+ if match :
98+ try :
99+ # Extract numeric part and parse
100+ numeric_part = match .group (1 )
101+ parsed_numeric = version .parse (numeric_part )
102+ return (
103+ 1 ,
104+ parsed_numeric ,
105+ original_ver ,
106+ ) # After valid versions, but sorted by numeric part
107+ except version .InvalidVersion :
108+ pass
109+ # Completely unparseable versions, sort by string
110+ return (2 , original_ver )
111+
44112
45113if __name__ == "__main__" :
46114 update_all_version_from_github_api ()
47115 versions = list (get_all_version ())
48- valid_versions = [] # semantic version
49- invalid_versions = []
116+ version_tuples = []
50117 for v in versions :
51- ver , is_valid = parse_version (v )
52- if is_valid :
53- valid_versions . append ( ver )
54- continue
55- invalid_versions . append ( ver )
118+ ver , is_valid , original = parse_version (v )
119+ version_tuples . append (( ver , is_valid , original ))
120+
121+ # Sort using custom sorting key
122+ sorted_versions = sorted ( version_tuples , key = custom_version_sort_key , reverse = True )
56123
57- versions = sorted (valid_versions , reverse = True )
58124 with open ("versions.txt" , "w" , encoding = "utf-8" ) as file :
59- for v in versions :
60- file .write (str (v ) + '\n ' )
61- for v in invalid_versions :
62- file .write (v + '\n ' )
125+ for v_tuple in sorted_versions :
126+ _ , _ , original = v_tuple
127+ file .write (original + "\n " )
0 commit comments