1:- module(parse, [
    2              parse_upload_metadata/2
    3          ]).    4
    5:- use_module(library(when)).    6:- use_module(library(base64)).    7:- use_module(library(plunit)).    8/* parsing */
    9
   10assoc_metadata_parts([], []).
   11assoc_metadata_parts([Key-Value|Rest_A], [Meta|Rest_B]) :-
   12    when((   ground(Value)
   13         ;   ground(Value_base64)),
   14         base64_encoded(Value,Value_base64, [])),
   15    atomic_list_concat([Key,Value_base64], ' ', Meta),
   16    !,
   17    assoc_metadata_parts(Rest_A, Rest_B).
   18assoc_metadata_parts([Key|Rest_A], [Key|Rest_B]) :-
   19    assoc_metadata_parts(Rest_A, Rest_B).
   20
   21parse_upload_metadata(Atom, Assoc) :-
   22    when(
   23        (   ground(Assoc)
   24        ;   ground(Parts)),
   25        % once/1 needed for SWI-Prolog 10: assoc_metadata_parts/2 has two clauses
   26        % that can both match when Assoc is unbound, leaving a choicepoint.
   27        % SWI-Prolog 10 preserves choicepoints in delayed goals more strictly than v9.
   28        % The predicate logically has exactly one solution, so once/1 eliminates
   29        % the choicepoint warning while preserving correct semantics.
   30        once(assoc_metadata_parts(Assoc, Parts))
   31    ),
   32    atomic_list_concat(Parts, ',', Atom).
   33
   34:- begin_tests(parse).   35
   36test(parse_upload_metadata, []) :-
   37    String = 'filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential',
   38    parse_upload_metadata(String, Struct),
   39    Struct = [filename-"world_domination_plan.pdf",is_confidential].
   40
   41test(parse_upload_metadata_backwards, []) :-
   42    Struct = [filename-"world_domination_plan.pdf",is_confidential],
   43    parse_upload_metadata(String, Struct),
   44    String = 'filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential'.
   45
   46test(parse_upload_metadata_backwards_empty, []) :-
   47    Struct = [filename-"world_domination_plan.pdf"],
   48    parse_upload_metadata(String, Struct),
   49    String = 'filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=='.
   50
   51:- end_tests(parse).