microsoft / winget-cli

WinGet is the Windows Package Manager. This project includes a CLI (Command Line Interface), PowerShell modules, and a COM (Component Object Model) API (Application Programming Interface).
https://learn.microsoft.com/windows/package-manager/
MIT License
22.93k stars 1.42k forks source link

Change default sort order on search. #142

Open denelon opened 4 years ago

denelon commented 4 years ago

I would like the ability to customize the sort order when multiple results are returned.

In addition to arguments like winget search _foo_ -sort Name, I would like settings to specify my default preferences.

winget list -sort Version

In addition, I would like to have settings to handle results from multiple sources so they can be displayed in a coalesced manner vs. the default where we sort by source based on their order in winget source list.

Note: The current sort order is based on the fields matched for search. This may vary by source as REST sources are allowed to return results in any order they prefer.

jhoff80 commented 4 years ago

Same for the 'show' command. I don't want it to be sorted by the ID but the name.

weitzhandler commented 4 years ago

Currently, is there any way to display all apps alphabetically at all?

chausner commented 3 years ago

I started an implementation of this feature in https://github.com/microsoft/winget-cli/pull/1205.

JohnMcPMS commented 3 years ago

There is currently a sort order defined by the search itself (as mentioned in the OP). It is currently sorted by (applies to the winget source; arbitrary REST sources can sort arbitrarily):

  1. The "quality" of the match
  2. The field that matched
  3. Arbitrary ordering inherent to the internal data representation

Quality here means whether the match was exact, a substring, etc. So this means that winget search git puts "Git" first as it is the best match due to the case insensitive equality between the name and the search term. Always sorting the entire output by name blindly will lead to results with far less relevance being placed first. For instance, when I run this locally I am seeing a result that is accidentally related because "digital" happens to contain "git" (and tags are lower for sort criteria 2 than names):

FL Studio                            ImageLine.FLStudio                    20.8.3.2304   Tag: digital audio workstation

This would actually be placed before "Git" in the results with name sorting only. I would imagine that the large majority of users searching for "git" are not looking for this at all though. These 4 also fall into the same bucket:

Duplicate Cleaner Pro                DigitalVolcanoSoftware.DuplicateClea… 4.1.4
Duplicate Cleaner Free               DigitalVolcanoSoftware.DuplicateClea… 4.1.2
APDigitalExams                       CollegeBoard.APDigitalExams           0.9.1
2021 Digital AP Exams                CollegeBoard.2021DigitalAPExams       0.9.4.5

If users still want results to be sorted purely based on name, I would think it best to have a setting rather than change the default (or have a setting and change the default).

denelon commented 3 years ago

I copied the original body of the Issue from a comment, and I never got back around to clarifying. Thanks for jumping in @JohnMcPMS.

Yes, we do need to think about how sorting can impact results.

I think what we currently have is similar to "best-match". I think the ask is an ability to get an alphabetical sort. This may be handled via arguments and/or settings.

winget search terminal yields interesting results as many packages have "terminal" as one of their "Tag"s.

winget list is possibly a better candidate for sorting by default.

chausner commented 3 years ago

@JohnMcPMS I see, thanks for explaining! I wasn't aware that there was already some logic for sorting implemented. I fully agree that always sorting by name seems like a step back to what we currently have, at least for the output of winget search. I guess this feature needs some more thinking then.

Maybe this feature also becomes somewhat obsolete once there are PowerShell cmdlets for winget as you could then just use the sorting available in PowerShell.

robinmalik commented 3 years ago

+1 for winget list sorting by name. It's a lot more difficult to use without that.

JohnMcPMS commented 3 years ago

+1 for winget list sorting by name. It's a lot more difficult to use without that.

I could definitely get behind list with no search parameter sorting alpha by name. That would be fairly easy to achieve internal to the Source implementation so as to not accidentally affect anything else, although might require a bit of plumbing and I'm not exactly sure on whether the SQLite ICU extensions already give us an easy ORDER BY.

ehawman-rosenberg commented 2 years ago

So this is a hack and a half, but in PowerShell it approximates alpha sort by name:

$nameVar = winget list ; $namevar[4..($namevar.length)] | Sort-Object | Format-Table

You lose out on the headers this way.

This is my even uglier (in code) table-formatted version. There's probably a graceful way to do this, but I'm just trying to bang out something that works. I hard-coded the column widths because I felt that some egregiously long names (Microsoft Security Updates primarily) ruined the entire table.

$wingetListOutput = winget list | Where-Object {$_ -notlike "*$([char]0x0008)*" -and $_ -notlike "*----------*"}
$wingetListOutputCSV = $wingetListOutput -replace '[ ]{2,}',','
$wingetListOutputCSV | ConvertFrom-Csv | Sort-Object -Property Name | Format-Table -Property @{ e='Name'; width = 30 }, @{ e='Id'; width = 60 }, @{ e='Version'; width = 20 }, @{ e='Available'; width = 20 }, Source

I'm gonna slap this in an alias and call it a day. Again, it's a frankenscript and it would definitely be best if the functionality were incorporated in WT.

robinmalik commented 2 years ago

@ehawman-rosenberg I decided to use the lack of alphabetised listing as an opportunity to take a quick look at the PowerShell module called Crescendo (Github: https://github.com/PowerShell/Crescendo):

The PowerShell Crescendo module provides a way to more easily take advantage of the PowerShell pipeline by invoking the native executable, facilitating parameter handling, and converting text output into objects.

but.. I immediately hit the problem that I think you've got, which is the lack of data present in the 'Available' column/property means the table gets jumbled up like this:

image

I couldn't think of an immediate solution but found this amazing bit of code: https://github.com/iRon7/ConvertFrom-SourceTable/blob/master/ConvertFrom-SourceTable.ps1 which solves the problem (once you've wrapped it into a function command which is missing).

Example:

$Data = winget list;  $Data = $Data | Where-Object {$_ -notlike '*$([char]0x0008)*' }
$Converted = ConvertFrom-SourceTable -InputObject $Data[4..$($Data.Length-1)] -Header $Data[2]

and $Converted is exactly the kind of array object you'd want. I've stuck the function into a personal PS module where I tend to shove anything that's useful so it's available by default.

Wrapping this up in Crescendo gives a new cmdlet "Invoke-WinGet":

image

The .json for the Crescendo code incase anyone wants it, which includes an -Upgrade switch too:

{
    "$schema": "https://aka.ms/PowerShell/Crescendo/Schemas/2021-11",
    "Commands": [
        {
            "Verb": "Invoke",
            "Noun": "WinGet",
            "OriginalName": "winget",
            "OriginalCommandElements": [
            ],
            "Parameters": [
                {
                    "Name": "List",
                    "OriginalName": "list",
                    "ParameterType": "switch",
                    "ParameterSetName": [
                        "list"
                    ],
                    "Description": "list items",
                    "Mandatory": false
                },
                {
                    "Name": "Upgrade",
                    "OriginalName": "upgrade",
                    "ParameterType": "switch",
                    "ParameterSetName": [
                        "upgrade"
                    ],
                    "Description": "list items for upgrade",
                    "Mandatory": false
                }
            ],
            "OutputHandlers": [
                {
                    "ParameterSetName": "list",
                    "Handler": "param($Data)
                    $Data = $Data | Where-Object { $_ -match '^[a-zA-Z0-9-]' }
                    ConvertFrom-SourceTable -InputObject $Data[2..$($Data.Length-1)] -Header $Data[0] | Sort-Object Name
                    "
                },
                {
                    "ParameterSetName": "upgrade",
                    "Handler": "param($Data)
                    $Data = $Data | Where-Object { $_ -match '^[a-zA-Z0-9-]' }
                    ConvertFrom-SourceTable -InputObject $Data[2..$($Data.Length-1)] -Header $Data[0] | Sort-Object Name
                    "
                }
            ]
        }
    ]
}

and

Export-CrescendoModule -ConfigurationFile "C:\Test\winget.Crescendo.json" -ModuleName "C:\Test\WinGetWrapper.psm1"
Import-Module "C:\Test\WinGetWrapper.psd1"

I find it somewhat amusing that we're using PowerShell to solve this problem when Chocolatey is native PowerShell and does the same thing as WinGet, essentially 😅

EDIT: Tweaked the code returned from winget output so it filters out progress bar lines (which resulted in errors when passing to ConvertFrom-SourceTable), and added -Upgrade switch.

denelon commented 2 years ago

@robinmalik you might want to take a look at https://github.com/microsoft/winget-cli/tree/master/tools/PowerShell

I participated in a hackathon to build PowerShell cmdlets. We were considering Crescendo but ended up doing more manual work due to the impedance of our command names vs approved PowerShell Nouns and Verbs. There may be some useful code there, and we would welcome any improvements :)

wisdomtooth commented 2 years ago

I'm gonna slap this in an alias and call it a day.

Pardon the n00b question: how do you do that? (Set-Alias, I know, but I'm struggling with the syntax)

Cheers.

aycippo commented 1 year ago

@robinmalik hey there, was curious if you have made any progress on the addition of Crescendo? or if anyone else has been working on a similar solution.

robinmalik commented 1 year ago

@aycippo They're doing it officially now. In amongst the assets for the latest release there's the module zipped up: https://github.com/microsoft/winget-cli/releases/download/v1.4.10173/Microsoft.WinGet.Client-PSModule.zip

robinmalik commented 1 year ago

Having just had a very quick look at it, this works: Get-WinGetPackage | Sort Name.

To filter by those with updates available you have to use the IsUpdateAvailable property, and not Available as is shown in the default view by a custom formatting rule:

Get-WinGetPackage | ? { $_.IsUpdateAvailable } | Sort Name

(I wasn't able to pipe to Format-List to see all data for a single package object because it threw an error Format-List: Exception has been thrown by the target of an invocation. but I was able to pipe it to Get-Member to figure that out)

Trenly commented 1 year ago

[Policy] Area-Output [Policy] Area-Sorting

denelon commented 1 year ago

@robinmalik do we have an issue covering your finding with the "PowerShell" label?

robinmalik commented 1 year ago

@denelon I personally haven't created an issue for that encountered error. I can do, if you'd like?

denelon commented 1 year ago

Yes, please. 😊

robinmalik commented 1 year ago

@denelon Happy to report that having installed the latest version from the PowerShell Gallery (congrats on getting it up there!) that the format-list issue I saw in the above zipped copy no longer exists :)