99from warnings import warn
1010
1111import click
12+ from config import Config
1213
1314from bak .data import bakfile , bak_db
1415
2223 config_dir = os .environ ["XDG_CONFIG_HOME" ]
2324except KeyError :
2425 config_dir = os .path .expanduser ("~/.config" )
25- bak_dir = os .path .join (data_dir , "bak" , "bakfiles" )
26- bak_db_loc = os .path .join (data_dir , "bak" , "bak.db" )
26+
27+ config_file = os .path .join (config_dir , 'bak.cfg' )
28+ cfg = Config (config_file )
29+
30+ bak_dir = cfg ['bakfile_location' ] or os .path .join (data_dir ,
31+ "bak" , "bakfiles" )
32+ bak_db_loc = cfg ['bak_database_location' ] or \
33+ os .path .join (data_dir , "bak" , "bak.db" )
2734
2835if not os .path .exists (bak_dir ):
2936 os .makedirs (bak_dir )
@@ -38,14 +45,14 @@ def expandpath(i_path):
3845def _assemble_bakfile (filename ):
3946 time_now = datetime .now ()
4047 splitname = os .path .split (expandpath (filename ))
41- bakfile_name = "" .join (["." .join (i [1 :].replace ("/" , "-" ) for i in splitname [:- 1 ]) +
48+ bakfile_name = "" .join (["." .join (i [1 :].replace ("/" , "-" )
49+ for i in splitname [:- 1 ]) +
4250 '-' +
43- splitname [- 1 ], # [os.path.split(filename)[-1],
51+ splitname [- 1 ],
4452 "." ,
4553 '-' .join (str (
4654 time_now .timestamp ()).split ('.' )),
4755 ".bak" ]).replace (" " , "-" )
48- # TODO #26 get bakfile directory from config
4956 bakfile_path = os .path .join (bak_dir , bakfile_name )
5057
5158 new_bak_entry = bakfile .BakFile (os .path .basename (filename ),
@@ -56,53 +63,71 @@ def _assemble_bakfile(filename):
5663 return new_bak_entry
5764
5865
59- default_select_prompt = ("Enter a number, or: (V)iew (C)ancel" , 'c' )
66+ default_select_prompt = ("Enter a number, or: (V)iew (D)iff ( C)ancel" , 'c' )
6067
6168
62- def _get_bakfile_entry (filename , select_prompt = default_select_prompt , err = False ):
69+ def _get_bakfile_entry (filename ,
70+ select_prompt = default_select_prompt ,
71+ err = False ):
6372 entries = db_handler .get_bakfile_entries (expandpath (filename ))
6473 if not entries or len (entries ) == 0 :
6574 return None
66- return entries [0 ] if len (entries ) == 1 else _do_select_bakfile (entries , select_prompt , err )
75+ # If there's only one bakfile corresponding to filename, return that.
76+ # If there's more than one, disambiguate.
77+ return entries [0 ] if len (entries ) == 1 else \
78+ _do_select_bakfile (entries , select_prompt , err )
6779
6880
6981def _do_select_bakfile (bakfiles : List [bakfile .BakFile ],
7082 select_prompt = default_select_prompt ,
7183 err = False ):
7284 click .echo (
73- f"Found { len (bakfiles )} bakfiles for file: { bakfiles [0 ].orig_abspath } " , err = err )
85+ f"Found { len (bakfiles )} bakfiles for file: { bakfiles [0 ].orig_abspath } " ,
86+ err = err )
7487 click .echo ("Please select from the following: " , err = err )
7588 _range = range (len (bakfiles ))
7689 for i in _range :
7790 click .echo (
78- f"{ i + 1 } : .bakfile last modified at { bakfiles [i ].date_modified } " , err = err )
79- choice = click .prompt (* select_prompt , err = err )
80- # TODO add diff and print as choices here
81- # "Enter a number, or: (V)iew (C)ancel", default='c').lower()
82- if choice != "c" :
83- view = False
84- try :
85- if choice == "v" :
86- idx = int (click .prompt ("View which .bakfile?" , err = err )) - 1
87- # idx = int(click.prompt("View which .bakfile? (#)")) - 1
88- view = True
89- else :
90- idx = int (choice ) - 1
91- if idx not in _range :
92- click .echo ("Invalid selection. Aborting." , err = err )
93- return None
94- else :
95- if view :
96- bak_print_cmd (bakfiles [idx ])
97- return
98- return bakfiles [idx ]
99- except (ValueError , TypeError ) as e :
100- warn (e )
101- click .echo ("Invalid input. Aborting." , err = err )
91+ f"{ i + 1 } : .bakfile last modified at { bakfiles [i ].date_modified } " ,
92+ err = err )
93+
94+ def get_choice ():
95+ return click .prompt (* select_prompt , err = err ).lower ()
96+ choice = get_choice ()
97+
98+ while True :
99+ if choice == "c" :
100+ click .echo ("Cancelled." , err = err )
102101 return None
103- else :
104- click .echo ("Aborting." , err = err )
105- return None
102+ else :
103+ view = False
104+ try :
105+ if choice == "v" :
106+ idx = int (click .prompt (
107+ "View which .bakfile?" , err = err )) - 1
108+ view = True
109+ elif choice == "d" :
110+ idx = int (click .prompt (
111+ "Diff which .bakfile?" , err = err )) - 1
112+ bak_diff_cmd (bakfiles [idx ])
113+ choice = get_choice ()
114+ continue
115+ else :
116+ idx = int (choice ) - 1
117+ if idx not in _range :
118+ click .echo ("Invalid selection. Aborting." , err = err )
119+ return None
120+ elif view :
121+ bak_print_cmd (bakfiles [idx ])
122+ choice = get_choice ()
123+ continue
124+ else :
125+ return bakfiles [idx ]
126+ except (ValueError , TypeError ) as e :
127+ warn (e )
128+ click .echo ("Invalid input. Aborting." , err = err )
129+ return None
130+ get_choice ()
106131
107132
108133def show_bak_list (filename : (None , str , os .path ) = None ):
@@ -140,7 +165,9 @@ def bak_up_cmd(filename: str):
140165 filename (str|os.path)
141166 """
142167 # Return Truthy things for failures that echo their own output,
143- # false for nonspecific or generic failures
168+ # false for nonspecific or generic failures.
169+ # Put differently, False is for complete failures. If this function
170+ # handles a failure gracefully, it should return True.
144171
145172 filename = expandpath (filename )
146173 old_bakfile = db_handler .get_bakfile_entries (filename )
@@ -151,19 +178,20 @@ def bak_up_cmd(filename: str):
151178 old_bakfile = old_bakfile [0 ] if len (old_bakfile ) == 1 else \
152179 _do_select_bakfile (old_bakfile )
153180 if old_bakfile is None :
181+ click .echo ("Cancelled." )
154182 return True
155183 elif not isinstance (old_bakfile , bakfile .BakFile ):
156184 return False
157185
158- new_bakfile = _assemble_bakfile (filename )
159- new_bakfile .date_created = old_bakfile .date_created
160- copy2 (new_bakfile .original_file , new_bakfile .bakfile_loc )
161- db_handler .update_bakfile_entry (old_bakfile , new_bakfile )
186+ old_bakfile .date_modified = datetime .now ()
187+ copy2 (old_bakfile .original_file , old_bakfile .bakfile_loc )
188+ db_handler .update_bakfile_entry (old_bakfile )
162189 return True
163190
164191
165192def bak_down_cmd (filename : str ,
166- keep_bakfile : bool = False ):
193+ keep_bakfile : bool = False ,
194+ quiet : bool = False ):
167195 """ Restore `filename` from .bakfile. Prompts if ambiguous (such as
168196 when there are multiple .bakfiles of `filename`)
169197
@@ -173,19 +201,37 @@ def bak_down_cmd(filename: str,
173201 """
174202 filename = expandpath (filename )
175203 bakfile_entries = db_handler .get_bakfile_entries (filename )
204+ if not bakfile_entries :
205+ click .echo (f"No bakfiles found for { filename } " )
206+ return
176207
177- # TODO still only pulling first result
178208 bakfile_entry = _do_select_bakfile (bakfile_entries ) if len (
179209 bakfile_entries ) > 1 else bakfile_entries [0 ]
180210
181211 if not bakfile_entry :
212+ click .echo (f"No bakfiles found for { filename } " )
182213 return
183214
184- os .remove (filename )
185- copy2 (bakfile_entry .bakfile_loc , filename )
215+ if quiet :
216+ confirm = 'y'
217+ else :
218+ confirm_prompt = f"Confirm: Restore { filename } and erase bakfiles?\n " \
219+ if not keep_bakfile else \
220+ f"Confirm: Restore { filename } and keep bakfiles?\n "
221+ confirm_prompt += "(y/n)"
222+ confirm = click .prompt (confirm_prompt , default = 'n' )
223+ if confirm .lower ()[0 ] != 'y' :
224+ click .echo ("Cancelled." )
225+ return
226+ # os.remove(filename)
227+ # copy2(bakfile_entry.bakfile_loc, filename)
186228 if not keep_bakfile :
229+ os .rename (bakfile_entry .bakfile_loc , filename )
187230 for entry in bakfile_entries :
188- os .remove (entry .bakfile_loc )
231+ # bakfile_entry's bakfile has already been moved
232+ # trying to rm it would print a failure
233+ if entry != bakfile_entry :
234+ os .remove (entry .bakfile_loc )
189235 db_handler .del_bakfile_entry (entry )
190236
191237
@@ -212,7 +258,8 @@ def bak_off_cmd(filename: (None, str, os.path),
212258 filename = expandpath (filename )
213259 bakfiles = db_handler .get_bakfile_entries (filename )
214260 if not bakfiles :
215- click .echo (f"No bakfiles found for { filename } " )
261+ click .echo (f"No bakfiles found for { os .path .abspath (filename )} " )
262+ return False
216263 confirm = input (
217264 f"Confirming: Remove { len (bakfiles )} .bakfiles for { filename } ? "
218265 f"(y/N) " ).lower () == 'y' if not quietly else True
@@ -223,32 +270,47 @@ def bak_off_cmd(filename: (None, str, os.path),
223270 return False
224271
225272
226- def bak_print_cmd (bak_to_print : (str , bakfile .BakFile ), using : (str , None ) = None ):
273+ def bak_print_cmd (bak_to_print : (str , bakfile .BakFile ),
274+ using : (str , None ) = None ):
275+ # if this thing is given a string, it needs to go find
276+ # a corresponding bakfile
227277 if not isinstance (bak_to_print , bakfile .BakFile ):
228- bak_to_print = _get_bakfile_entry (bak_to_print ,
229- select_prompt = ("View which .bakfile? (#)" , "c" ))
278+ _bak_to_print = _get_bakfile_entry (bak_to_print ,
279+ select_prompt = (
280+ "View which .bakfile? (#)" ,
281+ "c" ))
282+ if not _bak_to_print :
283+ click .echo (
284+ f"No bakfiles found for { os .path .abspath (bak_to_print )} " )
285+ else :
286+ bak_to_print = _bak_to_print
230287 if not isinstance (bak_to_print , bakfile .BakFile ):
231288 return # _get_bakfile_entry() handles failures, so just exit here
232- if using :
233- pager = using
234- else :
235- try :
236- pager = os .environ ['PAGER' ]
237- except KeyError :
238- pager = 'less'
239- call ([pager , bak_to_print .bakfile_loc ])
289+ pager = using if using else \
290+ (cfg ['bak_show_exec' ] or os .environ ['PAGER' ]) or 'less'
291+ pager = pager .strip ('"' ).strip ("'" ).split (" " )
292+ call (pager + [bak_to_print .bakfile_loc ])
240293
241294
242295def bak_getfile_cmd (bak_to_get : (str , bakfile .BakFile )):
243296 if not isinstance (bak_to_get , bakfile .BakFile ):
297+ filename = bak_to_get
244298 bak_to_get = _get_bakfile_entry (bak_to_get , err = True )
245299 if not bak_to_get :
300+ click .echo (f"No bakfiles found for { os .path .abspath (filename )} " )
246301 return # _get_bakfile_entry() handles failures, so just exit
247- click .echo (bak_to_get .bakfile_loc )
302+ click .echo (bak_to_get .bakfile_loc )
248303
249304
250- def bak_diff_cmd (filename , command = 'diff' ):
305+ def bak_diff_cmd (filename : str , command = 'diff' ):
251306 # TODO write tests for this (mildly tricky)
252- bak_to_diff = _get_bakfile_entry (expandpath (filename ))
253- call ([command if command else 'diff' ,
254- bak_to_diff .bakfile_loc , bak_to_diff .orig_abspath ])
307+ bak_to_diff = filename if isinstance (filename , bakfile .BakFile ) else \
308+ _get_bakfile_entry (expandpath (filename ))
309+ if not command :
310+ command = cfg ['bak_diff_exec' ] or 'diff'
311+ if not bak_to_diff :
312+ click .echo (f"No bakfiles found for { os .path .abspath (filename )} " )
313+ return
314+ command = command .strip ('"' ).strip ("'" ).split (" " )
315+ call (command +
316+ [bak_to_diff .bakfile_loc , bak_to_diff .orig_abspath ])
0 commit comments