(* ::Package:: *) (*......................................................................*) (* :Title: PackageOptionChecks *) (* :Author: Leonid B.Shifrin *) (* :Summary: The present functionality allows to set up additional and more stringent tests for option values than those achieved by traditional option filtering, and, more importantly, set up the responding bahavior of the option-receiving function. This extra "protection" can be set up/modified for entire context or individual functions, in a flexible way. One can bundle this functionality with the package being created, or use "externally" for bulletproofing already written packages. Also, the present package can help during debugging stage. Only the functions which use opts___?OptionQ pattern for declaring optional arguments, can be protected (the name is arbitrary). The "protection" does add new definitions to functions being "protected". Effort was made to ensure that this will not affect their main functionality, however still this may work incorrectly in certain cases (see discussion below). *) (* :Context: PackageOptionChecks` *) (* :Package version: 1.0 *) (* :Copyright: Copyright 2009, Leonid B.Shifrin. Permission is granted to distribute verbatim copies of this package together with any of your packages that use it, provided the following acknowledgement is printed in a standard place: "PackageOptionChecks.m is distributed with permission by Leonid B. Shifrin." *) (* :History: Version 1.0 January 2009 *) (* :Keywords: package development, debugging, options, bulletproofing *) (* :Mathematica version: 5.0 *) (* :Discussion: The functionality of the present package allows one to "protect" given option-taking functions from passed options which are unknown to the function or are set to inappropriate values. One can do this protection both on the level of the entire package or individual functions. This protection is different from the traditional option- filtering, since it allows a called function to respond to a bad option in a certain specified way,rather than a calling function to restrict the set of options passed to a called one. In other words, this works "from the other side" of option-passing and complements traditional options filtering. In particular,it allows to discover cases of options not properly filtered. An absolute pre-requisite for the function-to-be-protected is that it (or at least,its relevant option-carrying definitions) is defined through DownValues and the opts___?OptionQ form is used to declare optional parameters (the nameis arbitrary). The new constructs OptionPattern-OptionValue, introduced in v.6, are not covered in the present version of the package (work is underway). Setting up the initial protection is a 2-step process,where the first step can be performed before or after, but the second has to be performed after the functions to be protected are defined.The first step consists of "declaring" the options with the patterns that their rhs-s have to match,and functions which take them.This is done by calling the function. The second step is done by calling the function.If there is a need to protect a number of functions being currently interactively developed in the "Global`"context,one just has to pass "Global`"to both SetOptionsInfo and TurnOptionChecksOn.In other words,one can use the present functionality also for initial interactive development and before placing functions to a package (different context).After the initial protection is set up,a number of possibilities are available to change its level and scope. First,one can add/remove protection,both on the level of entire context (package) and individual functions.Second,one can change the option-protecting information.This can be done both from within the context containing functions of interest,and from the outside. This allows,for instance,to add information about new options/ functions that emerged during incremental development,and/or change the level of protection for existing options/functions. The information used to protect options/functions is based entirely on what is provided by the user to the information-setting functions SetOptionsInfo, ResetOptionsInfo,and not on analysis of declarations Options[function] = { ...}. Even if an option is set by Options[f]=..., it is considered unknown to the package unless declared in SetOptionsInfo, ResetOptionsInfo. While the option-protection functionality is completely independent of any one package (context) which we want to protect,the former does intrude into and change the functionality of the latter. For all option-protected functions,new definitions are added and thus they are modified. It is hoped that these modifications will never affect their main functionality,but there is no guarantee for this. It is certainly possible to give examples of the opposite,especially for functions which rely on/manipulate the internal/global rule ordering of their definitions and/or add/remove their own or other function's definitions dynamically (at run-time).In some of these cases,by executing TurnOptionChecksOn[context]after any changes to definitions have been made, it should be possible to keep the changed functions "protected" (for the price of possibly slowing these functions down). One can use the present functionality to protect functions also in packages written by someone else.In this case,the package code does not need to be modified at all.One just has to execute SetOptionsInfo[context, info] (with a proper option-protecting information ),and then TurnOptionChecksOn[context],after loading the package of interest. The implementation is relatively strongly typed. Internal consistency checks are set up for private functions of the package. In case of inappropriate input, most private functions will throw an exception which will be caught on the level of public functions and cause their abnormal termination with an error-message issued and $Failed returned. Such behavior means an internal bug in the package, and I will appreciate if it is reported to me for fixing. *) (* :Limitations: The present functionality will work only for functions defined through DownValues. This excludes SubValues- and UpValues- based definitions-cases like these: f[n_?Integer][x_,y_,opts___?OptionQ]:=... (SubValues) or f/:g[a_,b_,f[x_,y_,opts___?OptionQ]]:=... (UpValues) As mentioned, the present protection changes (adds new) definitions for functions being protected. This may not work correctly if these definitions are manipulated at run-time by the function itself or some other function, or if some other functions parse/analyze the definitions of a given one without the present protection in mind. *) (*......................................................................*) BeginPackage["PackageOptionChecks`"]; Begin["`Private`"]; With[{earlyVersion=ToExpression[StringTake[$Version,1]]<6}, status= Function[code, If[earlyVersion,code,Quiet[code]], HoldAll]@ Check[Needs["CheckOptions`"], $Failed, Get::noopen,Needs::nocont]]; End[]; PackageOptionChecksAvailableQ[]= PackageOptionChecks`Private`status=!=$Failed; (* ** These If statements play here a role of C ifdef/ifndef macros *) If[PackageOptionChecksAvailableQ[], (********************************************************************) (* Usage messages *) (********************************************************************) OptionCheckOn::usage = "OptionCheckOn[function_] can be used to enforce some option-\ checking and additional diagnostics for the function .\ This may, however, slow it down. A pair of functions OptionCheckOn/\ OptionCheckOff gives a more detailed control over option-protection \ than TurnOptionChecksOn/TurnOptionChecksOff, which protect/unprotect\ all the functions in a given contexts that were \"registered\" with \ SetOptionsInfo. Functions OptionCheckOn/OptionCheckOff will only \ operate on functions \"registered\" with SetOptionsInfo prior to \ their use"; OptionCheckOff::usage = "OptionCheckOff[function_] removes option checks from the \ definitions of , added by OptionCheckOn. This can be used \ when one knows for sure that passed options make sense, to speed up the \ computation"; TurnOptionChecksOn::usage = "TurnOptionChecksOn[context_String] turns on the protection against \ passed options with inappropriate values, for all functions in \ the context , \"registered\" with SetOptionsInfo before \ using it. Also, it \"declares\" these functions as \"protected\".\ It uses OptionCheckOn internally, and also the pair OptionCheckOn/\ OptionCheckOff can be used on these functions afterwards, for a \ finer control over functions' protection. TurnOptionChecksOn[] uses the current context as . It is \ recommended to use it after the end of `Private` sub-context and \ before the command, to protect the option-receiving \ functions in the package. It can also be used from within the \ `Private` sub-context (or any other place in the program, but \ after all the functions to be protected have been defined), but then \ the context name has to be given to it explicitly. It is very important that TurnOptionChecksOn is called only after \ the functions to be protected have been completely defined. The reason \ is that the protection mechanism used here, uses the \"normal\" \ function's definition(s) to construct new \"protecting\" ones that \ will be tried before the main ones. Likewise, should the functions\ be dynamically redefined, and TurnOptionChecksOn has in general to \ be called again to update the \"protecting\" definitions accordingly. It is also very important for the presently used mechanism to work \ correctly, that options in the option-taking function's signature(s)\ are defined through the opts___?OptionQ pattern (name is \ arbitrary). This pattern is used by the protection mechanism to \ recognize options, and thus options expressed with different patterns \ will not be recognized as such."; TurnOptionChecksOff::usage = "TurnOptionChecksOn[context_String] turns off the protection against \ passed options with inappropriate values, for those functions in the \ context that receive options and have been both \"registered\" \ with SetOptionsInfo (usually, in the beginning of the package), and \ the \"protected\" and \"declared\" with TurnOptionChecksOn. The functions \ lose protection but remain \"declared\", so that the pair of \ OptionCheckOn/OptionCheckOff can still be used on the functions \ individually to change their individual protection status. Calling \ TurnOptionChecksOn will protect all these function again. Unlike \ TurnOptionChecksOn, TurnOptionChecksOff does not have a brief form \ and should be always given the name of the context explicitly."; SetOptionsInfo::usage = "SetOptionsInfo[context_String,optInfo:\ {{(_String|_Symbol),_,((_Symbol:>_)|{(_Symbol:>_)..})}..}] is used to \ \"register\" all the option-receiving functions in the context \ that one wishes to protect against passed options with inappropriate values. \ In the pattern of argument, in each sublist, the first element \ is option's name, the second is the pattern that the option's rhs must match, \ and the last is a rule or list of rules each of which has a form \ (function_Symbol:>respeonseCode_). In this form, the present function \ can be called from within any \ place in the package (program), but before the first call of \ TurnOptionChecksOn. It is recommended to use it close to the beginning \ of the package. In case when it is called multiple times, the current \ option information is the one corresponding to the last call. A typical \ example of use: SetOptionsInfo[\"MyContext`\", {{FontSize,_Integer?(10<=#<=20&), {displayText:>$Failed,displayCode:>Trow[$Failed]}}, {FontWeight,Bold|Plain,displayText:>Print[\"Bad optiion\"]}] SetOptionsInfo[optInfo:\ {{(_String|_Symbol),_,((_Symbol:>_)|{(_Symbol:>_)..})}..}] \ will use the current context as . If used in a package, this \ form has to be used in the main context of a package rather than `Private` \ or any other subcontexts. In this case, one should not use any new symbols \ in pattern definitions (say, pattern-testing functions), otherwise these \ symbols will be created in the main context of the package. If their \ presence is anavoidable, one can move the function call into the `Private` \ subcontext and give the main context name to the function explicitly, as \ above"; ResetOptionsInfo::usage = "ResetOptionsInfo[context_String,\ {{(_String|_Symbol),_,((_Symbol:>_)|{(_Symbol:>_)..})}..}] is used to \ replace the option-protecting information for known options or \ add new option-protecting information for new options. "; ClearOptionsInfo::usage = "ClearOptionsInfo[context_String,removeList:{((Rule|RuleDelayed)[\ (_Symbol|_String),_Symbol|{__Symbol}|All])..}] \ will remove option-protecting information for all options with \ names in "; OptionCheckIsOn::usage = "OptionCheckIsOn[function_Symbol] returns True if the \ function is currently \"protected\" from \ inappropriate option settings, and False otherwise"; GetOptionsInfo::usage = "GetOptionsInfo[context_String] returns the current \ option-protecting information for the context ."; (********************************************************************) (* Error messages *) (********************************************************************) OptionCheckIsOn::badarg = "The argument is supposed to be a Symbol"; OptionCheckIsOn::argnum = "The function was called with `1` arguments. Exactly 1 argument is expected"; SetOptionsInfo::badargs = "Some of the arguments are of the wrong type. The optional first \ stands for context and has to be a String. The second stands for \ the option information. It has to match the following pattern: {{(_String|_Symbol),_,((_Symbol:>_)|{(_Symbol:>_)..})}..}, \ where in each sublist the first element is option's name, the \ second is the pattern that the option rhs must match, and the last \ is a rule or list of rules each of which has a form \ (function_Symbol:>respeonseCode_) ."; SetOptionsInfo::argnum = "The function was called with `1` arguments. 1 or 2 arguments \ were expected"; OptionCheckOn::undecl= "The symbol `1` has not been declared as a name of one of the \ functions protected from inappropriate option values. Either it \ was not present in the argument to SetOptionsInfo, or the functions \ in its context have not yet been protected with TurnOptionChecksOn"; OptionCheckOn::badarg = "The argument is supposed to be a Symbol"; OptionCheckOn::argnum= "The function was called with `1` arguments. Exactly one argument \ was expected"; OptionCheckOff::undecl= "The symbol `1` has not been declared as a name of one of the \ functions protected from inappropriate option values. Either it \ was not present in the argument to SetOptionsInfo, or the functions \ in its context have not yet been protected with TurnOptionChecksOn"; OptionCheckOff::badarg = "The argument is supposed to be a Symbol"; OptionCheckOff::argnum= "The function was called with `1` arguments. Exactly one argument \ was expected"; TurnOptionChecksOn::noinfo = "There is no information available about the patterns that options \ for the functions in the context `1` must match. Perhaps, the \ SetOptionsInfo function has not yet been called for the context `1`."; TurnOptionChecksOn::badarg = "The argument is supposed to be a context name (String)"; TurnOptionChecksOn::argnum = "The function was called with `1` arguments. Zero or 1 argument were \ expected"; TurnOptionChecksOff::badarg = "The argument is supposed to be a context name (String)"; TurnOptionChecksOff::argnum = "The function was called with `1` arguments. Exactly 1 argument \ was expected"; ResetOptionsInfo::badargs = "The first argument must be a string, and the second must follow \ the general option-protection information pattern \ {{(_String|_Symbol),_,((_Symbol:>_)|{(_Symbol:>_)..})}..}"; ResetOptionsInfo::argnum = "The function was called with `1` argument(s). Exactly 2 arguments \ were expected"; ClearOptionsInfo::badargs = "The first argument must be a string, and the second must be \ a list of rules matching the pattern {((Rule|RuleDelayed)[\ (_Symbol|_String),_Symbol|{__Symbol}|All])..}]"; ClearOptionsInfo::argnum = "The function was called with `1` argument(s). Exactly 2 arguments \ were expected"; GetOptionsInfo::badarg = "The argument is supposed to be a context name (String)"; GetOptionsInfo::argnum = "The function was called with `1` arguments. Exactly 1 argument \ was expected"; ];(* End If *) (********************************************************************) (********************************************************************) (********** Implementation - Private part ************) (********************************************************************) (********************************************************************) If[PackageOptionChecksAvailableQ[], Begin["`Private`"]]; If[PackageOptionChecksAvailableQ[], (********************************************************************) (* Initialization *) (********************************************************************) DeclaredFunctionsWithOptions[_]={}; optionInfo[_]={}; reactF[_,_]=$Failed; (********************************************************************) (* Auxilliary functions *) (********************************************************************) Attributes[setDelayedHeld]={HoldFirst}; setDelayedHeld[lhs_,Hold[rhs_]]:=lhs:=rhs; optName[opt_?OptionQ]:=First[opt]; deleteSpecificDefs[f_Symbol,test_]:= DownValues[f]=DeleteCases[DownValues[f],def_?test]; (********************************************************************) (* Methods manipulating the option information data structure *) (********************************************************************) (* ** (function:>(code-to-execute-upon-bad-option)). RuleDelayed ** is required in order to prevent premature evaluation *) functionInfoPattern = (_Symbol:>_); (* accessor methods for this data type *) With[{infof=functionInfoPattern}, fname[finfo:infof]:=First[finfo]; fname[finfo:{infof...}]:=Map[fname,finfo]; code[finfo:infof]:=First[Extract[finfo,{{-1}},Hold]]; (* General option-information data structure *) optionInformationPattern = {{(_String|_Symbol),_,(infof|{infof..})}...}; ]; With[{infoPattern = optionInformationPattern, infof = functionInfoPattern}, optionNames[optInfo:infoPattern]:=Part[optInfo,All,1]; (* ** functionSplit and functionMerge are used to convert ** the option-information data structure to its "normal" ** form (new line for each function) and back to "brief" ** form (for the same option name and same option pattern, ** functions grouped in a list). "Normal" form is easier ** for internal manipulations, "brief" is more user-friendly. *) functionSplit[optInfo:infoPattern]:= Flatten[Replace[optInfo,#,{1}],1]&@ {{opt_,pt_,funs:{infof..}}:> Map[{opt,pt,#}&,funs], x_:>{x}}; (* Generate full rules from simple ones *) Attributes[mergingRules]={HoldRest};(* to keep the unevaluated *) mergingRules[rules:{({_,_}:> _)..}]:=mergingRules/@rules; mergingRules[(lhs:{_,_}):>rhs_]:=mergingRules[lhs,rhs]; mergingRules[{fst_,snd_},merged_]:= {x___,{opt_,pt_,fst},y___,{opt_,pt_,snd},z___}:> {x,{opt,pt,merged},y,z}; (* ** Can be inefficient for large number of functions and options. *) functionMerge[optInfo:infoPattern]:= optInfo//.mergingRules[{ {finfo:infof,ginfo:infof}:>{finfo,ginfo}, {finfo:infof,funs:{infof..}}:>Append[funs,finfo], {funs:{infof..},finfo:infof}:>Append[funs,finfo]}]; allFunctionsWithOptions[optInfo:infoPattern]:= Union@Flatten@ Map[Cases[#,x:(infof|{infof..}):>fname[x]]&,optInfo]; functionsWithOption[optInfo:infoPattern, optName:(_Symbol|_String)]:= fname[Flatten@Cases[optInfo,{optName,_,funs_}:>funs]]; removeFunctionsForOption[optInfo:infoPattern, optName:(_Symbol|_String),All]:= removeFunctionsForOption[optInfo,optName, functionsWithOption[optInfo,optName]]; removeFunctionsForOption[optInfo:infoPattern, optName:(_Symbol|_String),fun_Symbol]:= removeFunctionsForOption[optInfo,optName,{fun}]; (* ** <<>> - non-generic: the structure of the ** pattern used explicitly *) removeFunctionsForOption[optInfo:infoPattern, optName:(_Symbol|_String),funs:{__Symbol}]:= functionMerge[ DeleteCases[ functionSplit[optInfo], {optName,_,Alternatives@@funs:>_}]]; (* ** Again,inefficient (but simple). Can be improved ** with e.g. Reap-Sow *) newOptionInfo[oldInfo:infoPattern,updateInfo:infoPattern]:= functionMerge[ Join[functionSplit[oldInfo],functionSplit[updateInfo]]//. {x___,{opt_,oldpt_,f:infof},y___,{opt_,newpt_,g:infof},z___}/; fname[f]===fname[g]:> {x,{opt,newpt,g},y,z}]; (* is a Symbol, of course - this is just for documentation *) clearedOptionInfo[oldInfo:infoPattern,removeList: {((Rule|RuleDelayed)[(_Symbol|_String),_Symbol|{__Symbol}|All])..}]:= Fold[removeFunctionsForOption[#1,Sequence@@#2]&, oldInfo, removeList]; ]; (* End With*) (********************************************************************) (* Functions to be used in interfacing with CheckOptions` package *) (********************************************************************) allOptionsValidQ[f_Symbol,opts___?OptionQ]:= And@@Map[goodOptionQ[f,#]&,Flatten[{opts}]]; invalidOptions[f_Symbol,opts___?OptionQ]:= Extract[#,Position[goodOptionQ[f,#]&/@#,False]]&@ Flatten[{opts}]; (* ** Function to be executed in case of inappropriate option. ** Wrapping second and third arguments in Hold is necessary ** to comply with the functionality. If there ** is more than one invalid option, all the response code for ** each invalid function is executed, and the result of the ** last one is returned *) errorF[f_,Hold[opts___],Hold[args___]]:= With[{invalid = invalidOptions[f,opts], responseF= CompoundExpression[ If[knownOptionQ[f,optName[#]], Message[f::badopt,#,optPattern[f,optName[#]]], (* else *) Message[f::optundef,#]]; reactF[f,optName[#]]]&}, Last[responseF/@invalid]]; (* ** Option-checking function to be a part of a new option- ** checking definition for *) optionCheckingF[f_,Hold[opts___],Hold[args___]]:= !allOptionsValidQ[f,opts]; (********************************************************************) (** Generating checking/response functions and error messages **) (********************************************************************) With[{optHead=Rule|RuleDelayed,infof = functionInfoPattern, earlyVersion=ToExpression[StringTake[$Version,1]]<6, infoPattern = optionInformationPattern}, makeOptionCheck[optName:(_String|_Symbol),ptrn_,fun:infof]:= With[{fn = fname[fun],fcode = code[fun]}, optPattern[fn,optName]=ptrn; goodOptionQ[fn,optHead[optName,rhs_]]:= MatchQ[rhs,ptrn]; knownOptionQ[fn,optName]=True; setDelayedHeld[reactF[fn,optName],fcode]; (* Correcting rule ordering for early versions *) If[earlyVersion, With[{dv=DownValues[goodOptionQ]}, With[{pos=Position[dv,def_/;Not[FreeQ[def,general]],1]}, If[pos=!={{Length[dv]}}, DownValues[goodOptionQ]= Join[Delete[dv,pos],Extract[dv,pos]]]]]]; ]; makeOptionCheck[optName:(_String|_Symbol),ptrn_,funs:{infof..}]:= Map[makeOptionCheck[optName,ptrn,#]&,funs]; resetOptionChecks[optInfo:infoPattern]:= With[{allfuns=allFunctionsWithOptions[optInfo]}, With[{delDefs=deleteSpecificDefs[#, Not[FreeQ[#,Alternatives@@allfuns]]&]&}, delDefs/@{goodOptionQ,reactF,knownOptionQ,optPattern}; If[earlyVersion, goodOptionQ[_,optHead[(_String|_Symbol),_]]/;(general;True):=False, (* else *) goodOptionQ[_,optHead[(_String|_Symbol),_]]:=False]; reactF[_,_]=$Failed; knownOptionQ[_,_]=False;]]; ]; (* External With *) createErrorMessage[function_Symbol]:= CompoundExpression[ function::badopt = "The option `1` is invalid. The pattern for the option's rhs to match is `2` "; function::optundef ="The option `1` is unknown"; ]; (********************************************************************) (* A few higher-level functions interfacing with the public ones *) (********************************************************************) (* ** Tests if the function is "known" to the checking system *) declaredQ[function_Symbol]:= MemberQ[ DeclaredFunctionsWithOptions[ Context[function]], function]; (* ** Updates the current checking etc function definitions. ** Is called by all functions that change the option-protecting ** information. *) update[context_String]/;optionInfo[context]=!={}:= With[{optInfo = optionInfo[context]}, With[{funsWithOpts=allFunctionsWithOptions[optInfo]}, resetOptionChecks[optInfo]; makeOptionCheck@@@optInfo; createErrorMessage/@funsWithOpts; Unprotect[DeclaredFunctionsWithOptions]; DeclaredFunctionsWithOptions[context]=funsWithOpts; Protect[DeclaredFunctionsWithOptions]; ];]; update[context_String]/;optionInfo[context]==={}:=Null; (* ** Is used to set or reset option-protecting information *) With[{infoPattern = optionInformationPattern}, setOptionInfo[context_String,optInfo:infoPattern]:= CompoundExpression[ optionInfo[context] = optInfo; update[context];]]; (********************************************************************) (* Setting up internal consistency checks *) (********************************************************************) Attributes[setConsistencyChecks]={Listable}; setConsistencyChecks[function_Symbol]:= function[___]:=Throw[$Failed,{InternalError,function}]; Attributes[catchInternalError] = {HoldAll}; catchInternalError[code_]:= Catch[code,{InternalError,_}, Function[{value,tag}, With[{fn = Last[tag]}, fn::err = "Internal package error"; Message[fn::err]; fn::err=.; value]]]; setConsistencyChecks[reactF,setDelayedHeld,optName,fname,code, optionNames,functionSplit,mergingRules,functionMerge, allFunctionsWithOptions,functionsWithOption, removeFunctionsForOption,newOptionInfo,clearedOptionInfo, allOptionsValidQ,invalidOptions,errorF,optionCheckingF, makeOptionCheck,resetOptionChecks,createErrorMessage, update,setOptionInfo,optPattern,goodOptionQ]; (********************************************************************) (********************************************************************) (***************** Public interface ******************) (********************************************************************) (********************************************************************) (********************************************************************) (* Switching off/on or testing protection of individual functions *) (* *) (* Note: this is the only block interfacing with the *) (* functionality of the CheckOptions` package *) (********************************************************************) (* ** Note that this adds protecting definitions even to ** Protected functions. *) OptionCheckOn[function_Symbol?declaredQ]:= With[{protected=Unprotect[function]}, AddOptionsCheck[function,optionCheckingF,errorF]; Protect[protected]]; (* Error cases *) OptionCheckOn[f_Symbol]:= "never happens"/;Message[OptionCheckOn::undecl,f]; OptionCheckOn[f_]/;Head[f]=!=Symbol:= "never happens"/;Message[OptionCheckOn::badarg]; OptionCheckOn[x___]/;Length[{x}]=!=1:= "never happens"/;Message[OptionCheckOn::argnum,Length[{x}]]; (************************************) OptionCheckOff[function_Symbol?declaredQ]:= With[{protected=Unprotect[function]}, RemoveOptionsCheck[function,optionCheckingF]; Protect[protected]]; (* Error cases *) OptionCheckOff[f_Symbol]:= "never happens"/;Message[OptionCheckOff::undecl,f]; OptionCheckOff[f_]/;Head[f]=!=Symbol:= "never happens"/;Message[OptionCheckOff::badarg]; OptionCheckOff[x___]/;Length[{x}]=!=1:= "never happens"/;Message[OptionCheckOff::argnum,Length[{x}]]; (******************************************************) OptionCheckIsOn[function_Symbol]:= OptionIsChecked[function,optionCheckingF]; (* Error cases *) OptionCheckIsOn[_]:= "never happens"/;Message[OptionCheckIsOn::badarg]; OptionCheckIsOn[x___]/;Length[{x}]=!=1:= "never happens"/;Message[OptionCheckIsOn::argnum,Length[{x}]]; (********************************************************************) (* Setting/retrieving/changing the option-protection information *) (********************************************************************) GetOptionsInfo[context_String]:= optionInfo[context]; (* Error cases *) GetOptionsInfo[_]:= "never happens"/;Message[GetOptionsInfo::badarg]; GetOptionsInfo[x___]/;Length[{x}]=!=1:= "never happens"/;Message[GetOptionsInfo::argnum,Length[{x}]]; (******************************************************) With[{optinfoPattern = optionInformationPattern}, SetOptionsInfo[context_String,optInfo:optinfoPattern]:= catchInternalError@setOptionInfo[context,optInfo]; SetOptionsInfo[optInfo:optinfoPattern]:= SetOptionsInfo[$Context,optInfo]]; (* Error cases *) SetOptionsInfo[_,_]:= "never happens"/;Message[SetOptionsInfo::badargs]; SetOptionsInfo[x___]/;Not[MatchQ[Length[{x}],1|2]]:= "never happens"/;Message[SetOptionsInfo::argnum,Length[{x}]]; (******************************************************) With[{optinfoPattern = optionInformationPattern}, ResetOptionsInfo[context_String,updateInfo:optinfoPattern]:= catchInternalError@setOptionInfo[context, newOptionInfo[optionInfo[context],updateInfo]]]; (* Error cases *) ResetOptionsInfo[_,_]:= "never happens"/;Message[ResetOptionsInfo::badargs]; ResetOptionsInfo[x___]/;Length[{x}]=!=2:= "never happens"/;Message[ResetOptionsInfo::argnum,Length[{x}]]; (******************************************************) ClearOptionsInfo[context_String,removeList:{((Rule|RuleDelayed)[ (_Symbol|_String),_Symbol|{__Symbol}])..}]:= catchInternalError@setOptionInfo[context, clearedOptionInfo[optionInfo[context],removeList]]; (* Error cases *) ClearOptionsInfo[_,_]:= "never happens"/;Message[ClearOptionsInfo::badargs]; ClearOptionsInfo[x___]/;Length[{x}]=!=2:= "never happens"/;Message[ClearOptionsInfo::argnum,Length[{x}]]; (********************************************************************) (* Switching on/off protection of an entire context *) (********************************************************************) TurnOptionChecksOn[]:=TurnOptionChecksOn[$Context]; TurnOptionChecksOn[context_String]/;optionInfo[context]=!={}:= catchInternalError@CompoundExpression[ update[context]; TurnOptionChecksOff[context]; OptionCheckOn/@DeclaredFunctionsWithOptions[context]; DeclaredFunctionsWithOptions[context] ]; (* Error cases *) TurnOptionChecksOn[x_String]:= "never happens"/;Message[TurnOptionChecksOn::noinfo,x]; TurnOptionChecksOn[x_]/;Head[x]=!=String:= "never happens"/;Message[TurnOptionChecksOn::badarg]; TurnOptionChecksOn[x___]/;Not[MatchQ[Length[{x}],0|1]]:= "never happens"/;Message[TurnOptionChecksOn::argnum,Length[{x}]]; (******************************************************) TurnOptionChecksOff[context_String]:= catchInternalError@Map[(OptionCheckOff[#];#)&, DeclaredFunctionsWithOptions[context]]; (* Error cases *) TurnOptionChecksOff[x_]:= "never happens"/;Message[TurnOptionChecksOff::badarg]; TurnOptionChecksOff[x___]/;Length[{x}]=!=1:= "never happens"/;Message[TurnOptionChecksOff::argnum,Length[{x}]]; ]; (* End of large If *) (********************************************************************) (********************************************************************) (* END OF MAIN CODE *) (********************************************************************) (********************************************************************) If[PackageOptionChecksAvailableQ[], End[], (* else *) Clear[PackageOptionChecksAvailableQ] ]; If[PackageOptionChecksAvailableQ[], Protect[OptionCheckOn,OptionCheckOff,OptionCheckIsOn, SetOptionsInfo,TurnOptionChecksOn,TurnOptionChecksOff, ResetOptionsInfo,GetOptionsInfo,ClearOptionsInfo, PackageOptionChecksAvailableQ]; ]; EndPackage[];