Поэтому я заметил отсутствие nuget outdated
или dotnet nuget outdated
, которые, как можно было бы ожидать, будут действовать аналогично npm outdated
.
Итак, я подумал, что пора попробовать, и теперь я полностью по умолчанию использую F# для написания своих утилит и приложений.
Существует этот API командной строки, который Microsoft, похоже, использует для своего CLI dotnet, и я действительно узнал об этом, потому что я наткнулся на него, читая их исходный код (чтобы увидеть, где эта новая команда может вписаться!)
Его легко использовать. Запустите dotnet new console -lang f# -o mycmdline -n MyCmdLine
или любое другое название проекта, которое вы хотите использовать, затем выполните dotnet add package System.CommandLine --prerelease
, и все будет готово.
Прямо из коробки, когда вы делаете dotnet build
или dotnet watch build
, а затем делаете MyCmdLine.exe
, вы получаете это из коробки
Что очень круто — игнорировать параметры кеша, это то, что я вставил.
Итак, теперь мы можем создать то, что называется корневым обработчиком:
Итак, чтобы разбить это, поскольку мы используем .net 6 и минимальный API, нам нужно получить аргументы командной строки из статического класса Environment.
Команда root означает, что без каких-либо аргументов она работает так
Теперь мы можем добавить несколько подкоманд и опций 😃
Здесь у нас есть опция verbose
, она действует как переключатель. Поэтому, когда вы запустите его с -h
, вы будете проинформированы
Нет переключателя
С переключателем
Довольно просто, ау!
Подкоманды
Я накручиваю интенсивность здесь, так что голая. Команды иерархичны, поэтому вы строите их как таковые.
Итак, если вы хотите получить индекс службы nuget таким образом, что вам нужен MyCmdLine get serviceIndex
, давайте начнем с команды serviceIndex
.
Так что игнорируйте cache
и EasyClient
, это просто небольшие абстракции, которые я сделал, EasyClient
— это просто HttpClient
, основанный на nuget, а cache
просто хранит любые HttpResponse
, поэтому я не заливаю серверы nuget.
Суть еще в том, что fetchData
теперь является функцией handle
с сигнатурой Func<Task>
— let fetchData () : Task
делает эту работу, я обнаружил, что без нее Task<unit>
просто не компилируется.
Легкие дни.
Теперь нам нужно создать команду get
(родитель serviceIndex
)
Итак, поскольку эта команда может принимать несколько подкоманд, я получил commands: Command list
, а commands |> List.iter (cmd.AddCommand)
добавляет каждую команду в родительский элемент.
Теперь мы можем связать это с командой root.
Здесь я абстрагировался от корневого обработчика. Я также создал этот getCmd
, который просто возвращает родительскую команду GetCommand
из более ранней и добавляется через cmd.AddCommand(getCmd ())
Бег с -h
Бег с get -h
И вот… easy peasy — ваша очень мощная полнофункциональная программа командной строки…
Мне это нравится, я совершенно поражен профессионализмом, который дает коробку и гибкость, чтобы разбить ее из парка!
Предостережения
Как и во всем, есть некоторые предостережения
- Единственный обработчик, который запускается, когда вы выполняете команду, — это обработчик этой команды — это не промежуточное ПО, где каждый обработчик запускается по порядку…
- Внедрения зависимостей нет, но это потому, что нет открытого запущенного процесса, что меня и привлекло в первый раз. Кэш изначально был в памяти, но забыли, что каждый запуск — это новый запуск процесса… поэтому любое хранение материала должно выполняться снаружи (я только что создал файл
.cache.json
…) (Сказав это, если вы хотите сохранить в памяти параметры конфигурации, тогда вы можете создать одноэлементную зависимость, которая может быть маршрутизирована через частичное приложение или монаду чтения) - Параметры можно использовать только в обработчике, к которому они подключены, поэтому, если вы хотите
--namespace charlie get serviceIndex
и прикрепитеOption<string>("namespace", "Scope the subsequent commands")
к корневому обработчику, это не сработает. Поэтому вам лучше абстрагироваться от логики, которая обрабатывает пространство имен, и иметь этот «базовый» параметр, прикрепленный к каждой команде, где вы хотите его использовать. Таким образом, вы могли бы по существу создать базовый обработчик, который обрабатывает всю конфигурацию, который затем принимает фактический обработчик в качестве параметра (о котором я только что подумал, когда писал это, и звучит круто)
Сходить с ума!