88from ..constants import * # NOQA
99from ..helpers import ArchiveFormatter , interval , sig_int , ProgressIndicatorPercent , CommandError , Error
1010from ..helpers import archivename_validator
11+ from ..helpers import json_print , basic_json_data
1112from ..helpers .argparsing import ArgumentParser
1213from ..manifest import Manifest
1314
@@ -168,8 +169,11 @@ def do_prune(self, args, repository, manifest):
168169 keep += prune_split (archives , rule , num , kept_because )
169170
170171 to_delete = set (archives ) - set (keep )
171- logger .info ("Found %d archives." , len (archives ))
172- logger .info ("Keeping %d archives, pruning %d archives." , len (keep ), len (to_delete ))
172+ if not args .json :
173+ logger .info ("Found %d archives." , len (archives ))
174+ logger .info ("Keeping %d archives, pruning %d archives." , len (keep ), len (to_delete ))
175+ if args .json :
176+ output_data = []
173177 list_logger = logging .getLogger ("borg.output.list" )
174178 # set up counters for the progress display
175179 to_delete_len = len (to_delete )
@@ -178,29 +182,50 @@ def do_prune(self, args, repository, manifest):
178182 for archive_info in archives :
179183 if sig_int and sig_int .action_done ():
180184 break
181- # format_item may internally load the archive from the repository,
185+ # get_item_data/ format_item may internally load the archive from the repository,
182186 # so we must call it before deleting the archive.
183- archive_formatted = formatter .format_item (archive_info , jsonline = False )
187+ if args .json :
188+ archive_data = formatter .get_item_data (archive_info , jsonline = True )
189+ else :
190+ archive_formatted = formatter .format_item (archive_info , jsonline = False )
184191 if archive_info in to_delete :
185- pi .show ()
192+ if not args .json :
193+ pi .show ()
194+ archives_deleted += 1
186195 if args .dry_run :
187196 log_message = "Would prune:"
188197 else :
189198 log_message = "Pruning archive (%d/%d):" % (archives_deleted , to_delete_len )
190199 manifest .archives .delete_by_id (archive_info .id )
191- archives_deleted += 1
200+ if args .json :
201+ archive_data ["kept" ] = False
202+ archive_data ["deleted_archive_number" ] = archives_deleted
192203 else :
193- log_message = "Keeping archive (rule: {rule} #{num}):" .format (
194- rule = kept_because [archive_info .id ][0 ], num = kept_because [archive_info .id ][1 ]
195- )
196- if (
204+ rule , num = kept_because [archive_info .id ]
205+ log_message = "Keeping archive (rule: {rule} #{num}):" .format (rule = rule , num = num )
206+ if args .json :
207+ archive_data ["kept" ] = True
208+ archive_data ["keep_rule" ] = rule
209+ archive_data ["kept_archive_number" ] = num
210+ if args .json :
211+ if (
212+ args .output_list
213+ or not (args .list_pruned or args .list_kept )
214+ or (args .list_pruned and archive_info in to_delete )
215+ or (args .list_kept and archive_info not in to_delete )
216+ ):
217+ output_data .append (archive_data )
218+ elif (
197219 args .output_list
198220 or (args .list_pruned and archive_info in to_delete )
199221 or (args .list_kept and archive_info not in to_delete )
200222 ):
201223 list_logger .info (f"{ log_message :<44} { archive_formatted } " )
202- pi .finish ()
203- if archives_deleted > 0 :
224+ if not args .json :
225+ pi .finish ()
226+ if args .json :
227+ json_print (basic_json_data (manifest , extra = {"archives" : output_data }))
228+ if archives_deleted > 0 and not args .dry_run :
204229 manifest .write ()
205230 self .print_warning ('Done. Run "borg compact" to free space.' , wc = None )
206231 if sig_int :
@@ -295,6 +320,14 @@ def build_parser_prune(self, subparsers, common_parser, mid_common_parser):
295320 action = Highlander ,
296321 help = "specify format for the archive part " '(default: "{archive:<36} {time} [{id}]")' ,
297322 )
323+ subparser .add_argument (
324+ "--json" ,
325+ action = "store_true" ,
326+ help = "Format output as JSON. "
327+ "The form of ``--format`` is ignored, "
328+ "but keys used in it are added to the JSON output. "
329+ "Some keys are always present. Note: JSON can only represent text." ,
330+ )
298331 subparser .add_argument (
299332 "--keep-within" ,
300333 metavar = "INTERVAL" ,
0 commit comments