Ellipsis function for PowerShell

One more unintended thing arose from another post.

I was looking for a way to fit a lot of lengthy paths into console screen by removing less important parts. “Ellipsis” is a standard name for such a thing. You’re dealing with it every time you’re looking at “…” in place of something too long to fit into borders.

Here is my implementation for PowerShell.

Let’s say we have a string “C:\Windows\System32\WindowsPowerShell\v1.0\Modules\WindowsErrorReporting\WindowsErrorReporting.psm1” and no more than 50 characters on a screen to fit into. Showing it precisely isn’t really important, as user can figure out exact path if some details are provided.

The most trivial approach is to cut (50-3)/2 characters from left and right sides of a string, join them like $left + "..." + $right and get C:\Windows\System32\Wind...dowsErrorReporting.psm1. It works and may even be good enough in a lot of cases. But what I don’t like about it is that it is unaware of folder and file name borders, cutting right in the middle, and can be misleading sometimes.

After little research in Google, I found not so many solutions. With only one worth to be noted (no surprises, since it may seem trivial). But instead of just reimplementing it in PowerShell, I decided to do my own from scratch.

The code is available on GitHub and below is some kind of description. You may need to check the code first, since I’m not providing it here.

Make it work

The main idea looks like this:

  • let’s treat folder and file names as single units (segments);
  • let’s build output string from edges to the middle, adding whole segments as long as there is enough space in the middle;
  • let’s not hardcode all decisions, providing a lot of flexibility, but with reasonable defaults.

Back to our example. If we split the path string by directory separator “\“, we will get following array of string segments:

“C:“, “Windows”, “System32”, “WindowsPowerShell”, “v1.0”, “Modules”, “WindowsErrorReporting”, “WindowsErrorReporting.psm1”

Now we can fit them into output string step by step, checking if we are still fit into bounds. For a decision which segment should be added next, we have few options: we may prefer output string to be ellipsed approximately in the middle visually (by number of characters), or we may prefer to keep similar number of segments on the left and on the right. The resulting code is providing both options but let’s just count segments for this example.

Step Left Right L/(L+R) Free space Next segment Fit Free space after
1 0 0 0.5 * 50 left: “C:” Yes 50-2-1-3 = 44 **
2 1 0 1 44 right: “WindowsErrorReporting.psm1” Yes 44-26-1 = 17
3 1 1 0.5 17 left: “Windows” Yes 17-7-1 = 9
4 2 1 0.667 9 right: “WindowsErrorReporting” No 9
9 left: “System32” Yes 9-8-1 = 0

* When both sides are empty, 0.5 is a good assumption for a proportion.

** 3 is for ellipsis, 1 is for separator, and the other number is for string length.

The output is C:\Windows\System32\...\WindowsErrorReporting.psm1. We end up with left side having two more segments (3) than the right side (1), even though we tried to keep them equal. That’s because we also tried to fit as much segments as possible (checked other side after fitting failure). The resulting code has a strict mode that will not switch to opposite side when facing limitations (as in step 4) and will give C:\Windows\...\WindowsErrorReporting.psm1 as output.

In this example we end up with 0 characters left. In case there is still free space left, algorithm can do few things: leave it as is and return shorter string, or add a part of next segment to left or right side of ellipsis. In “auto” mode it will decide based on proportion from the last step. By default, algorithm prefers left side as containing more useful information.

In this example we have chosen every next segment by comparing proportion to 0.5 (less or equal - choose left, otherwise right). We could also target not only middle point, but any arbitrary point actually, in case we would like to see more of the tail or the head of the string.

In case we prefer to compare string lengths rather than segment numbers, the algorithm looks exactly the same, with an exception of counters “Left” and “Right” being filled differently.

Make it right

It looks overengineered. Am I sure that it will work properly?

First of all, I added debug output, that produces a table, similar to the one above and checked my math in various cases. You can turn it on with $DebugPreference = 'Continue':

> Format-Ellipsis 'C:\Windows\System32\WindowsPowerShell\v1.0\Modules​\WindowsErrorReporting​\WindowsErrorReporting.psm1' 50 -by 'count'
DEBUG:   0 |   0 | 0,5     |  46 |  47 | ...
DEBUG:   1 |   0 | 1       |  43 |  44 | C:\...
DEBUG:   1 |   1 | 0,5     |  16 |  17 | C:\...\WindowsErrorReporting.psm1
DEBUG:   2 |   1 | 0,66667 |   8 |   9 | C:\Windows\...\WindowsErrorReporting.psm1
DEBUG:   3 |   1 | 0,75    |  -1 |   0 | C:\Windows\System32\...\WindowsErrorReporting.psm1

And then I added Pester tests to be sure that I have documented behaviour for every function parameter.

Make it fast

I’m aware that there is a lot of array manipulations in code that can be avoided. At the moment, this code fits my requirements and I’m pretty happy with it. I may do some updates and performance estimation in the future, but for now I need to move on.

The module

After getting a working function, I thought that it’s a good candidate to learn about PowerShell module release process.

For now I have Pester tests and I will add Appveyor support shortly after (Update: added). I may also release it in the PowerShell Gallery if I get some positive feedback. Do you want it to be available from Install-Module?

One conscious decision I’ve made is that I will not optimize the workflow for PowerShell v2.0. But single-file version compatible with v2.0 is provided at least.

Where to get it

Once again, everything is available on GitHub.

comments powered by Disqus