1+ import os
2+ from os .path import join
3+ from functools import wraps
4+ from color import error , warning , success , Colors
5+ from difflib import SequenceMatcher
6+ import sys
7+
8+ shared = join (".." , "shared" )
9+ version_file = join ("shared" , "project_version.txt" )
10+
11+ class App :
12+ def __init__ (self ):
13+ self .procedures = {}
14+
15+
16+ def command (self , * cli_args ):
17+ def util (f ):
18+ @wraps (f )
19+ def g (* args , ** kwargs ):
20+ f (self , * args , ** kwargs )
21+ self .procedures [g .__name__ ] = (g , cli_args )
22+ return g
23+ return util
24+
25+ def run (self , args ):
26+ if len (args ) == 0 :
27+ error (f"No argument is passed." )
28+ return
29+ if len (args ) > 2 :
30+ error (f"No more than two args is required." )
31+ return
32+ command , * arg = args
33+ try :
34+ procedure , expected_args = self .get_command (command )
35+ except ValueError :
36+ return
37+
38+ if not arg :
39+ try :
40+ procedure ()
41+ except TypeError as e :
42+ raise e
43+ error (f"{ command !r} command requires an argument but none were given." )
44+ return
45+
46+ arg = arg [0 ]
47+ if arg in expected_args :
48+ procedure (arg )
49+ else :
50+ if not expected_args :
51+ error (f"{ command !r} command takes no argument." )
52+ return
53+ match = self .find_similar (arg , expected_args )
54+ error (f"{ command !r} command have no argument named { arg !r} ." )
55+ if match is not None :
56+ error (f"Did you perhaps meant { match !r} ?" )
57+
58+ @classmethod
59+ def find_similar (cls , name , pool ):
60+ matcher = SequenceMatcher ()
61+ matcher .set_seq1 (name .casefold ())
62+ ratios = {}
63+ for p in pool :
64+ matcher .set_seq2 (p .casefold ())
65+ ratio = matcher .ratio ()
66+ ratios [ratio ] = p
67+ m = max (ratios )
68+ if m > 0.6 :
69+ return ratios [m ]
70+ else :
71+ return None
72+
73+ def get_command (self , name ):
74+ try :
75+ return self .procedures [name ]
76+ except KeyError :
77+ match = self .find_similar (name , self .procedures )
78+ error (f"No command named { name !r} ." )
79+ if match is not None :
80+ error (f"Did you perhaps meant { match !r} ?" )
81+ raise ValueError ()
82+
83+ app = App ()
84+
85+ @app .command ("release" )
86+ def build (app , job = None ):
87+ "Builds the docs, moves them to where they are needed and upgrades the version."
88+ import subprocess
89+ from io import StringIO
90+
91+ def call (cmd , jobname ):
92+ err = subprocess .PIPE
93+ out = subprocess .PIPE
94+ p = subprocess .run (cmd , shell = True , stdout = out , stderr = err )
95+ error (p .stderr .decode (), end = "" )
96+ if not p .stderr :
97+ success (f"Built { jobname } ." )
98+ else :
99+ warning (f"There were errors while building { jobname } ." )
100+
101+ print ("Starting the build process." )
102+ p = call ("make html" , "HTML" )
103+ p = call ("make singlehtml" , "HTML (single file)" )
104+ p = call ("make latexpdf" , "PDF" )
105+ p = call ("make epub" , "EPUB" )
106+
107+ if job is None :
108+ return
109+
110+ if job == "release" :
111+ app .version ("patch" )
112+ import move_documents
113+
114+
115+
116+ @app .command ()
117+ def check_links (app ):
118+ pass
119+
120+ @app .command ("major" , "minor" , "patch" , "downgrade" )
121+ def version (app , field ):
122+ "Upgrades or downgrades the project version with respect to the specified argument."
123+ with open (version_file , "r" ) as f :
124+ versions = list (map (lambda x : x [:- 1 ] if x .endswith ("\n " ) else x , f ))
125+ if field == "downgrade" :
126+ if len (versions ) == 1 :
127+ error ("Can't downgrade when there is a single recorded version." )
128+ return
129+ versions .pop ()
130+ success (f"Downgraded the version to { versions [- 1 ]} ." )
131+ else :
132+ version = list (map (int , versions [- 1 ].split ("." )))
133+ index = ("major" , "minor" , "patch" ).index (field )
134+
135+ version [index ] += 1
136+ for i in range (index + 1 , 3 ):
137+ version [i ] = 0
138+ version = "." .join (map (str , version ))
139+ versions .append (version )
140+ success (f"Upgraded the version to { version } ." )
141+
142+ with open (version_file , "w" ) as f :
143+ f .write ("\n " .join (versions ))
144+
145+ @app .command (* app .procedures , "help" )
146+ def help (app , method = None ):
147+ "Displays a help message for the given argument."
148+ if method is None :
149+ print ("Possible arguments for the application:\n " )
150+ for i in app .procedures :
151+ print (f"- { i :<20} " + app .procedures [i ][0 ].__doc__ )
152+ else :
153+ print (f"{ method } :" , app .procedures [method ][0 ].__doc__ )
154+
155+ if __name__ == "__main__" :
156+ import sys
157+ args = sys .argv [1 :]
158+ app .run (args )
0 commit comments