44import requests
55import os
66import hashlib
7+ import difflib
78import asyncio
89import sys
910import glob
@@ -23,7 +24,7 @@ def get_modules():
2324 global _modules
2425 if _modules is None :
2526 _modules = Modules (scriptor_config ["base_url" ], cookies = scriptor_config ["cookies" ])
26- asyncio .new_event_loop (). run_until_complete (_modules .init ())
27+ asyncio .run (_modules .init ())
2728
2829 return _modules
2930
@@ -36,22 +37,39 @@ def script():
3637
3738
3839@script .command ()
39- @click .option ('--url' , default = None , help = 'Set the url' )
40+ @click .option ('--url' , default = None , help = 'Set the server url' )
4041@click .option ('--username' , default = None , help = 'Set the username' )
4142@click .option ('--working_dir' , default = None , help = 'Set the working directory where scripts are stored to' )
4243def configure (url : str , username : str , working_dir : str ):
4344 """
4445 Manage configuration settings.
4546 """
47+ if not any ([url , username , working_dir ]):
48+ click .echo ("No parameters provided. Use one or more of the following options:" )
49+ click .echo (" --url Set the server URL" )
50+ click .echo (" --username Set the username" )
51+ click .echo (" --working_dir Set the working directory where scripts are stored to" )
52+ return
53+
54+ changed = []
4655
4756 if url :
4857 scriptor_config ["base_url" ] = url
58+ changed .append (f"url = { url } " )
4959
5060 if username :
5161 scriptor_config ["username" ] = username
62+ changed .append (f"username = { username } " )
5263
5364 if working_dir :
5465 scriptor_config ["working_dir" ] = working_dir .replace ("\\ " , "/" )
66+ changed .append (f"working_dir = { working_dir } " )
67+
68+ click .echo ("Configuration updated:" )
69+ for entry in changed :
70+ click .echo (f" { entry } " )
71+
72+ scriptor_config .save ()
5573
5674
5775@script .command ()
@@ -111,7 +129,8 @@ def check_session(ctx: click.Context):
111129 get_modules ()
112130
113131@script .command ()
114- @click .option ('--force' , default = False , help = 'Force replace files from server in local working directory' )
132+ @click .option ('--force' , default = False , is_flag = True ,
133+ help = 'Overwrite local files without asking for confirmation' )
115134@click .pass_context
116135def pull (ctx : click .Context , force : bool ):
117136 """
@@ -125,33 +144,74 @@ async def main():
125144 tree = await modules .get_module ("script" )
126145 working_dir = scriptor_config .get ("working_dir" )
127146
147+ stats = {"new" : 0 , "updated" : 0 , "skipped" : 0 , "unchanged" : 0 , "dirs" : 0 }
148+
128149 async def process_entry (entry : dict , is_node : bool ):
129- _path = os .path .join (working_dir , entry ["path" ])
150+ _path = os .path .join (working_dir , entry ["path" ]. lstrip ( "/" ) )
130151
131152 if is_node :
132153 if not os .path .exists (_path ):
154+ click .echo (click .style (f" mkdir { entry ['path' ]} " , fg = "blue" ))
133155 os .makedirs (_path )
156+ stats ["dirs" ] += 1
134157 else :
158+ click .echo (f" check { entry ['path' ]} " , nl = False )
159+
135160 def create_file ():
136161 with open (_path , "a+" ) as f :
137162 f .write (entry ["script" ])
138163
139- click .echo (f"Pull { _path } " )
140-
141164 if os .path .exists (_path ):
142165 if force :
166+ with open (_path , "r" ) as f :
167+ changed = f .read ().splitlines () != entry ["script" ].splitlines ()
143168 os .remove (_path )
144169 create_file ()
170+ if changed :
171+ click .echo (click .style (" [updated]" , fg = "yellow" ))
172+ stats ["updated" ] += 1
173+ else :
174+ click .echo (click .style (" [ok]" , fg = "green" ))
175+ stats ["unchanged" ] += 1
145176 else :
146177 with open (_path , "r" ) as f :
147- if hashlib .sha256 (entry ["script" ].encode ()).digest () \
148- != hashlib .sha256 (f .read ().encode ()).digest ():
149- if click .confirm (f"There is a difference with { entry ['path' ]} . Overwrite?" ):
150- os .remove (_path )
151- create_file ()
152-
178+ local_content = f .read ()
179+ remote_content = entry ["script" ]
180+ diff = list (difflib .unified_diff (
181+ remote_content .splitlines (),
182+ local_content .splitlines (),
183+ fromfile = f"server/{ entry ['path' ].lstrip ('/' )} " ,
184+ tofile = f"local/{ entry ['path' ].lstrip ('/' )} " ,
185+ lineterm = "" ,
186+ ))
187+ if diff :
188+ click .echo (click .style (" [diff]" , fg = "yellow" ))
189+ for line in diff :
190+ if line .startswith ("+++" ) or line .startswith ("---" ):
191+ click .echo (click .style (line , bold = True ))
192+ elif line .startswith ("@@" ):
193+ click .echo (click .style (line , fg = "cyan" ))
194+ elif line .startswith ("+" ):
195+ click .echo (click .style (line , fg = "green" ))
196+ elif line .startswith ("-" ):
197+ click .echo (click .style (line , fg = "red" ))
198+ else :
199+ click .echo (line )
200+ if click .confirm (f"Overwrite local { entry ['path' ]} with remote version?" ):
201+ os .remove (_path )
202+ create_file ()
203+ stats ["updated" ] += 1
204+ else :
205+ stats ["skipped" ] += 1
206+ else :
207+ click .echo (click .style (" [ok]" , fg = "green" ))
208+ stats ["unchanged" ] += 1
153209 else :
210+ click .echo (click .style (" [new]" , fg = "green" ))
154211 create_file ()
212+ stats ["new" ] += 1
213+
214+ click .echo ("Fetching scripts from server..." )
155215
156216 # Process nodes first
157217 async for node in tree .list (skel_type = "node" ):
@@ -161,7 +221,14 @@ def create_file():
161221 async for leaf in tree .list (skel_type = "leaf" ):
162222 await process_entry (leaf , False )
163223
164- asyncio .new_event_loop ().run_until_complete (main ())
224+ click .echo ("" )
225+ click .echo (click .style ("Summary:" , bold = True ))
226+ click .echo (f" new: { stats ['new' ]} " )
227+ click .echo (f" updated: { stats ['updated' ]} " )
228+ click .echo (f" skipped: { stats ['skipped' ]} " )
229+ click .echo (f" unchanged: { stats ['unchanged' ]} " )
230+
231+ asyncio .run (main ())
165232
166233
167234@script .command ()
@@ -305,21 +372,22 @@ def watch_loop():
305372
306373 def on_modified (event ):
307374 try :
308- # check for tmp file
309375 if event .src_path .endswith ("~" ):
310376 return
377+ if not os .path .exists (event .src_path ):
378+ return
311379 if event .src_path not in modified_files :
312380 modified_files [event .src_path ] = os .path .getmtime (event .src_path )
313381 elif os .path .getmtime (event .src_path ) == modified_files [event .src_path ]:
314382 return
315383 modified_files [event .src_path ] = os .path .getmtime (event .src_path )
316- asyncio .new_event_loop (). run_until_complete (main (event .src_path ))
384+ asyncio .run (main (event .src_path ))
317385 except Exception as e :
318386 import traceback
319387 print (f"Error: on file { event .src_path } { e } " )
320388 traceback .print_exc ()
321389
322- regexes = [r".*\.py" ]
390+ regexes = [r".*\.py$ " ]
323391 ignore_regexes = []
324392 ignore_directories = True
325393 case_sensitive = False
@@ -341,9 +409,9 @@ def on_modified(event):
341409 observer .stop ()
342410 observer .join ()
343411
344- asyncio . new_event_loop (). run_until_complete ( watch_loop () )
412+ watch_loop ()
345413 return
346- asyncio .new_event_loop (). run_until_complete (main ())
414+ asyncio .run (main ())
347415
348416
349417@script .command ()
@@ -376,4 +444,4 @@ async def main():
376444
377445 await module .main ()
378446
379- asyncio .new_event_loop (). run_until_complete (main ())
447+ asyncio .run (main ())
0 commit comments