3

When programming, I try and name my functions and arguments clearly and document them with usage:: so I can keep track of everything; e.g.,

functionWithALongName::usage = 
  "functionWithALongName[x] transforms x into y with options Opt1-> opt1..."

I'd like to automate this process so that the function name, functionWithALongName), the variables ,x, and the options are included in my documentation instead of having to write them all out.

I'm wondering (a) how do I reference the name of the function called and (b) if anyone has done this and would share their code so I don't have to reinvent the wheel?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
mikemtnbikes
  • 679
  • 3
  • 11
  • You'll get part fo the way there using Definition[functionWithALongName] – bill s Sep 06 '17 at 17:06
  • Take a look here: https://github.com/b3m2a1/mathematica-BTools – Szabolcs Sep 06 '17 at 17:15
  • @Szabolcs it's actually a different package. See this. Or this answer: https://mathematica.stackexchange.com/questions/154743/convert-a-notebook-directly-to-a-package-with-boiler-plate/154744#154744 – b3m2a1 Sep 06 '17 at 17:52
  • @Szabolcs that's way more complex than I'm looking for. I'd like to be able to reference it within, say, a StringFormat function. – mikemtnbikes Sep 07 '17 at 04:58
  • @bills I appreciate the comment, but I'd say that's about 1/4 where I want to go. – mikemtnbikes Sep 07 '17 at 04:59
  • To rephrase my question, is there a way to reference the function being called? e.g. functionWithALongName::usage = StringJoin["`` transforms x into wy with options...", internalFunctionThatReturnsName[]] – mikemtnbikes Sep 22 '17 at 17:06

1 Answers1

3

Here's a partial implementation, working from the DownValues and friends:

We start with a bunch of DownValues cleaning:

$usageTypeReplacements =
  {
   Integer :> int,
   Real :> float,
   String :> str,
   List :> list,
   Association :> assoc,
   Symbol :> sym
   };
$usageSymNames =
  {
   Alternatives -> alt,
   PatternTest -> test,
   Condition -> cond,
   s_Symbol :>
    RuleCondition[
     Symbol@
      ToLowerCase[StringTake[SymbolName[Unevaluated@s], UpTo[3]]],
      True
     ]
   };
symbolUsageReplacementPattern[names_, conts_] :=
  s_Symbol?(
     GeneralUtilities`HoldFunction[
      ! MatchQ[Context[#], conts] &&
       ! 
        MemberQ[$ContextPath, Context[#]] &&
       ! 
        KeyMemberQ[names, SymbolName@Unevaluated[#]]
      ]
     ) :>
   RuleCondition[
    ToExpression@
     Evaluate[$Context <>

       With[{name = SymbolName@Unevaluated[s]},
        If[StringLength@StringTrim[name, "$"] > 0,
         StringTrim[name, "$"],
         name
         ]
        ]
      ],
    True];
usagePatternReplace[
   vals_,
   reps_: {}
   ] :=
  With[{
    names = AssociationMap[Null &, {}(*Names[]*)],
    conts = 
     Alternatives @@ {"System`", "FrontEnd`", "PacletManager`", 
       "Internal`"}
    },
   Replace[
      Replace[
       #,
        {
        Verbatim[HoldPattern][a___] :> a
        },
       {2, 10}
       ],
      Join[$usageTypeReplacements, $usageSymNames],
       Depth[#]
      ] &@
    ReplaceRepeated[
     FixedPoint[
      Replace[
        #,
        {
         Verbatim[Pattern][_, e_] :>
          e,
         Verbatim[HoldPattern][Verbatim[Pattern][_, e_]] :>

          HoldPattern[e],
         Verbatim[HoldPattern][Verbatim[HoldPattern][e_]] :>

           HoldPattern[e]
         },
        1
        ] &,
      vals
      ],
     Flatten@{
       reps,
       Verbatim[PatternTest][_, ColorQ] :>
        color,
       Verbatim[PatternTest][_, ImageQ] :>
        im,
       Verbatim[Optional][name_, _] :>
        name,
       Verbatim[Pattern][_, _OptionsPattern] :>
        Sequence[],
       Verbatim[Pattern][name_, _] :>
        name,
       Verbatim[PatternTest][p_, _] :>
        p,
       Verbatim[Condition][p_, _] :>
        p,
       Verbatim[Alternatives][a_, ___][___] |
         Verbatim[Alternatives][a_, ___][___][___] |
         Verbatim[Alternatives][a_, ___][___][___][___] |
         Verbatim[Alternatives][a_, ___][___][___][___][___] |
         Verbatim[Alternatives][a_, ___][___][___][___][___][___] :>
        a,
       Verbatim[Alternatives][a_, ___] :>
        RuleCondition[
         Blank[
          Replace[Hold@a,
           {
            Hold[p : Verbatim[HoldPattern][_]] :>
             p,
            Hold[e_[___]] :> e,
            _ :> a
            }
           ]
          ],
         True
         ],
       Verbatim[Verbatim][p_][a___] :>
        p,
       Verbatim[Blank][] :>
        expr,
       Verbatim[Blank][
         t : Alternatives @@ Keys[$usageTypeReplacements]] :>

        RuleCondition[
         Replace[t,
          $usageTypeReplacements
          ],
         True
         ],
       Verbatim[Blank][t_] :>
        t,
       Verbatim[BlankSequence][] :>

        Sequence @@ ToExpression[{"expr1", "expr2"}],
       Verbatim[BlankNullSequence][] :>
        Sequence[],
       symbolUsageReplacementPattern[names, conts],
       h_[a___, Verbatim[Sequence][b___], c___] :> h[a, b, c]
       }
     ]
   ];

Then we take a function that uses this to build out a little template for the DownValues:

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]
        },
       (* change this chunk to customize the template *)
       If[! StringContainsQ[uu, meuu],
        meuu <> " " <>


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

generateSymbolUsage /@ {generateSymbolUsage, 
  autoCompletionsExtractSeeder}

{{"generateSymbolUsage[f, def]"}, \
{"autoCompletionsExtractSeeder[test, n]", 
  "autoCompletionsExtractSeeder[alt, n]", 
  "autoCompletionsExtractSeeder[pat, n]"}}

This doesn't fill out the rest of the template, but we can tweak it like so:

generateSymbolUsage2[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[
       {
        aerger =
         {ReleaseHold@
           Map[
            Function[
             Null,
             ToString[#, InputForm],
             HoldFirst
             ],
            Hold[a]
            ]},
        opa = ToString[#, InputForm] & /@ Options[s],
        meuu = ToString[Unevaluated[s[a]], InputForm]
        },
       TemplateApply[
        meuu <> 
         " takes `nopenopenope`argument`argplur` \
`argggggggggggg``hasopa``oppppppppp`",
        <|

         "nopenopenope" -> If[Length@aerger == 0, "no ", ""],
         "argplur" -> If[Length@aerger == 1, "", "s"],
         "argggggggggggg" ->
          Switch[Length@aerger,
           0 | 1,
           StringJoin@aerger,
           2,
           StringRiffle[aerger, " and "],
           _,
           StringJoin@
            Insert[
             Riffle[aerger, ", "],
             "and " ,
             -2
             ]
           ],
         "hasopa" -> If[Length@opa > 0, " and options ", ""],
         "oppppppppp" ->
          Switch[Length@opa,
           0 | 1,
           StringJoin@opa,
           2,
           StringRiffle[opa, " and "],
           _,
           StringJoin@
            Insert[
             Riffle[opa, ", "],
             "and " ,
             -2
             ]
           ]
         |>
        ]
       ],
     _ -> Nothing
     },
    {1}
    ]
   ];
generateSymbolUsage2~SetAttributes~HoldFirst;

and then we're like 80% of the way there:

StringRiffle[generateSymbolUsage2@#, "\n"] & /@ {generateSymbolUsage2,
   AutoSyntax}

{"generateSymbolUsage2[f, def] takes arguments f and def", \
"AutoSyntax[f] takes argument f and options \"SyntaxInformation\" -> \
{}, \"Autocompletions\" -> {}, \"UsageMessages\" -> {}, \"SetSyntax\" \
-> False, and \"GatherInfo\" -> True"}

This is undoubtedly not my most robust piece of code ever, though.

b3m2a1
  • 46,870
  • 3
  • 92
  • 239
  • I belief this answer is incomplete and requires the function getCodeValues that can be found in your other answer https://mathematica.stackexchange.com/a/164008/45020.

    Note that it is used here but never defined. That answer might well contain a more complete answer in general so I think a link to that answer might be useful here.

    – Kvothe Apr 13 '23 at 15:01