12
$\begingroup$

Cross posted on Wolfram Community


For such an increasingly relevant data type the Paclet has a particularly useless formatted form.

Can we do better?

In particular we'll want the Paclet Format to be attractive, show particularly relevant paclet info, and be customizable.

Here's my version, to get people started:

pacletKeyOrdering =
  {
   "Name", "Version", "Creator",
   "Description", "Root", "WolframVersion",
   "MathematicaVersion", "Internal", "Loading",
   "Qualifier", "SystemID", "BuildNumber",
   "Tags", "Icon", "Categories",
   "Authors", "Extensions"
   };
With[{keyOrdering = pacletKeyOrdering},
  pacletInfoAssociation[PacletManager`Paclet[k__]] := 
   KeySortBy[First@FirstPosition[keyOrdering, #] &]@
    With[
     {
      base =
       KeyMap[Replace[s_Symbol :> SymbolName[s]], <|k|>]
      },
     ReplacePart[base,
      "Extensions" ->
       AssociationThread[
        First /@ Lookup[base, "Extensions", {}],
        Association@*Rest /@ Lookup[base, "Extensions", {}]
        ]
      ]
     ]
  ];
If[! AssociationQ@$pacletIconCache, $pacletIconCache = <||>];
pacletGetIcon[a_Association] :=
  Replace[
   FileNames[
    Lookup[
     a,
     "Icon",
     "PacletIcon.m" | "PacletIcon.png"
     ],
    a["Location"]
    ],
   {
    {f_, ___} :>

     Lookup[$pacletIconCache, f, $pacletIconCache[f] = Import[f]],
    {} :>

     With[{f = 
        "https://github.com/b3m2a1/mathematica-BTools/raw/master/\
Resources/Icons/PacletIcon.png"
       },
      Image[
       Lookup[$pacletIconCache, f, $pacletIconCache[f] = Import[f]],
       ImageSize -> 28
       ]
      ]
    }
   ];
$formatPaclets = True;
Format[p_PacletManager`Paclet /;
    ($formatPaclets && AssociationQ@
       pacletInfoAssociation[p])] :=

  With[{a = pacletInfoAssociation[p]},
   RawBoxes@
    BoxForm`ArrangeSummaryBox[
     "Paclet",
     p,
     pacletGetIcon[a],
     KeyValueMap[
      BoxForm`MakeSummaryItem[
        {Row[{#, ": "}], #2},
        StandardForm
        ] &,
      a[[{"Name", "Version"}]]
      ],
     Join[
      {
       If[KeyMemberQ[a, "Location"],
        BoxForm`MakeSummaryItem[
         {Row[{"Location", ": "}],
          With[{l = a["Location"]},
           Button[
            Hyperlink[l],
            SystemOpen[l],
            Appearance -> None,
            BaseStyle -> "Hyperlink"
            ]
           ]},
         StandardForm
         ],
        Nothing
        ]
       },
      KeyValueMap[
       BoxForm`MakeSummaryItem[
         {Row[{#, ": "}], #2},
         StandardForm
         ] &,
       KeyDrop[a, {"Name", "Version", "Location"}]
       ]
      ],
     StandardForm
     ]
   ];
FormatValues[PacletManager`Paclet] =
  SortBy[
   FormatValues[PacletManager`Paclet],
   FreeQ[HoldPattern[$formatPaclets]]
   ];

This is what I use in my own packages. It makes paclets that look like this:

PacletFind /@ {"BTools", "ChemTools"} // Flatten

img

(sorry about bad raster quality)

Of course where this is actually useful is that it shows you what's inside the paclet:

PacletFind["MQTTLink"][[1]]

asdasd

$\endgroup$
  • 1
    $\begingroup$ Why don't you use the icon set in PacletInfo.m that's also displayed in the documentation? $\endgroup$ – Szabolcs Jan 17 '18 at 9:23
  • $\begingroup$ @Szabolcs I honestly didn’t know that existed (haven’t read the ”PacletInfo.m" question enough). I’ve now swapped things in the package where this is implemented to use that by default. $\endgroup$ – b3m2a1 Jan 18 '18 at 16:47
11
$\begingroup$

Demonstration

Here's what the end result looks like with my implementation:

enter image description here

I included information that I personally find important and useful. Of course, this is a bit subjective, and there's a tradeoff between conciseness/skimmability and completeness.

If the paclet has a standard icon that would be shown in the documentation centre, it will be used for formatting. Otherwise, a default icon is used (a rectangle here).

If the paclet has documentation, its name will be a clickable hyperlink (notice MaTeX in blue). This is currently determined based on the paclet name, not the documentation content that the paclet itself includes. In principle, the documentation for a loadable package may be contained within a different paclet than the package itself. Even in this case, it will be the package paclet whose name is clickable.

A few more examples (notice documentation link):

enter image description here


Implementation

The implementation below is commented so people can understand and modify it easily. It aims to be compatible with 10.0+.

In general, PacletManager` may not be in the context path (this happens when the context is local to the notebook). Thus, we start with

Needs["PacletManager`"]

If I were not lazy, I would just type out the context manually where needed.

First we remove the existing MakeBoxes definitions for Paclet:

FormatValues[Paclet] = DeleteCases[FormatValues[Paclet], HoldPattern[Verbatim[HoldPattern][MakeBoxes[___]]] :> ___];

Here's a default icon at a reasonable size. Normally one would design a better one.

$defaultPacletIcon = Graphics[{EdgeForm[Black], FaceForm[None], Rectangle[]}, ImageSize -> 23];

If the paclet comes with an icon, we use it. Otherwise, we use the default.

getPacletIcon[p_Paclet] :=     
 With[{file = FileNameJoin[{p["Location"], p["Thumbnail"]}]},
  If[FileType[file] === File,
   Show[Import[file], ImageSize -> 23], (* standard icon size is 46x46, resizing to 23 gives us a crisp result *)
   $defaultPacletIcon
  ]
 ]

This is a helper function to conditionally include information which may or may not be available. The value is optionally post-processed with fun (4th argument).

optionalItem[p_Paclet, name_, selector_, fun_: Identity] := 
 With[{value = p[selector]},
  If[value =!= "",
   BoxForm`SummaryItem[{name, fun[value]}],
   Unevaluated@Sequence[]
  ]
 ]

Now follows the main formatter. Note that if p is a Paclet expression, properties can be extracted using p["selector"]. This method is more robust than parsing the Paclet expression and much more robust than parsing the PacletInfo file directly. For example, "MathematicaVersion" will extract the compatibility version regardless of how it was specified in the PacletInfo file (MathematicaVersion, WolframVersion, "WolframVersion", etc.)

Paclet /: MakeBoxes[p_Paclet, form : (StandardForm | TraditionalForm)] :=     
 BoxForm`ArrangeSummaryBox[
  Paclet, (* head *)
  p, (* expression *)
  getPacletIcon[p], (* icon *)
  { (* always visible items *)
   With[{name = p["Name"]},
    BoxForm`SummaryItem[{
      "Name: ", 
      If[Documentation`ResolveLink["paclet:" <> name] =!= Null, (* simple way to check for working documentation *)
        Hyperlink[name, "paclet:" <> name], 
        name
      ]
    }]
   ],
   BoxForm`SummaryItem[{"Version: ", p["Version"]}]
  },
  { (* expandable items *)
   optionalItem[p, "Description: ", "Description"],
   optionalItem[p, "Creator: ", "Creator"],
   optionalItem[p, "URL: ", "URL", Hyperlink],
   optionalItem[p, "Compatibility: ", "MathematicaVersion"],
   optionalItem[p, "Extensions: ", "Extensions", #[[All, 1]] &]
   },
  form
 ]
$\endgroup$
  • 1
    $\begingroup$ Hm ... the documentation detection is not working great. Consider e.g. PacletFind["ServiceConnection_MailChimp"]. There's some room for improvement here, but I won't have time for that now. $\endgroup$ – Szabolcs Jan 17 '18 at 10:52
  • $\begingroup$ Nice! I didn't think to do a documentation link. That's good extension. You can also always test Documentation`ResolveLink["paclet:" <> StringReplace[ name, "ServiceConnection_ "->"ref/service"]] to handle service connection links. $\endgroup$ – b3m2a1 Jan 17 '18 at 14:00

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.