44import requests
55import os
66import hashlib
7+ import difflib
78import asyncio
89import sys
910import glob
@@ -34,7 +35,7 @@ def script():
3435
3536
3637@script .command ()
37- @click .option ('--url' , default = None , help = 'Set the url' )
38+ @click .option ('--url' , default = None , help = 'Set the server url' )
3839@click .option ('--username' , default = None , help = 'Set the username' )
3940@click .option ('--working_dir' , default = None , help = 'Set the working directory where scripts are stored to' )
4041def configure (url : str , username : str , working_dir : str ):
@@ -43,15 +44,32 @@ def configure(url: str, username: str, working_dir: str):
4344 Only flags that are passed are written; the rest of the
4445 `viur_scriptor_config.json` keeps its previous values.
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 ()
@@ -115,7 +133,8 @@ def check_session(ctx: click.Context):
115133 get_modules ()
116134
117135@script .command ()
118- @click .option ('--force' , default = False , help = 'Force replace files from server in local working directory' )
136+ @click .option ('--force' , default = False , is_flag = True ,
137+ help = 'Overwrite local files without asking for confirmation' )
119138@click .pass_context
120139def pull (ctx : click .Context , force : bool ):
121140 """Download all server-side Scriptor scripts into the local working_dir.
@@ -131,36 +150,74 @@ async def main():
131150 tree = await modules .get_module ("script" )
132151 working_dir = scriptor_config .get ("working_dir" )
133152
153+ stats = {"new" : 0 , "updated" : 0 , "skipped" : 0 , "unchanged" : 0 , "dirs" : 0 }
154+
134155 async def process_entry (entry : dict , is_node : bool ):
135156 _path = os .path .join (working_dir , entry ["path" ].lstrip ("/" ))
136157
137158 if is_node :
138159 if not os .path .exists (_path ):
160+ click .echo (click .style (f" mkdir { entry ['path' ]} " , fg = "blue" ))
139161 os .makedirs (_path )
162+ stats ["dirs" ] += 1
140163 else :
164+ click .echo (f" check { entry ['path' ]} " , nl = False )
165+
141166 def create_file ():
142167 with open (_path , "a+" ) as f :
143168 f .write (entry ["script" ])
144169
145- click .echo (f"Pull { _path } " )
146-
147170 if os .path .exists (_path ):
148171 if force :
172+ with open (_path , "r" ) as f :
173+ changed = f .read ().splitlines () != entry ["script" ].splitlines ()
149174 os .remove (_path )
150175 create_file ()
176+ if changed :
177+ click .echo (click .style (" [updated]" , fg = "yellow" ))
178+ stats ["updated" ] += 1
179+ else :
180+ click .echo (click .style (" [ok]" , fg = "green" ))
181+ stats ["unchanged" ] += 1
151182 else :
152183 with open (_path , "r" ) as f :
153- if hashlib .sha256 (entry ["script" ].encode ()).digest () \
154- != hashlib .sha256 (f .read ().encode ()).digest ():
155- try :
156- if click .confirm (f"There is a difference with { entry ['path' ]} . Overwrite?" ):
157- os .remove (_path )
158- create_file ()
159- except click .exceptions .Abort :
160- click .echo ("\n Skipping..." )
161-
184+ local_content = f .read ()
185+ remote_content = entry ["script" ]
186+ diff = list (difflib .unified_diff (
187+ remote_content .splitlines (),
188+ local_content .splitlines (),
189+ fromfile = f"server/{ entry ['path' ].lstrip ('/' )} " ,
190+ tofile = f"local/{ entry ['path' ].lstrip ('/' )} " ,
191+ lineterm = "" ,
192+ ))
193+ if diff :
194+ click .echo (click .style (" [diff]" , fg = "yellow" ))
195+ for line in diff :
196+ if line .startswith ("+++" ) or line .startswith ("---" ):
197+ click .echo (click .style (line , bold = True ))
198+ elif line .startswith ("@@" ):
199+ click .echo (click .style (line , fg = "cyan" ))
200+ elif line .startswith ("+" ):
201+ click .echo (click .style (line , fg = "green" ))
202+ elif line .startswith ("-" ):
203+ click .echo (click .style (line , fg = "red" ))
204+ else :
205+ click .echo (line )
206+ if click .confirm (f"Overwrite local { entry ['path' ]} with remote version?" ):
207+ os .remove (_path )
208+ create_file ()
209+ stats ["updated" ] += 1
210+ else :
211+ stats ["skipped" ] += 1
212+ else :
213+ click .echo (click .style (" [ok]" , fg = "green" ))
214+ stats ["unchanged" ] += 1
162215 else :
216+ click .echo (click .style (" [new]" , fg = "green" ))
163217 create_file ()
218+ stats ["new" ] += 1
219+
220+ click .echo ("Fetching scripts from server..." )
164221
165222 # Process nodes first
166223 async for node in tree .list (skel_type = "node" ):
@@ -170,6 +227,13 @@ def create_file():
170227 async for leaf in tree .list (skel_type = "leaf" ):
171228 await process_entry (leaf , False )
172229
230+ click .echo ("" )
231+ click .echo (click .style ("Summary:" , bold = True ))
232+ click .echo (f" new: { stats ['new' ]} " )
233+ click .echo (f" updated: { stats ['updated' ]} " )
234+ click .echo (f" skipped: { stats ['skipped' ]} " )
235+ click .echo (f" unchanged: { stats ['unchanged' ]} " )
236+
173237 asyncio .run (main ())
174238
175239
0 commit comments