11
$\begingroup$

I mostly write my packages within the Mathematica FE, but when I develop I never really think all the usage messages, autocompletes, syntax info, and argx patterns that a well developed function would have. And then once it comes time to put them in, I'm generally too lazy to do it.

Can we do this all automatically?

$\endgroup$
  • $\begingroup$ You have an answer already lined up? Or is this an actual question? $\endgroup$ – Szabolcs Jan 18 '18 at 22:19
  • $\begingroup$ @Szabolcs I do but I have to pare it down to length... I guess the question posted but the answer didn't. $\endgroup$ – b3m2a1 Jan 18 '18 at 22:19
  • $\begingroup$ The answer showed up now. $\endgroup$ – Szabolcs Jan 18 '18 at 22:24
  • $\begingroup$ @Szabolcs I had to excise a subsection, but yeah, it's down below the body length limit now. $\endgroup$ – b3m2a1 Jan 18 '18 at 22:25
  • $\begingroup$ Post two answers as a workaround. $\endgroup$ – Szabolcs Jan 18 '18 at 22:26
12
$\begingroup$

The answer is a qualified yes.

Yes, we can do all of this, but it won't be as finely tuned as if we did it by hand. There are 4 things we need to handle, "usage" messages, autocompletions, SyntaxInformation, and "argx" error patterns.

I've put this all in a package, so if you just want to use it, scroll to the bottom, otherwise we'll through each step individually:

( N.B. The package version has a number of little bug fixes and will be actively maintained, please use it until I can make sure all the fixes are in place and merge them here. )

Common Code

I had one function common to all the parts of this system:

getCodeValues[f_Symbol,
   vs :
    {Repeated[
      OwnValues | DownValues | SubValues | UpValues]} : {OwnValues, 
     DownValues, SubValues, UpValues}
   ] :=

  If[Intersection[Attributes@f, { ReadProtected, Locked}] === { 
     Locked, ReadProtected},
   {},
   Join @@ Map[#[f] &, vs]
   ];
getCodeValues~SetAttributes~HoldFirst

It'll keep popping up as we go

Usage Messages

Cleaning the patterns

The first step in generating these messages is knowing what we need from them. In general we want a cleaned form of the arguments to the function, any existing "usage" messages, and the function name. If we have a function:

test[a_, b_, c_]:=...

we want some usage that starts:

"test[a, b, c] ..."

so that it can be used via the FE MakeTemplate system (see more of Itai's answers to get a sense for how this works)

Of course, for more complicated patterns we need to be smarter about how we clean. I won't cover all of the cases I tried to handle, but in general I tried to reduce everything to a type or to a Pattern name so that I could generate nice Symbol forms of the variables.

In the interest of space (as I would otherwise hit the body-length limit on this post), you can see where I did this before in my answer to How can I automatically generate usage messages?

Generating the messages

Then we clean and call ToString on the DownValues to generate usages:

generateSymbolUsage[f_, 
   defaultMessages : {(_Rule | _RuleDelayed) ...} : {}] :=
  With[
   {
    uml =
     Replace[defaultMessages,
      {
       (h : Rule | RuleDelayed)[Verbatim[HoldPattern][pat___], m_] :>
        h[HoldPattern[Verbatim[HoldPattern][pat]], m],
       (h : Rule | RuleDelayed)[pat___, m_] :>
        h[Verbatim[HoldPattern][pat], m],
       _ -> Nothing
       },
      {1}
      ]
    },
   Replace[
    DeleteDuplicates@usagePatternReplace[Keys@getCodeValues[f]],
    {
     Verbatim[HoldPattern][s_[a___]] :>
      With[
       {
        uu =
         StringTrim@
          Replace[HoldPattern[s[a]] /. uml,

           Except[_String] :>

            Replace[s::usage, Except[_String] -> ""]
           ],
        sn = ToString[Unevaluated@s],
        meuu = ToString[Unevaluated[s[a]], InputForm]
        },
       StringReplace["FEInfoExtractor`Private`" -> ""]@
        If[! StringContainsQ[uu, meuu],
         meuu <> " " <>
          Which[
           uu == "",

           "has no usage message", ! 
            StringStartsQ[uu, 
             sn | (Except[WordCharacter] .. ~~ "RowBox[{" ~~ 
                Except[WordCharacter] ... ~~ sn)],
           uu,
           True,
           ""
           ],
         StringCases[uu, 
           (StartOfLine | StartOfString) ~~ Except["\n"] ... ~~ meuu ~~
             Except["\n"] ... ~~ EndOfLine,
           1
           ][[1]]
         ]
       ],
     _ -> Nothing
     },
    {1}
    ]
   ];
generateSymbolUsage~SetAttributes~HoldFirst;

You can then use this to build a decent "usage" message that will fill via template

test::usage = "my test function";
test[a_] := a;
generateSymbolUsage@test

{"test[a] my test function"}

Autocompletions

To get a first sense for how this works look at this post by Szabolcs.

I've been making good use of them for my own things, and have a little system to more conveniently define autocompletion types:

Autocompletion Aliases

First I just defined a bunch of aliased forms for the possible autocompletions:

$autoCompletionFormats =
  Alternatives @@ Join @@ {
     Range[0, 9],
     {
      _String?(FileExtension[#] === "trie" &),
      {
       _String | (Alternatives @@ Range[0, 9]) | {__String},
       (("URI" | "DependsOnArgument") -> _) ...
       },
      {
       _String | (Alternatives @@ Range[0, 9]) | {__String},
       (("URI" | "DependsOnArgument") -> _) ...,
       (_String | (Alternatives @@ Range[0, 9]) | {__String})
       },
      {
       __String
       }
      },
     {
      "codingNoteFontCom",
      "ConvertersPath",
      "ExternalDataCharacterEncoding",
      "MenuListCellTags",
      "MenuListConvertFormatTypes",
      "MenuListDisplayAsFormatTypes",
      "MenuListFonts",
      "MenuListGlobalEvaluators",
      "MenuListHelpWindows",
      "MenuListNotebookEvaluators",
      "MenuListNotebooksMenu",
      "MenuListPackageWindows",
      "MenuListPalettesMenu",
      "MenuListPaletteWindows",
      "MenuListPlayerWindows",
      "MenuListPrintingStyleEnvironments",
      "MenuListQuitEvaluators",
      "MenuListScreenStyleEnvironments",
      "MenuListStartEvaluators",
      "MenuListStyleDefinitions",
      "MenuListStyles",
      "MenuListStylesheetWindows",
      "MenuListTextWindows",
      "MenuListWindows",
      "PrintingStyleEnvironment",
      "ScreenStyleEnvironment",
      "Style"
      }
     };
$autocompletionAliases =
  {
   "None" | None | Normal -> 0,
   "AbsoluteFileName" | AbsoluteFileName -> 2,
   "FileName" | File | FileName -> 3,
   "Color" | RGBColor | Hue | XYZColor -> 4,
   "Package" | Package -> 7,
   "Directory" | Directory -> 8,
   "Interpreter" | Interpreter -> 9,
   "Notebook" | Notebook -> "MenuListNotebooksMenu",
   "StyleSheet" -> "MenuListStylesheetMenu",
   "Palette" -> "MenuListPalettesMenu",
   "Evaluator" | Evaluator -> "MenuListGlobalEvaluators",
   "FontFamily" | FontFamily -> "MenuListFonts",
   "CellTag" | CellTags -> "MenuListCellTags",
   "FormatType" | FormatType -> "MenuListDisplayAsFormatTypes",
   "ConvertFormatType" -> "MenuListConvertFormatTypes",
   "DisplayAsFormatType" -> "MenuListDisplayAsFormatTypes",
   "GlobalEvaluator" -> "MenuListGlobalEvaluators",
   "HelpWindow" -> "MenuListHelpWindows",
   "NotebookEvaluator" -> "MenuListNotebookEvaluators",
   "PrintingStyleEnvironment" | PrintingStyleEnvironment ->

    "PrintingStyleEnvironment",
   "ScreenStyleEnvironment" | ScreenStyleEnvironment ->

    ScreenStyleEnvironment,
   "QuitEvaluator" -> "MenuListQuitEvaluators",
   "StartEvaluator" -> "MenuListStartEvaluators",
   "StyleDefinitions" | StyleDefinitions ->

    "MenuListStyleDefinitions",
   "CharacterEncoding" | CharacterEncoding |
     ExternalDataCharacterEncoding ->

    "ExternalDataCharacterEncoding",
   "Style" | Style -> "Style",
   "Window" -> "MenuListWindows"
   };
$autocompletionTable = {
   f : $autoCompletionFormats :> f,
   Sequence @@ $autocompletionAliases,
   s_String :> {s}
   };

Then we compile these aliases down to the appropriate form:

autocompletionPreCompile[v : Except[{__Rule}, _List | _?AtomQ]] :=

  Replace[
    Flatten[{v}, 1],
   $autocompletionTable,
   {1}
   ];
autocompletionPreCompile[o : {__Rule}] :=
  Replace[o,
   (s_ -> v_) :>
    (
     Replace[s, _Symbol :> SymbolName[s]] ->

      autocompletionPreCompile[v]
     ),
   1
   ];
autocompletionPreCompile[s : Except[_List], v_] :=

  autocompletionPreCompile[{s -> v}];
autocompletionPreCompile[l_, v_] :=
  autocompletionPreCompile@
   Flatten@{
     Quiet@
      Check[
       Thread[l -> v],
       Map[l -> # &, v]
       ]
     };

Finally here's a quick function for actually setting these:

addAutocompletions[
   pats : {(_String -> {$autoCompletionFormats ..}) ..}] :=

  If[$Notebooks &&

    Internal`CachedSystemInformation["FrontEnd", "VersionNumber"] > 
     10.0,
   Scan[
    FE`Evaluate[FEPrivate`AddSpecialArgCompletion[#]] &,
    pats
    ];
   pats,
   $Failed
   ];
addAutocompletions[pat : (_String -> {$autoCompletionFormats ..})] :=

    addAutocompletions[{pat}];

addAutocompletions[a__] /; (! TrueQ@$recursionProtect) :=

  Block[{$recursionProtect = True},
   Replace[
    addAutocompletions@autocompletionPreCompile[a],
    _addAutocompletions -> $Failed
    ]
   ];

If you want to test it out you can try this:

addAutocompletions[
  addAutocompletions,
  {
   None,
   Replace[Keys[$autocompletionAliases],
    Verbatim[Alternatives][s_, ___] :> s,
    1
    ]
   }
  ];

Extracting the completions

With that in hand we move to what sorts of completions we can extract. We'll scan over the DownValues to find one of three things:

  • Colors
  • Files
  • Alternatives of Strings

The code then constructs the appropriate positional lists for this:

Attributes[autoCompletionsExtractSeeder] =
  {
   HoldFirst
   };
autoCompletionsExtractSeeder[
   HoldPattern[Verbatim[PatternTest][_, ColorQ]] |
    (Blank | BlankSequence)[Hue | RGBColor | XYZColor | LABColor],
   n_
   ] := Sow[n -> "Color"];
autoCompletionsExtractSeeder[
   HoldPattern[Verbatim[PatternTest][_, DirectoryQ]],
   n_
   ] := Sow[n -> "Directory"];
autoCompletionsExtractSeeder[
   HoldPattern[Verbatim[PatternTest][_, FileExistsQ]] |
    (Blank | BlankSequence)[File] | _File,
   n_
   ] := Sow[n -> "FileName"];
autoCompletionsExtractSeeder[
   Verbatim[Alternatives][s__String],
   n_
   ] :=
  Sow[n -> {s}];
autoCompletionsExtractSeeder[
   Verbatim[Pattern][_, b_],
   n_
   ] := autoCompletionsExtractSeeder[b, n];
autoCompletionsExtractSeeder[
   Verbatim[Optional][a_, ___],
   n_
   ] := autoCompletionsExtractSeeder[a, n];
(* actual extractor; delegates to the seeder *)
Attributes[autoCompletionsExtract] =
  {
   HoldFirst
   };
autoCompletionsExtract[
   Verbatim[HoldPattern][_[a___]]] :=
  {ReleaseHold@
    MapIndexed[
     Function[Null, autoCompletionsExtractSeeder[#, #2[[1]]], 
      HoldAllComplete],
     Hold[a]
     ]
   };
autoCompletionsExtract[f_Symbol] :=
 Flatten@
  Reap[
    autoCompletionsExtract /@

     Keys@getCodeValues[f, {DownValues}]
    ][[2]]
(* builds list from extracted data *)
generateAutocompletions[
   f : _Symbol : None, 
   otherAutos : {(_Integer -> _) ...} : {}
   ] :=
  With[
   {
    gg =
     Join[
      GroupBy[
       If[Unevaluated[f] =!= None,
        autoCompletionsExtract[f],
        {}
        ],
       First -> Last,
       Replace[{s_, ___} :> s]
       ],
      GroupBy[
       otherAutos,
       First -> Last
       ]
      ]
    },
   With[{km = Max@Append[Keys@gg, 0]},
    Table[
     Lookup[gg, i, None],
     {i, km}
     ]
    ]
   ];
SetAttributes[generateAutocompletions, HoldFirst];

We can test this as follows:

test // Clear
test[a_, col : _?ColorQ, f_String?FileExistsQ, "a" | "b" | "c"] := 
  Darker@col;
generateAutocompletions@test

{None, "Color", "FileName", {"a", "b", "c"}}

SyntaxInformation

For this we have four distinct things to handle:

  • ArgumentsPattern
  • LocalVariables
  • ColorEqualSigns
  • OptionNames

I only adequately handle the first and last, so I'll only really discuss those.

ArgumentsPattern

For this, we reduce the DownValues to a more canonical form. First a pattern reduction function:

reducePatterns[p_] :=
  Replace[
    p,
    {
     Except[
       _Pattern | _Optional | _Blank | _BlankSequence | 
        _BlankNullSequence | _PatternSequence | _OptionsPattern |
        _Repeated | _RepeatedNull | _Default | _PatternTest | \
_Condition
       ] -> _
     },
    {2}
    ] //. {
    _Blank -> "Blank",
    _BlankSequence -> "BlankSequence",
    _BlankNullSequence -> "BlankNullSequence",
    _OptionsPattern :> "OptionsPattern",
    Verbatim[HoldPattern][
       Verbatim[Pattern][a_, b_]
       ] | Verbatim[Pattern][a_, b_] :> b,
    (PatternTest | Condition)[a_, b_] :> a,
    Verbatim[Optional][a_, ___] :> "Optional"[a],
    _Default -> "Default",
    Verbatim[Repeated][_] -> "Repeated"[Infinity],
    Verbatim[RepeatedNull][_] -> "RepeatedNull"[Infinity],
    Verbatim[Repeated][_, s_] :> "Repeated"[s],
    Verbatim[RepeatedNull][_, s_] :> "RepeatedNull"[s]
    };

Whose output I then rebuild into more standardized lists:

reconstructPatterns[p_] :=
  p //. {
     "Optional"[a_] :> Optional[a],
     "Default" -> _.,
     "OptionsPattern" -> OptionsPattern[],
     "Blank" -> _,
     "BlankSequence" -> __,
     "BlankNullSequence" -> ___,
     "Repeated"[Infinity] -> Repeated[_],
     "RepeatedNull"[Infinity] -> RepeatedNull[_],
     "Repeated"[s_] :> Repeated[_, s],
     "RepeatedNull"[s_] :> RepeatedNull[_, s]
     } // Flatten;

This gives us an "ArgumentsPattern" for each individual DownValues. We then need to merge these into a maximal "ArgumentsPattern", starting by assigning a pattern Span:

argPatPartLens[patList_] :=
  Thread[
   patList ->
    Replace[
     patList,
     {
      _Blank -> 1 ;; 1,
      _BlankSequence -> 1 ;; Infinity,
      _BlankNullSequence -> 0 ;; Infinity,
      Verbatim[Repeated][_] -> 1 ;; Infinity,
      Verbatim[RepeatedNull][_] -> 0 ;; Infinity,
      Verbatim[Repeated][_, {M_}] :> 1 ;; M,
      Verbatim[RepeatedNull][_, {M_}] :> 0 ;; M,
      (Repeated | RepeatedNull)[_, {M_}] :> ;; M,
      (Repeated | RepeatedNull)[_, {m_, M_}] :> m ;; M,
      _Optional -> 0 ;; 1,
      _Default -> 0 ;; 1,
      _OptionsPattern -> 0 ;; Infinity,
      _ -> 1 ;; 1
      },
     {1}
     ]
   ];

And then merge these term-by-term (i.e. MapThread over the DownValues) either taking the one that spans all of the others or building a new one if there is no pattern that encompasses them all:

mergeArgPats[pats_, returnNum : False | True : False] :=
 Module[
  {
   reppedPats = argPatPartLens /@ pats,
   mlen,
   paddies,
   werkit = True,
   patMaxes,
   patMins,
   patChoices,
   patNs,
   patCho
   },
  mlen = Max[Length /@ reppedPats];
  paddies = PadRight[#, mlen, _. -> 0 ;; 1] & /@ reppedPats;
  MapThread[
   Function[
    patMins = MinimalBy[{##}, #[[2, 1]] &];
    patMaxes = MaximalBy[{##}, #[[2, 2]] &];
    patChoices = Intersection[patMins, patMaxes];
    patNs = {patMins[[1, 2, 1]], patMaxes[[1, 2, 2]]};
    patCho =
     If[Length@patChoices > 0,
      SortBy[patChoices,
        Switch[#[[1]],
          _OptionsPattern,
          0,
          _RepeatedNull | _Repeated,
          1,
          _Optional | _Default,
          2,
          _,
          3
          ] &
        ][[1, 1]],
      Replace[ patNs,
       {
        {0, 1} -> _.,
        {1, Infinity} -> __,
        {0, Infinity} -> ___,
        {m_, n_} :> Repeated[_, {m, n}]
        }
       ]
      ];
    If[returnNum, patCho -> patNs, patCho]
    ],
   paddies
   ]
  ]

Then I just apply this to the DownValues:

generateSIArgPat[f_] :=

  With[{dvs = Keys@getCodeValues[f, {DownValues}]},
   mergeArgPats@
    DeleteDuplicates[
     reconstructPatterns /@
      ReplaceAll[
       reducePatterns /@ dvs,
       {
        (f | HoldPattern) -> List
        }
       ]
     ]
   ];
generateSIArgPat~SetAttributes~HoldFirst

test2[a_] := a;
test2[a_, b_, c_] := b;
test2["a" | "b" | "c", asd_] := asd
test2 // generateSIArgPat

{_, _., _.}

OptionNames

This is pretty trivial. Just take the Options and ToString their Keys:

generateSIOpNames[f_] :=
  ToString[#, InputForm] & /@ Keys@Options[f];
generateSIOpNames~SetAttributes~HoldFirst

Total function

We'll then take two other functions which I generally don't use to build a total generator:

generateSILocVars[f_] :=

  With[{att = Attributes[f], 
    dvs = Keys@getCodeValues[f, {DownValues}]},
   Which[
    MemberQ[att, HoldAll],
    {1, Infinity},
    MemberQ[att, HoldRest],
    {2, Infinity},
    MemberQ[att, HoldFirst],
    {1}
    ]
   ];
generateSILocVars~SetAttributes~HoldFirst
generateSIColEq[f_] :=

  With[{dvs = Keys@getCodeValues[f, {DownValues}]},
   Replace[{a_, ___} :> a]@
    MaximalBy[
     MinMax@Flatten@Position[#, _Equal, 1] & /@ dvs,
     Apply[Subtract]@*Reverse
     ]
   ];
generateSIColEq~SetAttributes~HoldFirst
Options[generateSyntaxInformation] =
  {
   "ArgumentsPattern" -> Automatic,
   "LocalVariables" -> None,
   "ColorEqualSigns" -> None,
   "OptionNames" -> Automatic
   };
Attributes[generateSyntaxInformation] =
  {
   HoldFirst
   };
generateSyntaxInformation[
   f_,
   ops : OptionsPattern[]
   ] :=
  {
   "ArgumentsPattern" ->
    Replace[OptionValue["ArgumentsPattern"],
     Automatic :> generateSIArgPat[f]
     ],
   "LocalVariables" ->
    Replace[OptionValue["LocalVariables"],
     Automatic :> generateSILocVars[f]
     ],
   "ColorEqualSigns" ->
    Replace[OptionValue["LocalVariables"],
     Automatic :> generateSIColEq[f]
     ],
   "OptionNames" ->
    Replace[OptionValue["OptionNames"],
     Automatic :> generateSIOpNames[f]
     ]
   };

And here's what that output looks like:

generateSyntaxInformation@test2

{"ArgumentsPattern" -> {_, _., _.}, "LocalVariables" -> None, 
 "ColorEqualSigns" -> None, "OptionNames" -> {}}

generateSyntaxInformation@generateSyntaxInformation

{"ArgumentsPattern" -> {_, OptionsPattern[]}, 
 "LocalVariables" -> None, "ColorEqualSigns" -> None, 
 "OptionNames" -> {"\"ArgumentsPattern\"", "\"LocalVariables\"", 
   "\"ColorEqualSigns\"", "\"OptionNames\""}}

ArgX Patterns

Here we'll need a get and set mechanism, just like for the autocompletions.

Extracting the arg count

This will use the same toolchain as we had for the "ArgumentsPattern". Now we just need to take the min and max argument numbers and also determine if we have a possible OptionsPattern (if we do we want to ignore it when counting arguments).

generateArgCount[f_] :=
  Module[
   {
    dvs = Keys@getCodeValues[f, {DownValues}],
    patsNums,
    patsMax,
    patsMin,
    patsTypes,
    doNonOp = False
    },
   patsNums =
    mergeArgPats[
     DeleteDuplicates[
      reconstructPatterns /@
       ReplaceAll[
        reducePatterns /@ dvs,
        {
         (f | HoldPattern) -> List
         }
        ]
      ],
     True
     ];
   patsTypes = patsNums[[All, 1]];
   patsMin =
    Block[{noopnoop = False},
     MapThread[
      If[noopnoop,
        0,
        If[MatchQ[#, _OptionsPattern],
         doNonOp = True;
         noopnoop = True;
         0,
         #2
         ]
        ] &,
      {
       patsTypes,
       patsNums[[All, 2, 1]]
       }
      ]
     ];
   patsMax =
    Block[{noopnoop = False},
     MapThread[
      If[noopnoop,
        0,
        If[MatchQ[#, _OptionsPattern],
         doNonOp = True;
         noopnoop = True;
         0,
         #2
         ]
        ] &,
      {
       patsTypes,
       patsNums[[All, 2, 2]]
       }
      ]
     ];
   {"MinArgs" -> Total[patsMin], "MaxArgs" -> Total[patsMax], 
    "OptionsPattern" -> doNonOp}
   ];
generateArgCount~SetAttributes~HoldFirst

Setting the arg count

Here we'll just emulate the builtins:

setArgCount[f_Symbol, minA : _Integer, maxA : _Integer, 
   noo : True | False] :=
  f[argPatLongToNotDupe___] :=
   (
    1 /; (ArgumentCountQ[f,
        Length@If[noo,
          Replace[Hold[argPatLongToNotDupe], 

          Hold[argPatLongToNotDupe2___, (_Rule | _RuleDelayed | \
{(_Rule | _RuleDelayed) ..}) ...] :> Hold[argPatLongToNotDupe2]
          ], Hold[argPatLongToNotDupe]], minA, maxA]; False)
    );
setArgCount~SetAttributes~HoldFirst

Putting it all together

Extracting all the data

Finally, we'll want a function that extracts all of these pieces:

Options[generateAutoFrontEndInfo] =
  {
   "SyntaxInformation" -> {},
   "Autocompletions" -> {},
   "UsageMessages" -> {},
   "ArgCount" -> Automatic
   };
generateAutoFrontEndInfo[
   f_Symbol,
   ops : OptionsPattern[]
   ] :=
  {
   "UsageMessages" ->
    generateSymbolUsage[f,
     Cases[
      Flatten@List[OptionsPattern["UsageMessages"]],
      _Rule | _RuleDelayed
      ]
     ],
   "SyntaxInformation" ->
    generateSyntaxInformation[f,
     OptionValue["SyntaxInformation"]
     ],
   "Autocompletions" ->
    generateAutocompletions[f,
     OptionValue["Autocompletions"]
     ],
   "ArgCount" ->
    Replace[OptionValue@"ArgCount",
     Except[
       KeyValuePattern[
        {"MinArgs" -> _Integer, "MaxArgs" -> _Integer | Infinity, 
         "OptionsPattern" -> (True | False)}
        ]
       ] :> generateArgCount[f]
     ]
   };
generateAutoFrontEndInfo~SetAttributes~HoldFirst

Setting / Dumping the data

Once we have this there are two possible things we might want to do.

  • Dump to a file
  • Set it on the symbol

If we do the first, we'll want a Hold-wrapped expression. If we do the latter life is a bit simpler. Here's a function (the only one I expose in my packaged up version of this) to do that:

Options[AutoFrontEndInfo] =
  {
   "SyntaxInformation" -> {},
   "Autocompletions" -> {},
   "UsageMessages" -> {},
   "SetInfo" -> False,
   "GatherInfo" -> True
   };
AutoFrontEndInfo[f_Symbol, o : OptionsPattern[]] :=
 Module[
  {
   sinfBase =
    If[OptionValue["GatherInfo"] =!= False,
     generateAutoFrontEndInfo[f, 
      FilterRules[{o}, Options[generateAutoFrontEndInfo]]], 
     Flatten[{o, Options[AutoFrontEndInfo]}]
     ],
   sops = FilterRules[{o}, Options[generateAutoFrontEndInfo]],
   as = {},
   si,
   um,
   ac,
   argX,
   set = TrueQ@OptionValue["SetInfo"]
   },
  si =
   Replace[
    Replace[Lookup[sinfBase, "SyntaxInformation"],
     Except[{(_String -> _) ..}] :>
      Lookup[as, "SyntaxInformation",
       Lookup[Set[as, generateAutoFrontEndInfo[f, sops]], 
        "SyntaxInformation"]
       ]
     ],
    {
     (Except[_String] -> _) -> Nothing,
     (k_ -> None) :> k -> {}
     },
    {1}
    ];
  um =
   Replace[Lookup[sinfBase, "UsageMessages"],
    Except[{__String}] :>
     Lookup[as, "UsageMessages",
      Lookup[Set[as, generateAutoFrontEndInfo[f, sops]], 
       "UsageMessages"]
      ]
    ];
  um = StringRiffle[um, "\n"];
  ac =
   Replace[Lookup[sinfBase, "Autocompletions"],
    Except[_List] :>
     Lookup[as, "Autocompletions",
      Lookup[Set[as, generateAutoFrontEndInfo[f, sops]], 
       "Autocompletions"]
      ]
    ];
  argX =
   Association@
    Replace[Lookup[sinfBase, "ArgCount"],
     Except[{"MinArgs" -> _Integer, 
        "MaxArgs" -> _Integer | Infinity, 
        "OptionsPattern" -> True | False}] :>
      Lookup[as, "ArgCount",
       Lookup[Set[as, generateAutoFrontEndInfo[f, sops]], "ArgCount"]
       ]
     ];
  If[set,
   SyntaxInformation[Unevaluated@f] = si;
   If[StringLength@um > 0, f::usage = um];
   If[Length@ac > 0, addAutocompletions[f, ac]];
   setArgCount[f, argX["MinArgs"], argX["MaxArgs"], 
    argX["OptionsPattern"]],
   (* dump to held expression for writing to file *)
   With[
    {
     si2 = si,
     um2 = um,
     acpat = autocompletionPreCompile[ac],
     minA = argX["MinArgs"],
     maxA = argX["MaxArgs"],
     noo = argX["OptionsPattern"]
     },
    Hold[
     SyntaxInformation[Unevaluated[f]] = si2;
     If[StringLength@um2 > 0, Set[f::usage, um2]];
     If[Length@acpat > 0,
      If[$Notebooks &&

        Internal`CachedSystemInformation["FrontEnd", 
          "VersionNumber"] > 10.0,
       FE`Evaluate[
        FEPrivate`AddSpecialArgCompletion[
         ToString[Unevaluated[f]] -> acpat]
        ]
       ];
      SetDelayed[
       f[argPatLongToNotDupe___],
       (
        1 /; (ArgumentCountQ[f,
            Length@If[noo,
              Replace[Hold[argPatLongToNotDupe], 

              Hold[argPatLongToNotDupe2___, (_Rule | _RuleDelayed | \
{(_Rule | _RuleDelayed) ..}) ...] :> Hold[argPatLongToNotDupe2]
              ], Hold[argPatLongToNotDupe]
             ], minA, maxA]; False)
        )
       ]
      ]
     ]
    ]
   ]
  ]

Here's an example of this at work (note that "SetInfo" actually sets the values, otherwise it just generates a form you can put in a file or tweak by hand):

test3[a_] := a;
test3[a_, b_, c_] := b;
test3["aaaa" | "bbbb" | "cccc", asd_] := asd
AutoFrontEndInfo[test3, "SetInfo" -> True]

The usages:

uuuuu

Autocomplete:

aaaaa

Argpat coloring:

pp1

pp2

And "argx" calls:

test3[]

test3::argb: test3 called with 0 arguments; between 1 and 3 arguments are expected.

test3[]

test3[1, 2, 3, 4]

test3::argb: test3 called with 4 arguments; between 1 and 3 arguments are expected.

test3[1, 2, 3, 4]

Package version

This is in a package on GitHub here.

You can then use this like so:

<< https://raw.githubusercontent.com/b3m2a1/mathematica-tools/master/FEInfoExtractor.wl
AutoFrontEndInfo@AutoFrontEndInfo
$\endgroup$
  • $\begingroup$ Hi, @b3m2a1. Thank you so much for such a through package. But how to clear info binding of the symbol? I found ClearAll doesn't work $\endgroup$ – matheorem Feb 5 '18 at 15:20
  • $\begingroup$ @matheorem I'm assuming the SyntaxInformation is giving you trouble? Let me think about this. The hard part is what to do with the "OptionNames". For the most part, though, SyntaxInformation[f]={"ArgumentsPattern"->{_,OptionsPattern[]}, "LocalVariables"->{}, "ColorEqualSigns"->{}};SyntaxInformation[f]=. will do it. $\endgroup$ – b3m2a1 Feb 5 '18 at 15:35
  • $\begingroup$ I found a made a mistake. I mixed my own autosyntax function with your package. I reboot kernel and load your package, and found that Needs["FEInfoExtractor"]; ClearAll[f]; f[x_] := x; AutoFrontEndInfo[f]; f[1, 2]` didn't give red color warning. Am I doing something wrong? $\endgroup$ – matheorem Feb 5 '18 at 15:48
  • $\begingroup$ @matheorem AutoFrontEndInfo generally just generates the info. Use AutoFrontEndInfo[f, "SetInfo" -> True] to get it to also set. I do this so I can build it into packages without having to load AutoFrontEndInfo. $\endgroup$ – b3m2a1 Feb 5 '18 at 15:53
  • $\begingroup$ Ok, got it. Sorry for my direct jumping to the very end, I should read it more carefully : ) However, there are at least two issues. First, the usage for test3["aaaa" | "bbbb" | "cccc", asd_] := asd is not generated properly. Second, What if I add new definition of test3, I found it doesn't work for newly added definition after rerun AutoFrontEndInfo $\endgroup$ – matheorem Feb 5 '18 at 16:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.