123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327open!Coreopen!Ppxlibtypet={rewrite:expressionString.Map.t;css_string:string;stylesheet_location:location;dont_hash_prefixes:stringlist}letcombine_rewrite_and_dont_hash~loc~rewrite~dont_hash=List.folddont_hash~init:rewrite~f:(funaccdont_hash_this->Map.updateaccdont_hash_this~f:(function|None->letopen(valAst_builder.makeloc)inpexp_constant(Pconst_string(dont_hash_this,loc,None))|Some_->Location.raise_errorf~loc{|Found duplicate value \"%s\" between [dont_hash] and [rewrite].|}dont_hash_this));;moduleSerializable_options=structtypet={rewrite:stringString.Map.t;dont_hash:stringlist[@sexp.list];dont_hash_prefixes:stringlist[@sexp.list]}[@@sexp.allow_extra_fields][@@derivingsexp]lettests=letparsed=t_of_sexp(Sexp.of_strings)inprint_s(sexp_of_tparsed);;let%expect_test"Regression test against non-forwards compatible serialization with \
Jenga Rule."=test{|((rewrite ()))|};[%expect{| ((rewrite ())) |}];test{|((rewrite ()) (brand_new_field ()))|};[%expect{| ((rewrite ())) |}];test{|((rewrite ()) (dont_hash ()))|};[%expect{| ((rewrite ())) |}];test{|((rewrite ()) (dont_hash (1 2 3)))|};[%expect{| ((rewrite ()) (dont_hash (1 2 3))) |}];;(* Parses the string "A.B.x" into a Ppxlib AST corresponding to an identifier.
Also parses the string "x" into a Ppxlib AST correponding to the string constant
"x".
*)letparse_string_to_expression:string->expression=funs->letloc=Location.noneinletopen(valAst_builder.makeloc)inletitems=String.split~on:'.'sinmatchitemswith|[single]->pexp_constant(Pconst_string(single,Location.none,None))|first::tl->List.fold~init:(Lidentfirst)tl~f:(funaccitem->Ldot(acc,item))|>Located.mk|>pexp_ident|_->raise_s(Sexp.Atom"Expected a valid Ocaml identifier expression");;letto_options{rewrite;dont_hash;dont_hash_prefixes}~css_string={rewrite=(letrewrite=Map.maprewrite~f:(funs->parse_string_to_expressions)incombine_rewrite_and_dont_hash~loc:Location.none~rewrite~dont_hash);css_string;stylesheet_location=Location.none;dont_hash_prefixes};;endletempty~css_string={rewrite=String.Map.empty;css_string;stylesheet_location=Location.none;dont_hash_prefixes=[]};;(* Parses the AST of a list of expressions into an actual [expression list]. *)letparse_expr_list~on_errorexpression=letrechelper~acc~expression=matchexpression.pexp_descwith|Pexp_construct({txt=Lident"[]";_},None)->(* NOTE: This list is reversed, but it does not matter since currently it is only
used to populate a map, but if it ever were to matter that the list is
reversed, then this list would need to be reversed here. *)acc|Pexp_construct({txt=Lident"::";_},Some{pexp_desc=Pexp_tuple[expression;child];_})->helper~acc:(expression::acc)~expression:child|_->on_error~loc:expression.pexp_locinhelper~acc:[]~expression;;letloc_ghoster=objectinheritAst_traverse.mapassupermethod!locationlocation=super#location{locationwithloc_ghost=true}end;;letraise_due_to_malformed_rewrite~loc=Location.raise_errorf~loc"%s"(String.strip{|
The rewrite argument to 'stylesheet' must be called with a list literal containing tuple literals,
where the first element of the tuple must be a string literal (the second element in the tuple can be
any expression which evaluates to a string.)
examples:
stylesheet ~rewrite:[ "foo_bar", "foo-bar" ] (* Rewrites instances of "foo_bar" in the css string to "foo-bar" *)
stylesheet ~rewrite:[ "foo-bar", "foo-bar" ] (* Prevents the "foo-bar" identifier from being hashed for uniqueness *)
stylesheet ~rewrite:[ "my_table", My_table_component.table ] (* References an identifier defined in another module *)
|});;(* Parses the AST of a [(string * string) list] of expressions where each tuple's first
element is a string constant into a [expression String.Map.t] map. e.g parses:
["class1", Style.x; "class2"; "class2"]
to:
[String.Map.of_alist_exn ["class1", Style.x; "class2"; "class2"]] *)letparse_rewrite~locexpression=letalist=parse_expr_listexpression~on_error:raise_due_to_malformed_rewrite|>List.map~f:(funexpression->matchexpressionwith|{pexp_desc=Pexp_tuple[{pexp_desc=Pexp_constant(Pconst_string(key,_,_));_};value];_}->key,loc_ghoster#expressionvalue|{pexp_desc=_;_}->raise_due_to_malformed_rewrite~loc:expression.pexp_loc)inmatchString.Map.of_alistalistwith|`Okrewrite->rewrite|`Duplicate_keykey->Location.raise_errorf~loc"Found duplicate key \"%s\" inside of [rewrite]."key;;letmalformed_dont_hash_error_message=lazy(String.strip{|
The dont_hash argument to 'stylesheet' must be called with a list literal containing string literals.
example:
stylesheet ~dont_hash:[ "foo_bar" ] (* Does not hash instances of "foo_bar". *)
stylesheet ~dont_hash:[ "--bg-color" ] (* Does not hash instances of "--bg-color". *)
|});;letmalformed_dont_hash_prefixes_error_message=lazy(String.strip{|
The dont_hash_prefixes argument to 'stylesheet' must be called with a list literal containing string literals.
example:
stylesheet ~dont_hash_prefixes:[ "--bg" ] (* Does not hashes identifiers that start with "--bg" (e.g. "--bg-color"). *)
stylesheet ~dont_hash_prefixes:[ "--" ] (* Does not hash css variables. *)
|});;letparse_string_list~name~syntax_error_message~locexpression=letraise_on_error~loc=Location.raise_errorf~loc"%s"(forcesyntax_error_message)inletout=parse_expr_listexpression~on_error:raise_on_error|>List.map~f:(funexpression->matchexpression.pexp_descwith|Pexp_constant(Pconst_string(s,_,_))->s|_->raise_on_error~loc:expression.pexp_loc)inletdups=List.find_all_dupsout~compare:String.compareinmatchdupswith|[]->out|dups->Location.raise_errorf~loc{| Found duplicate values %s inside of [%s]. |}(Sexp.to_string([%sexp_of:stringlist]dups))name;;moduleList=structincludeList(* Like [find_map], but also returns the resulting list without the found element. *)lettake_mapl~f=letrecloop~acc=function|[]->None|hd::tl->(matchfhdwith|None->loop~acc:Reversed_list.(hd::acc)tl|Someres->Some(res,Reversed_list.rev_appendacctl))inloop~acc:Reversed_list.[]l;;endletraise_misparse_with_syntax_instructions~extra_message~loc=Location.raise_errorf~loc"%s%%css must contain a call to [val stylesheet : ?rewrite:(string * string) list -> \
?dont_hash:string list -> dont_hash_prefixes:string list -> string -> unit]"extra_message;;letraise_if_both_of_these_are_present_at_the_same_time_and_explain~locabargs=letcontainsidentifier=List.existsargs~f:(fun(label,_)->matchlabelwith|LabelledxwhenString.equalxidentifier->true|_->false)inmatchcontainsa,containsbwith|true,true->Location.raise_errorf~loc{| ppx_css found unexpected arguments. Found two uses of alternate syntax in the same place which might be ambiguous/result in unexpected results. Only use 1 of "%s" or "%s".|}ab|(false|true),(false|true)->();;letalternate_syntaxes=["dont_hash","don't_hash";"dont_hash_prefix","don't_hash_prefix"];;letraise_if_alternate_syntaxes~locargs=List.iteralternate_syntaxes~f:(fun(a,b)->raise_if_both_of_these_are_present_at_the_same_time_and_explain~locabargs);;letvalidate_args~locargs=letopenOption.Let_syntaxinraise_if_alternate_syntaxes~locargs;letargs=let%mapcss_string,remaining_args=List.take_mapargs~f:(fun(_,(arg:expression))->matcharg.pexp_descwith|Pexp_constant(Pconst_string(l,_,_))->Somel|_->None)inletrewrite,remaining_args=List.take_mapremaining_args~f:(function|Labelled"rewrite",expression->Some(parse_rewrite~locexpression)|_->None)|>Option.value~default:(String.Map.empty,remaining_args)inletdont_hash,remaining_args=List.take_mapremaining_args~f:(function|Labelled("dont_hash"|"don't_hash"),expression->Some(parse_string_list~name:"dont_hash"~syntax_error_message:malformed_dont_hash_error_message~loc:expression.pexp_locexpression)|_->None)|>Option.value~default:([],remaining_args)inletdont_hash_prefixes,remaining_args=List.take_mapremaining_args~f:(function|Labelled("dont_hash_prefixes"|"don't_hash_prefixes"),expression->Some(parse_string_list~name:"dont_hash_prefixes"~syntax_error_message:malformed_dont_hash_prefixes_error_message~loc:expression.pexp_locexpression)|_->None)|>Option.value~default:([],remaining_args)inletrewrite=List.folddont_hash~init:rewrite~f:(funaccdont_hash_this->Map.updateaccdont_hash_this~f:(function|None->letopen(valAst_builder.makeloc)inpexp_constant(Pconst_string(dont_hash_this,loc,None))|Some_->Location.raise_errorf~loc{|Found duplicate value \"%s\" between [dont_hash] and [rewrite].|}dont_hash_this))incss_string,rewrite,remaining_args,dont_hash_prefixesinmatchargswith|None|Some(_,_,_::_,_)->raise_misparse_with_syntax_instructions~extra_message:"ppx_css found unexpected arguments. "~loc|Some(css_string,rewrite,[],dont_hash_prefixes)->css_string,rewrite,dont_hash_prefixes;;letparse(expression:expression)=letloc=expression.pexp_locinletloc={locwithloc_ghost=true}inmatchexpression.pexp_descwith|Pexp_apply({pexp_desc=Pexp_ident{txt=Lident"stylesheet";loc};_},args)->letcss_string,rewrite,dont_hash_prefixes=validate_args~locargsin{css_string;rewrite;stylesheet_location={locwithloc_ghost=true};dont_hash_prefixes}|_->raise_misparse_with_syntax_instructions~extra_message:""~loc;;