Showing preview only (216K chars total). Download the full file or copy to clipboard to get everything.
Repository: mayuki/Chell
Branch: main
Commit: 79e25d9b187a
Files: 57
Total size: 200.3 KB
Directory structure:
gitextract_m746u3r_/
├── .editorconfig
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── build.yaml
│ └── release.yaml
├── .gitignore
├── Chell.sln
├── Directory.Build.props
├── LICENSE
├── README.md
├── samples/
│ ├── GettingStarted.Basic.Unix/
│ │ ├── GettingStarted.Basic.Unix.csproj
│ │ └── Program.cs
│ └── GettingStarted.Basic.Windows/
│ ├── GettingStarted.Basic.Windows.csproj
│ └── Program.cs
├── src/
│ ├── .editorconfig
│ ├── Chell/
│ │ ├── Chell.csproj
│ │ ├── ChellEnvironment.cs
│ │ ├── CommandLineString.cs
│ │ ├── Exports.cs
│ │ ├── Extensions/
│ │ │ ├── ChellExtensions.cs
│ │ │ ├── ProcessOutputExtensions.cs
│ │ │ ├── ProcessTaskExtensions.Generated.cs
│ │ │ ├── ProcessTaskExtensions.cs
│ │ │ ├── ProcessTaskExtensions.tt
│ │ │ └── StringExtensions.cs
│ │ ├── IO/
│ │ │ ├── ChellWrappedStream.cs
│ │ │ ├── ChellWritableStream.Generated.cs
│ │ │ ├── ChellWritableStream.tt
│ │ │ ├── IConsoleProvider.cs
│ │ │ ├── LINQPadConsoleProvider.cs
│ │ │ └── SystemConsoleProvider.cs
│ │ ├── Internal/
│ │ │ ├── CommandLineHelper.cs
│ │ │ ├── EnvironmentVariables.cs
│ │ │ ├── LINQPadHelper.cs
│ │ │ ├── ObjectDumper.cs
│ │ │ ├── OutputSink.cs
│ │ │ ├── StandardInput.cs
│ │ │ ├── StreamPipe.cs
│ │ │ └── Which.cs
│ │ ├── ProcessOutput.cs
│ │ ├── ProcessTask.cs
│ │ ├── ProcessTaskException.cs
│ │ ├── ProcessTaskOptions.cs
│ │ ├── Run.cs
│ │ └── Shell/
│ │ ├── BashShellExecutor.cs
│ │ ├── CmdShellExecutor.cs
│ │ ├── IShellExecutor.cs
│ │ ├── NoUseShellExecutor.cs
│ │ └── ShellExecutorProvider.cs
│ └── Chell.Run/
│ ├── Chell.Run.csproj
│ └── Program.cs
└── tests/
└── Chell.Tests/
├── Chell.Tests.csproj
├── ChellEnvironmentTest.cs
├── CommandLineStringTest.cs
├── ProcessTaskTest.cs
├── Shell/
│ ├── BashShellExecutorTest.cs
│ └── CmdShellExecutorTest.cs
└── TemporaryAppBuilder.cs
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
###############################
# Core EditorConfig Options #
###############################
root = true
# All files
[*]
indent_style = space
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8
[*.md]
charset = utf-8
###############################
# .NET Coding Conventions #
###############################
[*.{cs,vb}]
# Organize usings
dotnet_sort_system_directives_first = true
# this. preferences
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
# Style Definitions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const
###############################
# C# Coding Conventions #
###############################
[*.cs]
# var preferences
csharp_style_var_for_built_in_types = true:silent
csharp_style_var_when_type_is_apparent = true:silent
csharp_style_var_elsewhere = true:silent
# Expression-bodied members
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null-checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Expression-level preferences
csharp_prefer_braces = true:silent
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
###############################
# C# Formatting Rules #
###############################
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
*.sh text eol=lf
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .github/workflows/build.yaml
================================================
name: Build-Development
on:
push:
branches:
- main
- master
pull_request:
types:
- opened
- synchronize
jobs:
Build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
env:
DOTNET_NOLOGO: true
steps:
- uses: actions/checkout@v1
- uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
# Build
- run: dotnet restore
- run: dotnet build -c Release
# Run Unit tests
- run: dotnet test -c Release --no-build --logger trx --results-directory $GITHUB_WORKSPACE/artifacts
# Packaging
- name: dotnet pack
run: dotnet pack -c Release --no-build -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg --output $GITHUB_WORKSPACE/artifacts
shell: bash
# Upload & Publish
- uses: actions/upload-artifact@master
with:
name: Packages
path: artifacts
================================================
FILE: .github/workflows/release.yaml
================================================
name: Build-Release
on:
push:
tags:
- v*
jobs:
Release:
if: "contains(github.ref, 'refs/tags')"
runs-on: ubuntu-latest
env:
DOTNET_NOLOGO: true
steps:
- uses: actions/checkout@v1
- uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
- name: "Set VersionSuffix for Preview"
if: "contains(github.ref, 'refs/tags') && contains(github.ref, 'preview')"
run: |
echo "VERSION_SUFFIX=preview.`date '+%Y%m%d-%H%M%S'`+${GITHUB_SHA:0:6}" >> $GITHUB_ENV
- name: "Get git tag"
if: "contains(github.ref, 'refs/tags')"
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
# Build
- run: dotnet restore
- run: dotnet build -c Release
# Packaging
- name: dotnet pack
run: dotnet pack -c Release --no-build --version-suffix "$VERSION_SUFFIX" -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg --output $GITHUB_WORKSPACE/artifacts
# Upload & Publish
- uses: actions/upload-artifact@master
with:
name: Packages
path: artifacts
- name: "Push to NuGet.org"
run: |
dotnet nuget push "$GITHUB_WORKSPACE/artifacts/*.nupkg" --skip-duplicate -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
.DS_Store
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# .NET Launch Profiles
launchSettings.json
================================================
FILE: Chell.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chell", "src\Chell\Chell.csproj", "{6BE659EC-A00D-4148-B19D-B5478DE001FA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chell.Run", "src\Chell.Run\Chell.Run.csproj", "{893D8C70-3C13-47D6-987F-C099C6161D7E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chell.Tests", "tests\Chell.Tests\Chell.Tests.csproj", "{1FBAA8ED-438E-498C-AB1F-29429550DC21}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EBB92712-D878-48FD-8E31-E577AABDC0FB}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
.github\workflows\build.yaml = .github\workflows\build.yaml
Directory.Build.props = Directory.Build.props
README.md = README.md
.github\workflows\release.yaml = .github\workflows\release.yaml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{AF454F92-8B84-446B-B0E2-9BA8887B09CC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStarted.Basic.Windows", "samples\GettingStarted.Basic.Windows\GettingStarted.Basic.Windows.csproj", "{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStarted.Basic.Unix", "samples\GettingStarted.Basic.Unix\GettingStarted.Basic.Unix.csproj", "{69DC1056-843E-4980-908A-5DB4ADA95460}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Debug|x64.ActiveCfg = Debug|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Debug|x64.Build.0 = Debug|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Debug|x86.ActiveCfg = Debug|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Debug|x86.Build.0 = Debug|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Release|Any CPU.Build.0 = Release|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Release|x64.ActiveCfg = Release|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Release|x64.Build.0 = Release|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Release|x86.ActiveCfg = Release|Any CPU
{6BE659EC-A00D-4148-B19D-B5478DE001FA}.Release|x86.Build.0 = Release|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Debug|x64.ActiveCfg = Debug|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Debug|x64.Build.0 = Debug|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Debug|x86.ActiveCfg = Debug|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Debug|x86.Build.0 = Debug|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Release|Any CPU.Build.0 = Release|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Release|x64.ActiveCfg = Release|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Release|x64.Build.0 = Release|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Release|x86.ActiveCfg = Release|Any CPU
{893D8C70-3C13-47D6-987F-C099C6161D7E}.Release|x86.Build.0 = Release|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Debug|x64.ActiveCfg = Debug|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Debug|x64.Build.0 = Debug|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Debug|x86.ActiveCfg = Debug|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Debug|x86.Build.0 = Debug|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Release|Any CPU.Build.0 = Release|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Release|x64.ActiveCfg = Release|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Release|x64.Build.0 = Release|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Release|x86.ActiveCfg = Release|Any CPU
{1FBAA8ED-438E-498C-AB1F-29429550DC21}.Release|x86.Build.0 = Release|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Debug|x64.ActiveCfg = Debug|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Debug|x64.Build.0 = Debug|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Debug|x86.ActiveCfg = Debug|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Debug|x86.Build.0 = Debug|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Release|Any CPU.Build.0 = Release|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Release|x64.ActiveCfg = Release|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Release|x64.Build.0 = Release|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Release|x86.ActiveCfg = Release|Any CPU
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5}.Release|x86.Build.0 = Release|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Debug|x64.ActiveCfg = Debug|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Debug|x64.Build.0 = Debug|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Debug|x86.ActiveCfg = Debug|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Debug|x86.Build.0 = Debug|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Release|Any CPU.Build.0 = Release|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Release|x64.ActiveCfg = Release|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Release|x64.Build.0 = Release|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Release|x86.ActiveCfg = Release|Any CPU
{69DC1056-843E-4980-908A-5DB4ADA95460}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9BE2CB2C-D6F2-429A-A00F-CF30CAEF28B5} = {AF454F92-8B84-446B-B0E2-9BA8887B09CC}
{69DC1056-843E-4980-908A-5DB4ADA95460} = {AF454F92-8B84-446B-B0E2-9BA8887B09CC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5B16EB0E-5342-4D93-9FA2-8327FF69A276}
EndGlobalSection
EndGlobal
================================================
FILE: Directory.Build.props
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VersionPrefix>1.0.0</VersionPrefix>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;1587;1574</NoWarn>
<!-- NuGet Package Information -->
<Description>Write scripts with the power of .NET. Provides a shell script-like (bash, cmd, ...) experience to .NET application.</Description>
<Authors>Mayuki Sawatari</Authors>
<Copyright>Copyright © Mayuki Sawatari</Copyright>
<PackageProjectUrl>https://github.com/mayuki/Chell</PackageProjectUrl>
<RepositoryUrl>https://github.com/mayuki/Chell</RepositoryUrl>
<!--<PackageIcon>icon.png</PackageIcon>-->
<PackageTags>CommandLine Shell Process</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
</Project>
================================================
FILE: LICENSE
================================================
MIT License
Copyright © Mayuki Sawatari <mayuki@misuzilla.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Chell
Write scripts with the power of C# and .NET.
Chell is a library and execution tool for providing a shell script-like (bash, cmd, ...) experience to .NET applications.
```csharp
var branch = await Run($"git branch --show-current");
await Run($"git archive {branch} -o {branch}.zip");
```
.NET applications are great for complex tasks, but executing processes can be boring. Chell brings the experience closer to shell scripting. This library is heavily influenced by [google/zx](https://github.com/google/zx).
## When should I use Chell?
- **Write better shell scripts**: Write a complex script and use the power of .NET and C#
- **Write multi-platform shell scripts**: As an alternative to scripts that work on multiple platforms
- **Run a process quickly in your app**: As .NET library for easy handling of process launch and output
- **All developers in the project are .NET developers**: 🙃
Of course, if the shell script is already working fine and you don't have any problems, then there is no need to use Chell.
## Chell at a glance
Using Chell makes the code feel more like a script by taking advantage of C# 9's top-level statements and C# 6's `using static`.
```csharp
// Chell.Exports exposes a variety of functions and properties
using Chell;
using static Chell.Exports;
```
```csharp
// Move the current directory with Cd method
Cd("/tmp");
// Dispose the return value of Cd method to return to the previous directory
using (Cd("/usr/local/bin"))
{
// The current directory is "/usr/local/bin".
}
// The current directory is "/" again.
```
```csharp
// You can run the process by passing a string to Run method
await Run($"ls -lFa");
```
```csharp
// An interpolated string passed to Run method will be escaped and expanded if it is an array
var newDirs = new [] { "foo", "bar", "my app", "your;app" };
await Run($"mkdir {newDirs}"); // $ mkdir foo bar "my app" "your;app"
```
```csharp
// Run method returns the result object of the command (ProcessOutput class)
var result = await Run($"ls -lFa");
// You can read stdout & stderr line by line
foreach (var line in result)
{
Echo(line);
}
// Allows to get stdout & stderr with implicit conversion to `string`
string output = result;
// You can also get stdout as bytes (ReadOnlyMemory<byte>)
var binary = result.OutputBinary;
```
```csharp
// Provides convenient extension methods for parsing JSON.
var images = await Run($"docker image ls --format {"{{json .}}"}").SuppressConsoleOutputs();
foreach (var image in images.AsJsonLines(new { Repository = "", ID = "", Tag = ""}))
{
Echo(image);
}
// $ docker image ls --format "{{json .}}"
// { Repository = mcr.microsoft.com/dotnet/sdk, ID = b160c8f3dbd6, Tag = 5.0 }
// { Repository = <none>, ID = 3ee645b4a3bd, Tag = <none> }
```
```csharp
// Standard input/output of process tasks can be connected by pipes
await (Run($"ls -lFa") | Run($"grep dotnet"));
// The difference with `await (Run($"ls -lFa | grep dotnet"));` is that the shell can pipe or not.
// You can also specify a Stream as input or output
// Write ffmpeg output to a Stream.
await (Run($"ffmpeg ...") | destinationStream);
// Write a Stream to ffmpeg process.
await (srcStream | Run($"ffmpeg ..."));
```
Just want to make it easy for your app to handle processes? If you don't use `Chell.Exports`, you won't get any unnecessary methods or properties, and you'll get the same functions by `new Run(...)`.
```csharp
using Chell;
var result = await new Run($"ls -lF");
```
Want to run it like a scripting language? Install [Chell.Run](#chellrun), and you can run it like a script.
```bash
% dotnet tool install -g Chell.Run
% chell -e "Echo(DateTime.Now)"
9/1/2021 0:00:00 PM
% cat <<__EOF__ > MyScript.cs
var dirs = new [] { "foo bar", "baz" };
await Run($"mkdir {dirs}");
await Run($"ls -l");
__EOF__
% chell MyScript.cs
$ mkdir "foo bar" "baz"
$ ls -l
total 8
drwxr-xr-x 2 mayuki mayuki 4096 Sep 1 00:00 baz/
drwxr-xr-x 2 mayuki mayuki 4096 Sep 1 00:00 'foo bar'/
```
## Features
- Automatic shell character escaping and array expansion
- Stream and Process Pipes
- Provide utilities and shortcuts useful for scripting.
- Simple shell script-like execution tools
- Multi-platform (Windows, Linux, macOS)
- LINQPad friendly
## Install
```
dotnet package add Chell
```
### Requirements
.NET Standard 2.1, .NET 5 or higher
## Chell.Exports
Chell.Exports class exposes a variety of utilities and shortcuts to make writing feel like shell scripting. It is recommended to include this class in your scripts with `static using`.
### Methods (Functions)
#### `Run`
Starts a process using the specified command-line and returns a `ProcessTask`.
```csharp
await Run($"ls -lF");
// The followings are equivalent to calling Run method
await (Run)$"ls -lF";
await new Run($"ls -lF");
```
The process will be launched asynchronously and can wait for completion by `await`. And you can `await` to get a `ProcessOutput` object with its output.
If the exit code of the process returns non-zero, it will throw an exception. To suppress this exception, see `NoThrow`.
An interpolated string passed to Run method will be escaped and expanded if it is an array.
```csharp
var newDirs = new [] { "foo", "bar", "my app", "your;app" };
await Run($"mkdir {newDirs}"); // equivalent to `mkdir foo bar "my app" "your;app"`
```
You can also pass an execution options (`ProcessTaskOptions`) to Run method.
```csharp
await Run($"ping -t localhost", new ProcessTaskOptions(
workingDirectory: @"C:\Windows",
timeout: TimeSpan.FromSeconds(1)
));
```
#### `Cd(string)`
```csharp
Cd("/usr/local/bin"); // equivalent to `Environment.CurrentDirectory = "/usr/local/bin";`
```
Dispose the return value of `Cd` method to return to the previous directory.
```csharp
Cd("/"); // The current directory is "/".
using (Cd("/usr/local/bin"))
{
// The current directory is "/usr/local/bin".
}
// The current directory is "/" again.
```
#### `Mkdirp(string path)`
Same as `mkdir -p`. Creates a new directory and any necessary sub-directories in the specified path.
#### `Dump<T>(T value)`
Formats the object and write it to the console.
```csharp
Dump(new { Foo = 123, Bar = "Baz" }); // => "{ Foo = 123, Bar = "Baz" }"
```
#### `Which(string name)`, `TryWhich(string name, out string path)`
Returns a path of the specified command.
```csharp
var dotnetPath = Which("dotnet");
await Run($"{dotnetPath} run");
```
#### `Echo(object message = default)`
`Echo` method is equivalent to Console.WriteLine.
```csharp
Echo("Hello World!"); // equivalent to Console.WriteLine("Hello World!");
```
#### `Sleep(int duration)`, `Sleep(TimeSpan duration)`
Returns a Task that waits for the specified duration.
```csharp
await Sleep(10); // Sleep for 10 seconds.
```
#### `Exit(int exitCode)`
Terminates the application with an exit code.
```csharp
Exit(1);
```
### Properties
#### `Env.Vars`
Exposes the environment variables as `IDictionary<string, string>`.
```csharp
Env.Vars["PATH"] = Env.Vars["PATH"] + ":/path/to/";
```
#### `Env.IsWindows`
Returns whether the running operating system is Windows or not. If it returns `false`, the operating system is Linux or macOS.
```csharp
if (Env.IsWindows) { /* Something to do for Windows */ }
```
#### `Env.Shell`
Specify explicitly which shell to use, or set to not use a shell.
```csharp
Env.Shell.UseBash();
Env.Shell.NoUseShell();
Env.Shell.UseCmd();
```
#### `Env.Verbosity`
Sets or gets the output level when executing a command/process.
- `Verbosity.All`: Displays both the command line and the output of the command
- `Verbosity.CommandLine`: Displays the command line
- `Verbosity.Output`: Displays the output of the command
- `Verbosity.Silent`: No display
#### `Env.ProcessTimeout`
Sets the timeout for running the process. The default value is `0` (disabled).
```csharp
Env.ProcessTimeout = TimeSpan.FromSecond(1);
// OperationCanceledException will be thrown after 1s.
await Run($"ping -t localhost");
```
#### `Arguments`
Gets the arguments passed to the current application.
```csharp
// $ myapp foo bar baz => new [] { "foo", "bar", "baz" };
foreach (var arg in Arguments) { /* ... */ }
```
#### `CurrentDirectory`, `ExecutableDirectory`, `ExecutableName`, `ExecutablePath`
Gets the current directory and the application directory or name or path.
```csharp
// C:\> cd C:\Users\Alice
// C:\Users\Alice> Downloads\MyApp.exe
Echo(CurrentDirectory); // C:\Users\Alice
Echo(ExecutableDirectory); // C:\Users\Alice\Downloads
Echo(ExecutableName); // MyApp.exe
Echo(ExecutablePath); // C:\Users\Alice\Downloads\MyApp.exe
```
#### `HomeDirectory`
Gets the path of the current user's home directory.
```csharp
// Windows: C:/Users/<UserName>
// Linux: /home/<UserName>
// macOS: /Users/<UserName>
Echo(HomeDirectory);
```
#### `StdIn`, `StdOut`, `StdErr`
Provides the wrapper with methods useful for reading and writing to the standard input/output/error streams.
```csharp
// Reads data from standard input.
await StdIn.ReadToEndAsync();
// Writes data to standard output or error.
StdOut.WriteLine("FooBar");
StdErr.WriteLine("Oops!");
```
## ProcessTask class
Represents the execution task of the process started by `Run`.
### `Pipe`
Connects the standard output of the process to another `ProcessTask` or `Stream`.
```csharp
await (Run($"ls -lF") | Run($"grep .dll"));
// The followings are equivalent to using '|'.
var procTask1 = Run($"ls -lF");
var procTask2 = Run($"grep .dll");
procTask1.Pipe(procTask2);
```
A `Stream` can also be passed to Pipe. If the ProcessTask has connected to the `Stream`, it will not write to `ProcessOutput`.
```csharp
var memStream = new MemoryStream();
await Run($"ls -lF").Pipe(memStream);
```
### `ConnectStreamToStandardInput`
Connects the Stream to the standard input of the process. The method can be called only once before the process starts.
```csharp
await (myStream | Run($"grep .dll"));
// The followings are equivalent to using '|'.
var procTask = Run($"grep .dll");
procTask.ConnectStreamToStandardInput(myStream);
```
### `NoThrow`
Suppresses exception throwing when the exit code is non-zero.
```csharp
await Run($"AppReturnsExitCodeNonZero").NoThrow();
```
### `SuppressConsoleOutputs`
Suppresses the writing of command execution results to the standard output.
```csharp
// equivalent to "Env.Verbosity = Verbosity.Silent" or pipe to null.
await Run($"ls -lF").SuppressConsoleOutputs();
```
### `ExitCode`
Returns a `Task` to get the exit code of the process. This is equivalent to waiting for a `ProcessTask` with `NoThrow`.
```csharp
var proc = Run($"ls -lF");
if (await proc.ExitCode != 0)
{
...
}
// equivalent to `(await Run($"ls -lF").NoThrow()).ExitCode`
```
## ProcessOutput class
Provides the results of the process execution.
### `Combined`, `CombinedBinary`
Gets the combined standard output and error as a string or byte array.
### `Output`, `OutputBinary`
Gets the standard output as a string or byte array.
### `Error`, `ErrorBinary`
Gets the standard error as a string or byte array.
### `AsLines(bool trimEnd = false)`, `GetEnumerator()`
Gets the combined standard output and error as a per-line `IEnumerable<string>`.
```csharp
// equivalent to `foreach (var line in procOutput.AsLines())`
foreach (var line in procOutput) { ... }
```
### `ToString()`
The method equivalent to `Combined` property.
### `ExitCode`
Gets the exit code of the process.
## Utilities and shortcuts
Chell.Exports class also exposes a variety of useful utilities and shortcuts to libraries.
### `Prompt`
Prompts the user for input and gets it.
```csharp
var name = await Prompt("What's your name? ");
```
### `Chalk`: Kokuban: Terminal string styling
Provides a shortcut to [mayuki/Kokuban](https://github.com/mayuki/Kokuban). You can easily style the text on the terminal.
```csharp
// "Error: " will be colored.
Echo((Chalk.Red + "Error: ") + "Something went wrong.");
```
### `Glob`
Provides a shortcut to `Microsoft.Extensions.FileSystemGlobbing`.
- `Glob(params string[] patterns)`
- `Glob(string baseDir, string[] patterns)`
```csharp
// Glob patterns starting with '!' will be treated as excludes.
foreach (var path in Glob("**/*.cs", "!**/*.vb"))
{
...
}
```
### JSON serialize/deserialize (System.Text.Json)
Provides shortcuts to `System.Text.Json`.
- `ToJson<T>(T obj)`
```csharp
var obj = new { Name = "Alice", Age = 18 };
var json = ToJson(obj);
Echo(json); // {"Name":"Alice","Age":18}
```
- `FromJson<T>(string json)`
- `FromJson<T>(string json, T shape)`
```csharp
var json = "{ \"foo\": 123 }";
var obj = FromJson(json, new { Foo = 0 });
Dump(obj); // { Foo = 123 }
```
- `AsJson`
- `AsJsonLines`
```csharp
using Chell;
var output = await Run($"docker image ls --format {"{{json .}}"}");
foreach (var image in output.AsJsonLines(new { Repository = "", ID = "", Tag = ""}))
{
// ...
}
```
```csharp
using Chell;
var output = await Run($"kubectl version --client -o json");
var obj = output.AsJson(new { clientVersion = new { major = "", minor = "", gitVersion = "" } });
Echo(obj); // { clientVersion = { major = 1, minor = 21, gitVersion = v1.21.2 } }
```
### HTTP acccess (System.Net.Http)
Provides shortcuts to `System.Net.Http.HttpClient`.
- `FetchAsync`
- `FetchByteArrayAsync`
- `FetchStreamAsync`
- `FetchStringAsync`
## Chell as a Library
Chell can also be used as a utility library to run processes.
If you don't use `Chell.Exports`, you won't get any unnecessary methods or properties, and you can use `Run` and `ChellEnvironment`, `Exports` class.
```csharp
using Chell;
var results = await new Run($"ls -lF");
// ChellEnvironment.Current is equivalent to `Env` on `Chell.Exports`.
Console.WriteLine(ChellEnvironment.Current.ExecutablePath);
Console.WriteLine(ChellEnvironment.Current.ExecutableName);
Console.WriteLine(ChellEnvironment.Current.Arguments);
Console.WriteLine(ChellEnvironment.Current.Vars["PATH"]);
```
## Chell.Run
Chell.Run executes the input source code in an environment where Chell and some libraries are available.
It does not perform any NuGet package resolution, so we recommend creating a typical C# project if you need to handle such complexities.
```
$ dotnet tool install -g Chell.Run
```
```bash
$ chell -e "Echo(123);"
$ chell <<EOF
var result = await Run($"ls -lF");
Echo("Hello World!");
EOF
```
Chell.Run implicitly has some namespace imports and library references and can be used out-of-the-box.
Implicitly specified `using`s:
- System
- System.Collections
- System.Collections.Generic
- System.Diagnostics
- System.IO
- System.Text
- System.Text.RegularExpressions
- System.Linq
- System.Threading
- System.Threading.Tasks
- Chell
- Chell.Extensions
- Chell.Exports (using static)
Additional referenced libraries:
- Mono.Options
- Sharprompt
- Cocona.Lite
## Chell ❤ LINQPad
LINQPad is often used to do tasks in place of shell scripts. Chell can help you in this case as well.
Chell knows about LINQPad, so `Dump` method is replaced by LINQPad's `Dump`, and standard output and errors are displayed in the results without problems.

## License
```
MIT License
Copyright © Mayuki Sawatari <mayuki@misuzilla.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
================================================
FILE: samples/GettingStarted.Basic.Unix/GettingStarted.Basic.Unix.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Chell\Chell.csproj" />
</ItemGroup>
</Project>
================================================
FILE: samples/GettingStarted.Basic.Unix/Program.cs
================================================
using System;
using System.Linq;
using static Chell.Exports;
// Starts a process.
// The array will be expanded and the elements will be escaped
var dirs = new[] { "/", "/usr", "/bin" };
var results = await Run($"ls -l {dirs}");
// Enumerates the results to retrieve the standard output line by line.
foreach (var line in results)
{
Echo($"Result> {line}");
}
Echo();
// Built-in Variables
Echo((Chalk.Green + "ExecutableName: ") + string.Join(' ', ExecutableName));
Echo((Chalk.Green + "ExecutableDirectory: ") + string.Join(' ', ExecutableDirectory));
Echo((Chalk.Green + "Arguments: ") + string.Join(' ', Arguments));
Echo((Chalk.Green + "CurrentDirectory: ") + string.Join(' ', CurrentDirectory));
Echo();
// Environment Variables
Echo((Chalk.Green + "Env.Vars[\"PATH\"]: ") + Env.Vars["PATH"]);
Echo();
// Standard Input/Error as Stream + Utility methods.
StdOut.WriteLine("Hello World!");
StdErr.WriteLine("Hello World! (Error)");
Echo();
// Get the data from network and pipe it to the process
await (await FetchByteArrayAsync("http://www.example.com/") | Run("grep title"));
// Temporarily change the current directory.
using (Cd("/"))
{
await Run($"dir");
}
Exit(1);
================================================
FILE: samples/GettingStarted.Basic.Windows/GettingStarted.Basic.Windows.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Chell\Chell.csproj" />
</ItemGroup>
</Project>
================================================
FILE: samples/GettingStarted.Basic.Windows/Program.cs
================================================
using System;
using System.Linq;
using Chell;
using static Chell.Exports;
// Starts a process.
// The array will be expanded and the elements will be escaped
var dirs = new[] { @"C:\Windows\Microsoft.NET", @"C:\Program Files" };
var results = await Run($"dir {dirs}"); //
// Enumerates the results to retrieve the standard output line by line.
foreach (var line in results.Where(x => x.Contains("Windows")))
{
Echo($"Result> {line}");
}
Echo();
// Built-in Variables
Echo((Chalk.Green + "ExecutableName: ") + string.Join(' ', ExecutableName));
Echo((Chalk.Green + "ExecutableDirectory: ") + string.Join(' ', ExecutableDirectory));
Echo((Chalk.Green + "Arguments: ") + string.Join(' ', Arguments));
Echo((Chalk.Green + "CurrentDirectory: ") + string.Join(' ', CurrentDirectory));
Echo();
// Environment Variables
Echo((Chalk.Green + "Env.Vars[\"PATH\"]: ") + Env.Vars["PATH"]);
Echo();
// Standard Input/Error as Stream + Utility methods.
StdOut.WriteLine("Hello World!");
StdErr.WriteLine("Hello World! (Error)");
Echo();
// Get the data from network and pipe it to the process
await (await FetchByteArrayAsync("http://www.example.com/") | Run("findstr title"));
// Temporarily change the current directory.
using (Cd("C:\\Users"))
{
await Run($"dir");
}
Exit(1);
================================================
FILE: src/.editorconfig
================================================
###############################
# C# Nullability #
###############################
[*.cs]
# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable.
dotnet_diagnostic.CS8618.severity = error
# CS8604: Possible null reference argument.
dotnet_diagnostic.CS8604.severity = error
# CS8629: Nullable value type may be null.
dotnet_diagnostic.CS8629.severity = error
# CS8600: Converting null literal or possible null value to non-nullable type.
dotnet_diagnostic.CS8600.severity = error
# CS8603: Possible null reference return.
dotnet_diagnostic.CS8603.severity = error
# CS8610: Nullability of reference types in type of parameter doesn't match overridden member.
dotnet_diagnostic.CS8610.severity = error
# CS8625: Cannot convert null literal to non-nullable reference type.
dotnet_diagnostic.CS8625.severity = error
# CS8606: Possible null reference assignment to iteration variable
dotnet_diagnostic.CS8606.severity = error
# CS8602: Dereference of a possibly null reference.
dotnet_diagnostic.CS8602.severity = error
# CS8601: Possible null reference assignment.
dotnet_diagnostic.CS8601.severity = error
# CS8614: Nullability of reference types in type of parameter doesn't match implicitly implemented member.
dotnet_diagnostic.CS8614.severity = error
================================================
FILE: src/Chell/Chell.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;netcoreapp3.1;net5.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Kokuban" Version="0.1.0" />
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="5.0.0" />
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework) == 'netstandard2.1' or $(TargetFramework) == 'netcoreapp3.1'">
<PackageReference Include="System.Text.Json" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="Extensions\ProcessTaskExtensions.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>ProcessTaskExtensions.Generated.cs</LastGenOutput>
</None>
<None Update="IO\ChellWritableStream.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>ChellWritableStream.Generated.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Compile Update="Extensions\ProcessTaskExtensions.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ProcessTaskExtensions.tt</DependentUpon>
</Compile>
<Compile Update="IO\ChellWritableStream.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ChellWritableStream.tt</DependentUpon>
</Compile>
</ItemGroup>
</Project>
================================================
FILE: src/Chell/ChellEnvironment.cs
================================================
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Chell.Internal;
using Chell.IO;
using Chell.Shell;
namespace Chell
{
[Flags]
public enum ChellVerbosity
{
/// <summary>
/// By default, no output is written to the console.
/// </summary>
Silent = 0,
/// <summary>
/// Writes a executing command line to the console.
/// </summary>
CommandLine = 1 << 0,
/// <summary>
/// Writes a command output to the console.
/// </summary>
ConsoleOutputs = 1 << 1,
/// <summary>
/// Writes all command lines and command outputs.
/// </summary>
Full = CommandLine | ConsoleOutputs,
[EditorBrowsable(EditorBrowsableState.Never)]
Debug = Full | 1 << 31,
}
public class ChellEnvironment
{
public static ChellEnvironment Current { get; set; } = new ChellEnvironment();
private string[] _arguments;
private string? _executablePath;
private string _executableName;
private string _executableDirectory;
public ChellEnvironment()
{
var args = Environment.GetCommandLineArgs();
var path = args[0];
_arguments = args.Skip(1).ToArray();
_executablePath = path;
_executableName = Path.GetFileName(path);
_executableDirectory = Path.GetDirectoryName(path)!;
}
/// <summary>
/// Gets or sets the verbosity.
/// </summary>
public ChellVerbosity Verbosity { get; set; } = ChellVerbosity.Full;
public ShellExecutorProvider Shell { get; } = new ShellExecutorProvider();
public IConsoleProvider Console { get; set; } =
LINQPadHelper.RunningOnLINQPad
? new LINQPadConsoleProvider()
: SystemConsoleProvider.Instance;
/// <summary>
/// Gets the identifier for the current application process.
/// </summary>
public int ProcessId =>
#if NET5_0_OR_GREATER
Environment.ProcessId
#else
Process.GetCurrentProcess().Id
#endif
;
/// <summary>
/// Gets whether the current application is running on Windows.
/// </summary>
public bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
/// <summary>
/// Gets the command line arguments. like <c>args</c> of a entry point.
/// </summary>
public IReadOnlyList<string> Arguments => _arguments;
/// <summary>
/// Gets the path of the executing application. like <c>argv[0]</c>. (e.g. C:\\Path\To\App.exe, /path/to/app)
/// </summary>
/// <remarks>
/// The path may be null when running a inline script.
/// </remarks>
public string? ExecutablePath => _executablePath;
/// <summary>
/// Gets the name of the executing application. like <c>argv[0]</c>. (e.g. App.exe, app)
/// </summary>
public string ExecutableName => _executableName;
/// <summary>
/// Gets the directory of the executing application. like <c>argv[0]</c>. (e.g. C:\\Path\To, /path/to)
/// </summary>
public string ExecutableDirectory => _executableDirectory;
/// <summary>
/// Gets or sets the path of the current working directory.
/// </summary>
public string CurrentDirectory
{
get => Environment.CurrentDirectory;
set => Environment.CurrentDirectory = value;
}
/// <summary>
/// Gets the path of the current user's home directory.
/// </summary>
public string HomeDirectory => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
/// <summary>
/// Gets the environment variables as <see cref="IDictionary{String, String}"/> representation.
/// </summary>
public IDictionary<string, string> Vars { get; } = new EnvironmentVariables();
/// <summary>
/// Gets the standard input stream.
/// </summary>
public ChellReadableStream StdIn => new ChellReadableStream(this.Console.OpenStandardInput(), this.Console.InputEncoding);
/// <summary>
/// Gets the standard output stream.
/// </summary>
public ChellWritableStream StdOut => new ChellWritableStream(this.Console.OpenStandardOutput(), this.Console.OutputEncoding);
/// <summary>
/// Gets the standard output stream.
/// </summary>
public ChellWritableStream StdErr => new ChellWritableStream(this.Console.OpenStandardError(), this.Console.OutputEncoding);
/// <summary>
/// Gets or sets the default timeout for the process. The value affects the current application. The default value is <see cref="TimeSpan.Zero"/>.
/// </summary>
/// <remarks>
/// If the value is <see cref="TimeSpan.Zero"/> or <see cref="TimeSpan.MaxValue"/>, the process will not be timed out.
/// </remarks>
public TimeSpan ProcessTimeout { get; set; } = TimeSpan.Zero;
[EditorBrowsable(EditorBrowsableState.Never)]
public void SetCommandLineArgs(string? executablePath, string executableName, string executableDirectory, string[] args)
{
_arguments = args.ToArray();
_executableName = executableName;
_executablePath = executablePath;
_executableDirectory = executableDirectory;
}
}
}
================================================
FILE: src/Chell/CommandLineString.cs
================================================
using System;
using System.ComponentModel;
using System.Diagnostics;
namespace Chell
{
/// <summary>
/// Workaround for string/FormattableString overload issues
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[DebuggerDisplay("CommandLineString: String={StringValue,nq}; FormattableString={FormattableStringValue,nq}")]
public readonly struct CommandLineString
{
public string? StringValue { get; }
public FormattableString? FormattableStringValue { get; }
public CommandLineString(string value)
{
StringValue = value;
FormattableStringValue = null;
}
public CommandLineString(FormattableString value)
{
StringValue = null;
FormattableStringValue = value;
}
public static implicit operator CommandLineString(string value)
{
return new CommandLineString(value);
}
public static implicit operator CommandLineString(FormattableString value)
{
return new CommandLineString(value);
}
}
}
================================================
FILE: src/Chell/Exports.cs
================================================
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Chell.Internal;
using Chell.IO;
using Kokuban;
using Kokuban.AnsiEscape;
namespace Chell
{
public static class Exports
{
public static class Verbosity
{
/// <summary>
/// By default, no output is written to the console.
/// </summary>
public const ChellVerbosity Silent = ChellVerbosity.Silent;
/// <summary>
/// Writes a executing command line to the console.
/// </summary>
public const ChellVerbosity CommandLine = ChellVerbosity.CommandLine;
/// <summary>
/// Writes a command output to the console.
/// </summary>
public const ChellVerbosity ConsoleOutputs = ChellVerbosity.ConsoleOutputs;
/// <summary>
/// Writes all command lines and command outputs.
/// </summary>
public const ChellVerbosity Full = ChellVerbosity.Full;
}
/// <summary>
/// Gets the current environment.
/// </summary>
public static ChellEnvironment Env => ChellEnvironment.Current;
/// <summary>
/// Gets the standard input stream.
/// </summary>
public static ChellReadableStream StdIn => Env.StdIn;
/// <summary>
/// Gets the standard output stream.
/// </summary>
public static ChellWritableStream StdOut => Env.StdOut;
/// <summary>
/// Gets the standard error stream.
/// </summary>
public static ChellWritableStream StdErr => Env.StdErr;
/// <summary>
/// Gets the command line arguments. like <c>args</c> of a entry point.
/// </summary>
public static IReadOnlyList<string> Arguments => Env.Arguments;
/// <summary>
/// Gets the path of the executing application. like <c>argv[0]</c>. (e.g. C:\\Path\To\App.exe, /path/to/app)
/// </summary>
/// <remarks>
/// The path may be null when running a inline script.
/// </remarks>
public static string? ExecutablePath => Env.ExecutablePath;
/// <summary>
/// Gets the name of the executing application. like <c>argv[0]</c>. (e.g. App.exe, app)
/// </summary>
public static string ExecutableName => Env.ExecutableName;
/// <summary>
/// Gets the directory of the executing application. like <c>argv[0]</c>. (e.g. C:\\Path\To, /path/to)
/// </summary>
public static string ExecutableDirectory => Env.ExecutableDirectory;
/// <summary>
/// Gets or sets the path of the current working directory.
/// </summary>
public static string CurrentDirectory
{
get => Env.CurrentDirectory;
set => Env.CurrentDirectory = value;
}
/// <summary>
/// Gets the identifier for the current application process.
/// </summary>
public static int ProcessId => Env.ProcessId;
/// <summary>
/// Gets the Kokuban ANSI style builder to decorate texts.
/// </summary>
public static AnsiStyle Chalk => Kokuban.Chalk.Create(KokubanOptions.Default);
/// <summary>
/// Starts the process task with the specified command line.
/// </summary>
/// <param name="commandLine"></param>
/// <param name="options"></param>
/// <returns></returns>
public static ProcessTask Run(FormattableString commandLine, ProcessTaskOptions? options = default)
=> new ProcessTask(commandLine, options);
/// <summary>
/// Starts the process task with the specified command line.
/// </summary>
/// <param name="commandLine"></param>
/// <param name="options"></param>
/// <returns></returns>
public static ProcessTask Run(CommandLineString commandLine, ProcessTaskOptions? options = default)
=> new ProcessTask(commandLine, options);
/// <summary>
/// Starts the process task with the specified command line.
/// </summary>
/// <param name="inputStream">The data to be passed to the standard input of the process.</param>
/// <param name="commandLine"></param>
/// <param name="options"></param>
/// <returns></returns>
public static ProcessTask Run(Stream inputStream, FormattableString commandLine, ProcessTaskOptions? options = default)
=> new ProcessTask(inputStream, commandLine, options);
/// <summary>
/// Starts the process task with the specified command line.
/// </summary>
/// <param name="inputStream">The data to be passed to the standard input of the process.</param>
/// <param name="commandLine"></param>
/// <param name="options"></param>
/// <returns></returns>
public static ProcessTask Run(Stream inputStream, CommandLineString commandLine, ProcessTaskOptions? options = default)
=> new ProcessTask(inputStream, commandLine, options);
/// <summary>
/// Starts the process task with the specified command line.
/// </summary>
/// <param name="inputData">The data to be passed to the standard input of the process.</param>
/// <param name="commandLine"></param>
/// <param name="options"></param>
/// <returns></returns>
public static ProcessTask Run(ReadOnlyMemory<byte> inputData, FormattableString commandLine, ProcessTaskOptions? options = default)
=> new ProcessTask(inputData, commandLine, options);
/// <summary>
/// Starts the process task with the specified command line.
/// </summary>
/// <param name="inputData">The data to be passed to the standard input of the process.</param>
/// <param name="commandLine"></param>
/// <param name="options"></param>
/// <returns></returns>
public static ProcessTask Run(ReadOnlyMemory<byte> inputData, CommandLineString commandLine, ProcessTaskOptions? options = default)
=> new ProcessTask(inputData, commandLine, options);
/// <summary>
/// Writes the message to the console.
/// </summary>
/// <param name="message"></param>
public static void Echo(object? message = default)
=> ChellEnvironment.Current.Console.Out.WriteLine(message);
/// <summary>
/// Writes the object details to the console.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
public static void Dump<T>(T obj)
=> ObjectDumper.Dump(obj);
/// <summary>
/// Converts the object to a JSON.
/// </summary>
/// <typeparam name="T"></typeparam>
public static string ToJson<T>(T obj, JsonSerializerOptions? options = default)
=> JsonSerializer.Serialize<T>(obj, options);
/// <summary>
/// Converts the JSON to an object.
/// </summary>
/// <typeparam name="T"></typeparam>
public static T? FromJson<T>(string json, T shape)
=> FromJson<T>(json);
/// <summary>
/// Converts the JSON to an object.
/// </summary>
/// <typeparam name="T"></typeparam>
public static T? FromJson<T>(string json)
=> Chell.Extensions.StringExtensions.AsJson<T>(json);
/// <summary>
/// Changes the current directory to the specified path.
/// </summary>
/// <remarks>
/// Dispose the return value to return to the previous directory.
/// </remarks>
/// <param name="path"></param>
public static IDisposable Cd(string path)
=> new ChangeDirectoryScope(path);
private class ChangeDirectoryScope : IDisposable
{
private readonly string _previousCurrentDirectory;
public ChangeDirectoryScope(string newCurrentDirectory)
{
_previousCurrentDirectory = Environment.CurrentDirectory;
ChangeDirectory(newCurrentDirectory);
}
public void Dispose()
{
ChangeDirectory(_previousCurrentDirectory);
}
private void ChangeDirectory(string path)
{
CommandLineHelper.WriteCommandLineToConsole(ChellEnvironment.Current.Console, $"cd {path}");
Environment.CurrentDirectory = path;
}
}
/// <summary>
/// Sleeps for the specified time.
/// </summary>
/// <param name="timeSpan"></param>
/// <returns></returns>
public static Task Sleep(TimeSpan timeSpan)
=> Task.Delay(timeSpan);
/// <summary>
/// Sleeps for the specified time.
/// </summary>
/// <param name="seconds"></param>
/// <returns></returns>
public static Task Sleep(int seconds)
=> Task.Delay(TimeSpan.FromSeconds(seconds));
/// <summary>
/// Get the task to ignore the exception and return <see cref="ProcessOutput"/>.
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
public static Task<ProcessOutput> NoThrow(ProcessTask task)
=> task.NoThrow();
/// <summary>
/// Terminates the current process with specified exit code.
/// </summary>
/// <param name="exitCode"></param>
public static void Exit(int exitCode = 0)
=> Environment.Exit(exitCode);
/// <summary>
/// Creates a new directory and any necessary sub-directories in the specified path.
/// </summary>
/// <param name="path"></param>
public static void Mkdirp(string path)
=> Directory.CreateDirectory(path);
/// <summary>
/// Fetches the content of the specified URL using GET method.
/// </summary>
/// <param name="requestUri"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static Task<HttpResponseMessage> FetchAsync(string requestUri, CancellationToken cancellationToken = default)
{
CommandLineHelper.WriteCommandLineToConsole(ChellEnvironment.Current.Console, $"{nameof(FetchAsync)} {requestUri}");
var httpClient = new HttpClient();
return httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, requestUri), cancellationToken);
}
/// <summary>
/// Fetches the content of the specified URL as string using GET method.
/// </summary>
/// <param name="requestUri"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<string> FetchStringAsync(string requestUri, CancellationToken cancellationToken = default)
{
CommandLineHelper.WriteCommandLineToConsole(ChellEnvironment.Current.Console, $"{nameof(FetchStringAsync)} {requestUri}");
var httpClient = new HttpClient();
var res = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, requestUri), cancellationToken);
res.EnsureSuccessStatusCode();
#if NET5_0_OR_GREATER
return await res.Content.ReadAsStringAsync(cancellationToken);
#else
return await res.Content.ReadAsStringAsync();
#endif
}
/// <summary>
/// Fetches the content of the specified URL as byte[] using GET method.
/// </summary>
/// <param name="requestUri"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<byte[]> FetchByteArrayAsync(string requestUri, CancellationToken cancellationToken = default)
{
CommandLineHelper.WriteCommandLineToConsole(ChellEnvironment.Current.Console, $"{nameof(FetchByteArrayAsync)} {requestUri}");
var httpClient = new HttpClient();
var res = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, requestUri), cancellationToken);
res.EnsureSuccessStatusCode();
#if NET5_0_OR_GREATER
return await res.Content.ReadAsByteArrayAsync(cancellationToken);
#else
return await res.Content.ReadAsByteArrayAsync();
#endif
}
/// <summary>
/// Fetches the content of the specified URL as Stream using GET method.
/// </summary>
/// <param name="requestUri"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Stream> FetchStreamAsync(string requestUri, CancellationToken cancellationToken = default)
{
CommandLineHelper.WriteCommandLineToConsole(ChellEnvironment.Current.Console, $"{nameof(FetchStreamAsync)} {requestUri}");
var httpClient = new HttpClient();
var res = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, requestUri), cancellationToken);
res.EnsureSuccessStatusCode();
#if NET5_0_OR_GREATER
return await res.Content.ReadAsStreamAsync(cancellationToken);
#else
return await res.Content.ReadAsStreamAsync();
#endif
}
/// <summary>
/// Gets the full path to a command, similar to the `which` command on Unix.
/// </summary>
/// <param name="commandName"></param>
/// <returns></returns>
public static string Which(string commandName)
=> Internal.Which.TryGetPath(commandName, out var matchedPath)
? matchedPath
: throw new FileNotFoundException($"Command '{commandName}' is not found.");
/// <summary>
/// Gets the full path to a command, similar to the `which` command on Unix.
/// </summary>
/// <param name="commandName"></param>
/// <param name="matchedPath"></param>
/// <returns></returns>
public static bool TryWhich(string commandName, out string matchedPath)
=> Internal.Which.TryGetPath(commandName, out matchedPath);
/// <summary>
/// Enumerates paths under the current directory that match the specified glob pattern.
/// </summary>
/// <remarks>
/// A glob pattern accepts <c>*</c> and <c>**</c> (e.g. <c>**/*.cs</c>). If the specify a pattern is started with '!', it will be treated as an excluded pattern.
/// </remarks>
/// <param name="patterns"></param>
/// <returns></returns>
public static IEnumerable<string> Glob(params string[] patterns)
=> Glob(Environment.CurrentDirectory, patterns);
/// <summary>
/// Enumerates paths under the specified directory that match the specified glob pattern.
/// </summary>
/// <remarks>
/// A glob pattern accepts <c>*</c> and <c>**</c> (e.g. <c>**/*.cs</c>). If the specify a pattern is started with '!', it will be treated as an excluded pattern.
/// </remarks>
/// <param name="baseDir"></param>
/// <param name="patterns"></param>
/// <returns></returns>
public static IEnumerable<string> Glob(string baseDir, string[] patterns)
{
var matcher = new Matcher();
foreach (var pattern in patterns)
{
if (pattern.StartsWith("!"))
{
matcher.AddExclude(pattern.Substring(1));
}
else
{
matcher.AddInclude(pattern);
}
}
var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDir)));
return result.Files
.Select(x => Path.GetFullPath(Path.Combine(baseDir, x.Stem))); // NOTE: Microsoft.Extensions.FileSystemGlobbing 5.0.0 does not reflect the root directory in `Path`.
}
/// <summary>
/// Displays the message and reads lines entered by the user from the console.
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public static async Task<string?> Prompt(string message)
{
Console.Write(message);
return await Console.In.ReadLineAsync();
}
}
}
================================================
FILE: src/Chell/Extensions/ChellExtensions.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Chell.Internal;
namespace Chell.Extensions
{
public static class ChellExtensions
{
/// <summary>
/// Writes a object details to the output.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static T Dump<T>(this T value)
{
return ObjectDumper.Dump(value);
}
/// <summary>
/// Writes a object details to the output.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="task"></param>
/// <returns></returns>
public static async Task<T> Dump<T>(this Task<T> task)
{
var result = await task.ConfigureAwait(false);
ObjectDumper.Dump(result);
return result;
}
}
}
================================================
FILE: src/Chell/Extensions/ProcessOutputExtensions.cs
================================================
using System.Collections.Generic;
using System.Text.Json;
namespace Chell
{
public static class ProcessOutputExtensions
{
/// <summary>
/// Enumerates lines by converting them to objects as JSON.
/// </summary>
/// <remarks>
/// Converts to the anonymous type specified in <see cref="shape"/> argument.
/// </remarks>
public static IEnumerable<T?> AsJsonLines<T>(this ProcessOutput processOutput, T shape, bool skipEmptyLine = true, JsonSerializerOptions? options = null)
=> Chell.Extensions.StringExtensions.AsJsonLines<T>(processOutput.AsLines(), skipEmptyLine, options);
/// <summary>
/// Enumerates lines by converting them to objects as JSON.
/// </summary>
public static IEnumerable<T?> AsJsonLines<T>(this ProcessOutput processOutput, bool skipEmptyLine = true, JsonSerializerOptions? options = null)
=> Chell.Extensions.StringExtensions.AsJsonLines<T>(processOutput.AsLines(), skipEmptyLine, options);
/// <summary>
/// Converts the output string to an object as JSON.
/// </summary>
/// <remarks>
/// Converts to the anonymous type specified in <see cref="shape"/> argument.
/// </remarks>
public static T? AsJson<T>(this ProcessOutput processOutput, T shape, JsonSerializerOptions? options = null)
=> AsJson<T>(processOutput, options);
/// <summary>
/// Converts the output string to an object as JSON.
/// </summary>
public static T? AsJson<T>(this ProcessOutput processOutput, JsonSerializerOptions? options = null)
=> JsonSerializer.Deserialize<T>(processOutput.ToString(), options);
}
}
================================================
FILE: src/Chell/Extensions/ProcessTaskExtensions.Generated.cs
================================================
/// <auto-generated />
using System.Threading.Tasks;
namespace Chell
{
public static partial class ProcessTaskExtensions
{
public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutput Result1, ProcessOutput Result2)> GetAwaiter(this (ProcessTask T1, ProcessTask T2) tasks)
{
static async Task<(ProcessOutput Result1, ProcessOutput Result2)> WhenAllAsync(ProcessTask t1, ProcessTask t2)
{
var results = await Task.WhenAll<ProcessOutput>(t1, t2).ConfigureAwait(false);
return (results[0], results[1]);
}
return WhenAllAsync(tasks.T1, tasks.T2).GetAwaiter();
}
public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3)> GetAwaiter(this (ProcessTask T1, ProcessTask T2, ProcessTask T3) tasks)
{
static async Task<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3)> WhenAllAsync(ProcessTask t1, ProcessTask t2, ProcessTask t3)
{
var results = await Task.WhenAll<ProcessOutput>(t1, t2, t3).ConfigureAwait(false);
return (results[0], results[1], results[2]);
}
return WhenAllAsync(tasks.T1, tasks.T2, tasks.T3).GetAwaiter();
}
public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4)> GetAwaiter(this (ProcessTask T1, ProcessTask T2, ProcessTask T3, ProcessTask T4) tasks)
{
static async Task<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4)> WhenAllAsync(ProcessTask t1, ProcessTask t2, ProcessTask t3, ProcessTask t4)
{
var results = await Task.WhenAll<ProcessOutput>(t1, t2, t3, t4).ConfigureAwait(false);
return (results[0], results[1], results[2], results[3]);
}
return WhenAllAsync(tasks.T1, tasks.T2, tasks.T3, tasks.T4).GetAwaiter();
}
public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5)> GetAwaiter(this (ProcessTask T1, ProcessTask T2, ProcessTask T3, ProcessTask T4, ProcessTask T5) tasks)
{
static async Task<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5)> WhenAllAsync(ProcessTask t1, ProcessTask t2, ProcessTask t3, ProcessTask t4, ProcessTask t5)
{
var results = await Task.WhenAll<ProcessOutput>(t1, t2, t3, t4, t5).ConfigureAwait(false);
return (results[0], results[1], results[2], results[3], results[4]);
}
return WhenAllAsync(tasks.T1, tasks.T2, tasks.T3, tasks.T4, tasks.T5).GetAwaiter();
}
public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6)> GetAwaiter(this (ProcessTask T1, ProcessTask T2, ProcessTask T3, ProcessTask T4, ProcessTask T5, ProcessTask T6) tasks)
{
static async Task<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6)> WhenAllAsync(ProcessTask t1, ProcessTask t2, ProcessTask t3, ProcessTask t4, ProcessTask t5, ProcessTask t6)
{
var results = await Task.WhenAll<ProcessOutput>(t1, t2, t3, t4, t5, t6).ConfigureAwait(false);
return (results[0], results[1], results[2], results[3], results[4], results[5]);
}
return WhenAllAsync(tasks.T1, tasks.T2, tasks.T3, tasks.T4, tasks.T5, tasks.T6).GetAwaiter();
}
public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6, ProcessOutput Result7)> GetAwaiter(this (ProcessTask T1, ProcessTask T2, ProcessTask T3, ProcessTask T4, ProcessTask T5, ProcessTask T6, ProcessTask T7) tasks)
{
static async Task<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6, ProcessOutput Result7)> WhenAllAsync(ProcessTask t1, ProcessTask t2, ProcessTask t3, ProcessTask t4, ProcessTask t5, ProcessTask t6, ProcessTask t7)
{
var results = await Task.WhenAll<ProcessOutput>(t1, t2, t3, t4, t5, t6, t7).ConfigureAwait(false);
return (results[0], results[1], results[2], results[3], results[4], results[5], results[6]);
}
return WhenAllAsync(tasks.T1, tasks.T2, tasks.T3, tasks.T4, tasks.T5, tasks.T6, tasks.T7).GetAwaiter();
}
public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6, ProcessOutput Result7, ProcessOutput Result8)> GetAwaiter(this (ProcessTask T1, ProcessTask T2, ProcessTask T3, ProcessTask T4, ProcessTask T5, ProcessTask T6, ProcessTask T7, ProcessTask T8) tasks)
{
static async Task<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6, ProcessOutput Result7, ProcessOutput Result8)> WhenAllAsync(ProcessTask t1, ProcessTask t2, ProcessTask t3, ProcessTask t4, ProcessTask t5, ProcessTask t6, ProcessTask t7, ProcessTask t8)
{
var results = await Task.WhenAll<ProcessOutput>(t1, t2, t3, t4, t5, t6, t7, t8).ConfigureAwait(false);
return (results[0], results[1], results[2], results[3], results[4], results[5], results[6], results[7]);
}
return WhenAllAsync(tasks.T1, tasks.T2, tasks.T3, tasks.T4, tasks.T5, tasks.T6, tasks.T7, tasks.T8).GetAwaiter();
}
public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6, ProcessOutput Result7, ProcessOutput Result8, ProcessOutput Result9)> GetAwaiter(this (ProcessTask T1, ProcessTask T2, ProcessTask T3, ProcessTask T4, ProcessTask T5, ProcessTask T6, ProcessTask T7, ProcessTask T8, ProcessTask T9) tasks)
{
static async Task<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6, ProcessOutput Result7, ProcessOutput Result8, ProcessOutput Result9)> WhenAllAsync(ProcessTask t1, ProcessTask t2, ProcessTask t3, ProcessTask t4, ProcessTask t5, ProcessTask t6, ProcessTask t7, ProcessTask t8, ProcessTask t9)
{
var results = await Task.WhenAll<ProcessOutput>(t1, t2, t3, t4, t5, t6, t7, t8, t9).ConfigureAwait(false);
return (results[0], results[1], results[2], results[3], results[4], results[5], results[6], results[7], results[8]);
}
return WhenAllAsync(tasks.T1, tasks.T2, tasks.T3, tasks.T4, tasks.T5, tasks.T6, tasks.T7, tasks.T8, tasks.T9).GetAwaiter();
}
public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6, ProcessOutput Result7, ProcessOutput Result8, ProcessOutput Result9, ProcessOutput Result10)> GetAwaiter(this (ProcessTask T1, ProcessTask T2, ProcessTask T3, ProcessTask T4, ProcessTask T5, ProcessTask T6, ProcessTask T7, ProcessTask T8, ProcessTask T9, ProcessTask T10) tasks)
{
static async Task<(ProcessOutput Result1, ProcessOutput Result2, ProcessOutput Result3, ProcessOutput Result4, ProcessOutput Result5, ProcessOutput Result6, ProcessOutput Result7, ProcessOutput Result8, ProcessOutput Result9, ProcessOutput Result10)> WhenAllAsync(ProcessTask t1, ProcessTask t2, ProcessTask t3, ProcessTask t4, ProcessTask t5, ProcessTask t6, ProcessTask t7, ProcessTask t8, ProcessTask t9, ProcessTask t10)
{
var results = await Task.WhenAll<ProcessOutput>(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10).ConfigureAwait(false);
return (results[0], results[1], results[2], results[3], results[4], results[5], results[6], results[7], results[8], results[9]);
}
return WhenAllAsync(tasks.T1, tasks.T2, tasks.T3, tasks.T4, tasks.T5, tasks.T6, tasks.T7, tasks.T8, tasks.T9, tasks.T10).GetAwaiter();
}
}
}
================================================
FILE: src/Chell/Extensions/ProcessTaskExtensions.cs
================================================
using System.Threading.Tasks;
namespace Chell
{
public static partial class ProcessTaskExtensions
{
}
}
================================================
FILE: src/Chell/Extensions/ProcessTaskExtensions.tt
================================================
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".Generated.cs" #>
/// <auto-generated />
using System.Threading.Tasks;
namespace Chell
{
public static partial class ProcessTaskExtensions
{
<# for (var i = 2; i <= 10; i++) {
var inTuple = string.Join(", ", Enumerable.Range(1, i).Select(x => $"ProcessTask T{x}"));
var outTuple = string.Join(", ", Enumerable.Range(1, i).Select(x => $"ProcessOutput Result{x}"));
#>
public static System.Runtime.CompilerServices.TaskAwaiter<(<#= outTuple #>)> GetAwaiter(this (<#= inTuple #>) tasks)
{
static async Task<(<#= outTuple #>)> WhenAllAsync(<#= string.Join(", ", Enumerable.Range(1, i).Select(x => $"ProcessTask t{x}")) #>)
{
var results = await Task.WhenAll<ProcessOutput>(<#= string.Join(", ", Enumerable.Range(1, i).Select(x => $"t{x}")) #>).ConfigureAwait(false);
return (<#= string.Join(", ", Enumerable.Range(0, i).Select(x => $"results[{x}]")) #>);
}
return WhenAllAsync(<#= string.Join(", ", Enumerable.Range(1, i).Select(x => $"tasks.T{x}")) #>).GetAwaiter();
}
<# } #>
}
}
================================================
FILE: src/Chell/Extensions/StringExtensions.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Chell.Extensions
{
public static class StringExtensions
{
/// <summary>
/// Enumerates lines by converting them to objects as JSON.
/// </summary>
/// <remarks>
/// Converts to the anonymous type specified in <see cref="shape"/> argument.
/// </remarks>
public static IEnumerable<T?> AsJsonLines<T>(this IEnumerable<string> lines, T shape, bool skipEmptyLine = true, JsonSerializerOptions? options = null)
{
return AsJsonLines<T>(lines, skipEmptyLine, options);
}
/// <summary>
/// Enumerates lines by converting them to objects as JSON.
/// </summary>
public static IEnumerable<T?> AsJsonLines<T>(this IEnumerable<string> lines, bool skipEmptyLine = true, JsonSerializerOptions? options = null)
{
return lines.Where(x => !skipEmptyLine || !string.IsNullOrWhiteSpace(x)).Select(x => JsonSerializer.Deserialize<T>(x, options));
}
/// <summary>
/// Converts the output string to an object as JSON.
/// </summary>
/// <remarks>
/// Converts to the anonymous type specified in <see cref="shape"/> argument.
/// </remarks>
public static T? AsJson<T>(this string json, T shape, JsonSerializerOptions? options = null)
=> AsJson<T>(json, options);
/// <summary>
/// Converts the output string to an object as JSON.
/// </summary>
public static T? AsJson<T>(this string json, JsonSerializerOptions? options = null)
=> JsonSerializer.Deserialize<T>(json.ToString(), options);
}
}
================================================
FILE: src/Chell/IO/ChellWrappedStream.cs
================================================
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Chell.IO
{
public partial class ChellWritableStream : ChellWrappedStream
{
private readonly StreamWriter _writer;
public ChellWritableStream(Stream baseStream, Encoding encoding)
: base(baseStream)
{
_writer = new StreamWriter(baseStream, encoding);
_writer.AutoFlush = true;
}
public void Write(byte[] value) => BaseStream.Write(value);
public new void Write(ReadOnlySpan<byte> value) => BaseStream.Write(value);
public ValueTask WriteAsync(byte[] value, CancellationToken cancellationToken = default) => BaseStream.WriteAsync(value, cancellationToken);
public new ValueTask WriteAsync(ReadOnlyMemory<byte> value, CancellationToken cancellationToken = default) => BaseStream.WriteAsync(value, cancellationToken);
protected override void Dispose(bool disposing)
{
_writer.Dispose();
base.Dispose(disposing);
}
public override async ValueTask DisposeAsync()
{
await _writer.DisposeAsync();
await base.DisposeAsync();
}
}
public partial class ChellReadableStream : ChellWrappedStream
{
private readonly StreamReader _reader;
public ChellReadableStream(Stream baseStream, Encoding encoding)
: base(baseStream)
{
_reader = new StreamReader(baseStream, encoding);
}
public async Task<byte[]> ReadAllBytesAsync(CancellationToken cancellationToken = default)
{
var bufferWriter = new ArrayBufferWriter<byte>();
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
var readLen = await BaseStream.ReadAsync(bufferWriter.GetMemory(1024 * 32), cancellationToken);
if (readLen == 0)
{
return bufferWriter.WrittenMemory.ToArray();
}
bufferWriter.Advance(readLen);
}
}
public byte[] ReadAllBytes()
{
var bufferWriter = new ArrayBufferWriter<byte>();
while (true)
{
var readLen = BaseStream.Read(bufferWriter.GetSpan(1024 * 32));
if (readLen == 0)
{
return bufferWriter.WrittenMemory.ToArray();
}
bufferWriter.Advance(readLen);
}
}
public async Task<string> ReadToEndAsync()
{
return await _reader.ReadToEndAsync();
}
public string ReadToEnd()
{
return _reader.ReadToEnd();
}
public IEnumerable<string> ReadAllLines()
{
while (!_reader.EndOfStream)
{
var line = _reader.ReadLine();
if (line is null) yield break;
yield return line;
}
}
public async IAsyncEnumerable<string> ReadAllLinesAsync()
{
while (!_reader.EndOfStream)
{
var line = await _reader.ReadLineAsync();
if (line is null) yield break;
yield return line;
}
}
protected override void Dispose(bool disposing)
{
_reader.Dispose();
base.Dispose(disposing);
}
}
public abstract class ChellWrappedStream : Stream
{
private readonly Stream _baseStream;
protected Stream BaseStream => _baseStream;
protected ChellWrappedStream(Stream baseStream)
{
_baseStream = baseStream;
}
#region Stream Implementation
public override void Flush()
{
_baseStream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return _baseStream.Read(buffer, offset, count);
}
public override int Read(Span<byte> buffer)
{
return _baseStream.Read(buffer);
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _baseStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
{
return _baseStream.ReadAsync(buffer, cancellationToken);
}
public override long Seek(long offset, SeekOrigin origin)
{
return _baseStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
_baseStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
_baseStream.Write(buffer, offset, count);
}
public override void Write(ReadOnlySpan<byte> buffer)
{
_baseStream.Write(buffer);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _baseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
{
return _baseStream.WriteAsync(buffer, cancellationToken);
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => _baseStream.CanSeek;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position
{
get => _baseStream.Position;
set => _baseStream.Position = value;
}
#endregion
}
}
================================================
FILE: src/Chell/IO/ChellWritableStream.Generated.cs
================================================
#nullable enable
/// <auto-generated />
using System;
using System.IO;
using System.Threading.Tasks;
namespace Chell.IO
{
public partial class ChellWritableStream
{
public void Write(string? value) => _writer.Write(value);
public void WriteLine(string? value) => _writer.WriteLine(value);
public Task WriteAsync(string? value) => _writer.WriteAsync(value);
public Task WriteLineAsync(string? value) => _writer.WriteLineAsync(value);
public void Write(char value) => _writer.Write(value);
public void WriteLine(char value) => _writer.WriteLine(value);
public Task WriteAsync(char value) => _writer.WriteAsync(value);
public Task WriteLineAsync(char value) => _writer.WriteLineAsync(value);
public void Write(char[]? value) => _writer.Write(value);
public void WriteLine(char[]? value) => _writer.WriteLine(value);
public Task WriteAsync(char[]? value) => _writer.WriteAsync(value);
public Task WriteLineAsync(char[]? value) => _writer.WriteLineAsync(value);
public void Write(object? value) => _writer.Write(value);
public void WriteLine(object? value) => _writer.WriteLine(value);
public void Write(double value) => _writer.Write(value);
public void WriteLine(double value) => _writer.WriteLine(value);
public void Write(float value) => _writer.Write(value);
public void WriteLine(float value) => _writer.WriteLine(value);
public void Write(long value) => _writer.Write(value);
public void WriteLine(long value) => _writer.WriteLine(value);
public void Write(int value) => _writer.Write(value);
public void WriteLine(int value) => _writer.WriteLine(value);
public void Write(ReadOnlySpan<char> value) => _writer.Write(value);
public void WriteLine(ReadOnlySpan<char> value) => _writer.WriteLine(value);
public Task WriteAsync(ReadOnlyMemory<char> value) => _writer.WriteAsync(value);
public Task WriteLineAsync(ReadOnlyMemory<char> value) => _writer.WriteLineAsync(value);
}
}
================================================
FILE: src/Chell/IO/ChellWritableStream.tt
================================================
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".Generated.cs" #>
<#
var types = new []
{
(0, "string?"),
(0, "char"),
(0, "char[]?"),
(1, "object?"),
(1, "double"),
(1, "float"),
(1, "long"),
(1, "int"),
(1, "ReadOnlySpan<char>"),
(2, "ReadOnlyMemory<char>"),
};
#>
#nullable enable
/// <auto-generated />
using System;
using System.IO;
using System.Threading.Tasks;
namespace Chell.IO
{
public partial class ChellWritableStream
{
<# foreach (var (target, type) in types) { #>
<# if (target == 0 || target == 1) { #>
public void Write(<#= type #> value) => _writer.Write(value);
public void WriteLine(<#= type #> value) => _writer.WriteLine(value);
<# } #>
<# if (target == 0 || target == 2) { #>
public Task WriteAsync(<#= type #> value) => _writer.WriteAsync(value);
public Task WriteLineAsync(<#= type #> value) => _writer.WriteLineAsync(value);
<# } #>
<# } #>
}
}
================================================
FILE: src/Chell/IO/IConsoleProvider.cs
================================================
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Chell.IO
{
public interface IConsoleProvider
{
Stream OpenStandardInput();
Stream OpenStandardOutput();
Stream OpenStandardError();
Encoding InputEncoding { get; }
Encoding OutputEncoding { get; }
bool IsInputRedirected { get; }
bool IsOutputRedirected { get; }
bool IsErrorRedirected { get; }
TextWriter Out { get; }
TextWriter Error { get; }
}
}
================================================
FILE: src/Chell/IO/LINQPadConsoleProvider.cs
================================================
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Text;
using System.Threading.Tasks;
namespace Chell.IO
{
public class LINQPadConsoleProvider : IConsoleProvider
{
private readonly Pipe _pipe;
public Encoding InputEncoding => Console.InputEncoding;
public Encoding OutputEncoding => Console.OutputEncoding;
public bool IsInputRedirected => Console.IsInputRedirected;
public bool IsOutputRedirected => Console.IsOutputRedirected;
public bool IsErrorRedirected => Console.IsErrorRedirected;
public TextWriter Out { get; }
public TextWriter Error { get; }
public LINQPadConsoleProvider()
{
_pipe = new Pipe();
Out = new PipeTextWriter(_pipe.Writer, OutputEncoding);
Error = new PipeTextWriter(_pipe.Writer, OutputEncoding);
var reader = new StreamReader(_pipe.Reader.AsStream());
_ = Task.Run(async () =>
{
Memory<char> buffer = new char[1024];
while (true)
{
var read = await reader.ReadAsync(buffer, default);
if (read != 0)
{
Console.Out.Write(buffer.Span.Slice(0, read));
}
}
});
}
public Stream OpenStandardInput()
=> Console.OpenStandardInput();
public Stream OpenStandardOutput()
=> _pipe.Writer.AsStream(leaveOpen: true);
public Stream OpenStandardError()
=> _pipe.Writer.AsStream(leaveOpen: true);
private class PipeTextWriter : TextWriter
{
private readonly PipeWriter _writer;
public override Encoding Encoding { get; }
public PipeTextWriter(PipeWriter writer, Encoding encoding)
{
_writer = writer;
Encoding = encoding;
}
public override void Write(char value)
{
Span<byte> buffer = stackalloc byte[4];
Span<char> c = stackalloc char[1];
c[0] = value;
var written = Encoding.GetBytes(c, buffer);
_writer.Write(buffer.Slice(0, written));
_ = _writer.FlushAsync();
}
}
}
}
================================================
FILE: src/Chell/IO/SystemConsoleProvider.cs
================================================
using System;
using System.IO;
using System.Text;
namespace Chell.IO
{
/// <summary>
/// Encapsulates console intrinsic members as object.
/// </summary>
public sealed class SystemConsoleProvider : IConsoleProvider
{
public static IConsoleProvider Instance { get; } = new SystemConsoleProvider();
private SystemConsoleProvider()
{}
public Stream OpenStandardInput()
=> Console.OpenStandardInput();
public Stream OpenStandardOutput()
=> Console.OpenStandardOutput();
public Stream OpenStandardError()
=> Console.OpenStandardError();
public Encoding InputEncoding => Console.InputEncoding;
public Encoding OutputEncoding => Console.OutputEncoding;
public bool IsInputRedirected => Console.IsInputRedirected;
public bool IsOutputRedirected => Console.IsOutputRedirected;
public bool IsErrorRedirected => Console.IsErrorRedirected;
public TextWriter Out => Console.Out;
public TextWriter Error => Console.Error;
}
}
================================================
FILE: src/Chell/Internal/CommandLineHelper.cs
================================================
using System;
using System.Collections;
using System.Linq;
using Chell.IO;
using Chell.Shell;
using Kokuban;
// ReSharper disable CoVariantArrayConversion
namespace Chell.Internal
{
internal class CommandLineHelper
{
public static void WriteCommandLineToConsole(IConsoleProvider console, string commandLine, ChellVerbosity? verbosity = default)
{
verbosity ??= ChellEnvironment.Current.Verbosity;
if (verbosity.Value.HasFlag(ChellVerbosity.CommandLine))
{
var parts = commandLine.Split(' ', 2);
if (LINQPadHelper.RunningOnLINQPad)
{
LINQPadHelper.WriteRawHtml(
$"<span style=\"font-weight:bold;\">$ <span style=\"color:green\">{EscapeHtml(parts[0])}</span>{EscapeHtml((parts.Length > 1 ? " " + parts[1] : string.Empty))}</span>");
}
else
{
console.Out.WriteLine("$ " + (Chalk.BrightGreen + parts[0]) + (parts.Length > 1 ? " " + parts[1] : string.Empty));
}
}
static string EscapeHtml(string s)
=> s.Replace("&", "&").Replace("\"", """).Replace("<", ">");
}
public static string Expand(FormattableString commandLine, IShellExecutor shellExecutor)
{
return string.Format(commandLine.Format.Trim(), commandLine.GetArguments().Select(x =>
{
return x switch
{
ProcessOutput procOutput => shellExecutor.Escape(procOutput.Output.TrimEnd('\n')),
string s => shellExecutor.Escape(s),
IEnumerable enumerable => string.Join(" ", enumerable.OfType<object>().Select(y => shellExecutor.Escape(y.ToString() ?? string.Empty))),
null => string.Empty,
_ => shellExecutor.Escape(x.ToString() ?? string.Empty),
};
}).ToArray());
}
public static (string Command, string Arguments) Parse(FormattableString commandLine)
{
var (command, argumentsFormat) = Parse(commandLine.Format.Trim());
return (command, string.Format(argumentsFormat, commandLine.GetArguments().Select(x =>
{
if (x is IEnumerable enumerable)
{
return string.Join(" ", enumerable.OfType<object>().Select(y => Escape(y.ToString() ?? string.Empty)));
}
return Escape(x?.ToString() ?? string.Empty);
}).ToArray()));
}
public static (string Command, string Arguments) Parse(string commandLine)
{
if (commandLine.StartsWith("\""))
{
var pos = commandLine.IndexOf('"', 1);
if (pos == -1)
{
throw new InvalidOperationException("Invalid Command");
}
return (Command: commandLine.Substring(1, pos), Arguments: commandLine.Substring(pos));
}
else
{
var parts = commandLine.Split(' ', 2);
return (Command: parts[0], Arguments: parts.Length == 1 ? string.Empty : parts[1]);
}
}
public static string Escape(string v)
=> $"\"{v.Replace("`", "``").Replace("\"", "`\"")}\"";
}
}
================================================
FILE: src/Chell/Internal/EnvironmentVariables.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Chell.Internal
{
internal class EnvironmentVariables : IDictionary<string, string>
{
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return Environment.GetEnvironmentVariables()
.OfType<DictionaryEntry>()
.Select(x => KeyValuePair.Create((string)x.Key, (string)(x.Value ?? string.Empty)))
.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(KeyValuePair<string, string> item)
{
Environment.SetEnvironmentVariable(item.Key, item.Value);
}
public void Clear()
{
throw new NotSupportedException();
}
public bool Contains(KeyValuePair<string, string> item)
{
return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(item.Key));
}
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
{
throw new NotSupportedException();
}
public bool Remove(KeyValuePair<string, string> item)
{
if (Contains(item))
{
Environment.SetEnvironmentVariable(item.Key, null);
return true;
}
return false;
}
public int Count => Environment.GetEnvironmentVariables().Count;
public bool IsReadOnly => false;
public void Add(string key, string value)
{
Environment.SetEnvironmentVariable(key, value);
}
public bool ContainsKey(string key)
{
return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(key));
}
public bool Remove(string key)
{
if (ContainsKey(key))
{
Environment.SetEnvironmentVariable(key, null);
return true;
}
return false;
}
public bool TryGetValue(string key, out string value)
{
var tmpValue = Environment.GetEnvironmentVariable(key);
if (string.IsNullOrWhiteSpace(tmpValue))
{
value = string.Empty;
return false;
}
value = tmpValue;
return true;
}
public string this[string key]
{
get => TryGetValue(key, out var value) ? value : string.Empty;
set => Add(key, value);
}
public ICollection<string> Keys
=> Environment.GetEnvironmentVariables().OfType<DictionaryEntry>().Select(x => (string)x.Key).ToArray();
public ICollection<string> Values
=> Environment.GetEnvironmentVariables().OfType<DictionaryEntry>().Select(x => (string)(x.Value ?? string.Empty)).ToArray();
}
}
================================================
FILE: src/Chell/Internal/LINQPadHelper.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Chell.Internal
{
internal class LINQPadHelper
{
private static bool? _runningOnLINQPad;
internal static bool RunningOnLINQPad => _runningOnLINQPad ??= Type.GetType("LINQPad.Util, LINQPad.Runtime") != null;
public static void WriteRawHtml(string html)
{
Debug.Assert(RunningOnLINQPad);
var t = Type.GetType("LINQPad.Util, LINQPad.Runtime");
if (t != null)
{
var obj = t.InvokeMember("RawHtml", BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod, null, null, new[] {html});
ObjectDumper.Dump(obj);
}
}
}
}
================================================
FILE: src/Chell/Internal/ObjectDumper.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
using System.Threading.Tasks;
namespace Chell.Internal
{
internal static class ObjectDumper
{
public static T Dump<T>(T obj)
=> DumpMethodCache<T>.Method(obj);
private static class DumpMethodCache
{
public static MethodInfo? LINQPadDumpMethod { get; }
static DumpMethodCache()
{
var linqPadExtensionsType = Type.GetType("LINQPad.Extensions, LINQPad.Runtime");
if (linqPadExtensionsType != null)
{
LINQPadDumpMethod = linqPadExtensionsType.GetMethods()
.FirstOrDefault(x => x.Name == "Dump" && x.GetParameters().Length == 1);
}
}
}
private static class DumpMethodCache<T>
{
public static Func<T, T> Method { get; }
static DumpMethodCache()
{
if (DumpMethodCache.LINQPadDumpMethod != null)
{
var closedDumpMethod = DumpMethodCache.LINQPadDumpMethod.MakeGenericMethod(typeof(T));
Method = (Func<T, T>)closedDumpMethod.CreateDelegate(typeof(Func<T, T>));
}
else
{
Method = x =>
{
Console.WriteLine(JsonSerializer.Serialize(x, new JsonSerializerOptions() { Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), WriteIndented = true }));
return x;
};
}
}
}
}
}
================================================
FILE: src/Chell/Internal/OutputSink.cs
================================================
using System;
using System.IO;
using System.IO.Pipelines;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Chell.Internal
{
internal class OutputSink : IDisposable, IAsyncDisposable
{
private readonly Encoding _encoding;
private readonly Pipe _outputPipe;
private readonly Pipe _errorPipe;
private readonly MemoryStream _outputBuffer;
private readonly MemoryStream _errorBuffer;
private readonly MemoryStream _combinedBuffer;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly Task _readWriteTaskOutput;
private readonly Task _readWriteTaskError;
internal PipeWriter OutputWriter => _outputPipe.Writer;
internal PipeWriter ErrorWriter => _errorPipe.Writer;
public ReadOnlyMemory<byte> OutputBinary => new ReadOnlyMemory<byte>(_outputBuffer.GetBuffer(), 0, (int)_outputBuffer.Length);
public ReadOnlyMemory<byte> ErrorBinary => new ReadOnlyMemory<byte>(_errorBuffer.GetBuffer(), 0, (int)_errorBuffer.Length);
public ReadOnlyMemory<byte> CombinedBinary => new ReadOnlyMemory<byte>(_combinedBuffer.GetBuffer(), 0, (int)_combinedBuffer.Length);
public string Output => (_outputBuffer is { Length: > 0} s) ? _encoding.GetString(OutputBinary.Span) : string.Empty;
public string Error => (_errorBuffer is { Length: > 0 } s) ? _encoding.GetString(ErrorBinary.Span) : string.Empty;
public string Combined => (_combinedBuffer is { Length: > 0 } s) ? _encoding.GetString(CombinedBinary.Span) : string.Empty;
public OutputSink(Encoding encoding)
{
_encoding = encoding;
_outputPipe = new Pipe();
_errorPipe = new Pipe();
_cancellationTokenSource = new CancellationTokenSource();
_outputBuffer = new MemoryStream();
_errorBuffer = new MemoryStream();
_combinedBuffer = new MemoryStream();
_readWriteTaskOutput = RunReadWriteLoopAsync(_outputPipe.Reader, _outputBuffer, _cancellationTokenSource.Token);
_readWriteTaskError = RunReadWriteLoopAsync(_errorPipe.Reader, _errorBuffer, _cancellationTokenSource.Token);
}
public async Task CompleteAsync()
{
await _outputPipe.Writer.CompleteAsync().ConfigureAwait(false);
await _errorPipe.Writer.CompleteAsync().ConfigureAwait(false);
try
{
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5));
await _readWriteTaskOutput.ConfigureAwait(false);
await _readWriteTaskError.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
}
private async Task RunReadWriteLoopAsync(PipeReader reader, Stream dest, CancellationToken cancellationToken)
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
var result = await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
if (result.Buffer.IsSingleSegment)
{
dest.Write(result.Buffer.FirstSpan);
_combinedBuffer.Write(result.Buffer.FirstSpan);
}
else
{
foreach (var segment in result.Buffer)
{
dest.Write(segment.Span);
_combinedBuffer.Write(segment.Span);
}
}
reader.AdvanceTo(result.Buffer.End);
if (result.IsCanceled || result.IsCompleted)
{
return;
}
}
}
public void Dispose()
{
try
{
CompleteAsync().Wait();
}
catch { }
}
public async ValueTask DisposeAsync()
{
try
{
await CompleteAsync().ConfigureAwait(false);
}
catch { }
}
}
}
================================================
FILE: src/Chell/Internal/StandardInput.cs
================================================
using System;
using System.Threading;
namespace Chell.Internal
{
internal class StandardInput
{
private static readonly Lazy<StreamPipe> _pipe;
internal static StreamPipe Pipe => _pipe.Value;
static StandardInput()
{
_pipe = new Lazy<StreamPipe>(() => new StreamPipe(Console.OpenStandardInput()), LazyThreadSafetyMode.ExecutionAndPublication);
}
}
}
================================================
FILE: src/Chell/Internal/StreamPipe.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Chell.Internal
{
internal class StreamPipe
{
private readonly Pipe _pipe;
private readonly Stream _baseStream;
private readonly Task _copyTask;
private readonly Task _readerTask;
private readonly CancellationTokenSource _cancellationTokenSourceCopyStreamToPipe;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly object _syncLock = new object();
private readonly List<object> _destinations = new List<object>();
private readonly TaskCompletionSource<bool> _destinationsReady = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
private bool _shutdown;
public StreamPipe(Stream baseStream)
{
_pipe = new Pipe(new PipeOptions());
_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
_cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSourceCopyStreamToPipe = new CancellationTokenSource();
_copyTask = CopyStreamToPipeAsync(_cancellationTokenSourceCopyStreamToPipe.Token);
_readerTask = RunReadLoopAsync(_cancellationTokenSource.Token);
}
private async Task CopyStreamToPipeAsync(CancellationToken cancellationToken)
{
try
{
await _baseStream.CopyToAsync(_pipe.Writer, cancellationToken).ConfigureAwait(false);
await _pipe.Writer.CompleteAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
await _pipe.Writer.CompleteAsync(ex).ConfigureAwait(false);
}
}
public async Task CompleteAsync()
{
_shutdown = true;
_cancellationTokenSource.CancelAfter(1000);
_cancellationTokenSourceCopyStreamToPipe.CancelAfter(1000);
try
{
await _copyTask.ConfigureAwait(false);
await _readerTask.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
}
public void Ready()
{
_destinationsReady.TrySetResult(true);
}
public StreamPipe Connect(Stream stream)
{
lock (_syncLock)
{
_destinations.Add(stream ?? throw new ArgumentNullException(nameof(stream)));
}
return this;
}
public StreamPipe Connect(PipeWriter writer)
{
lock (_syncLock)
{
_destinations.Add(writer ?? throw new ArgumentNullException(nameof(writer)));
}
return this;
}
public StreamPipe Disconnect(Stream stream)
{
lock (_syncLock)
{
_destinations.Remove(stream ?? throw new ArgumentNullException(nameof(stream)));
}
return this;
}
public StreamPipe Disconnect(PipeWriter writer)
{
lock (_syncLock)
{
_destinations.Remove(writer ?? throw new ArgumentNullException(nameof(writer)));
}
return this;
}
private async Task RunReadLoopAsync(CancellationToken cancellationToken)
{
var cancellationTokenTask = new TaskCompletionSource<bool>();
await using var cancellationTokenRegistration = cancellationToken.Register(() => cancellationTokenTask.TrySetCanceled(cancellationToken));
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
// Wait for the destination to be available.
object[] dests;
lock (_syncLock)
{
dests = _destinations.ToArray();
}
while (dests.Length == 0)
{
if (_shutdown) return;
cancellationToken.ThrowIfCancellationRequested();
// Wait for signal to start sending to the destinations.
// This will wait only once after the process starts.
await Task.WhenAny(cancellationTokenTask.Task, _destinationsReady.Task).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
lock (_syncLock)
{
dests = _destinations.ToArray();
}
if (dests.Length == 0)
{
await Task.Yield();
}
}
// Reads from the pipe reader.
var result = await _pipe.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
// Get the current destination again.
// If there is no destination after reading, do not advance the pipe, and go to the top of the loop.
lock (_syncLock)
{
var destsCurrent = _destinations.ToArray();
if (!dests.SequenceEqual(destsCurrent))
{
dests = destsCurrent;
if (dests.Length == 0)
{
_pipe.Reader.AdvanceTo(result.Buffer.Start);
continue;
}
}
}
// Writes to the destinations.
await Task.WhenAll(dests.Select(async x =>
{
var writeTask = x switch
{
Stream stream => WriteAsync(stream, result, cancellationToken),
PipeWriter writer => WriteAsync(writer, result, cancellationToken),
_ => throw new NotSupportedException()
};
// NOTE: The destination may be closed first.
// When the destination is closed, the task throws an IOException (Broken pipe).
try
{
await writeTask.ConfigureAwait(false);
}
catch (IOException)
{
}
})).ConfigureAwait(false);
_pipe.Reader.AdvanceTo(result.Buffer.End);
if (result.IsCanceled || result.IsCompleted)
{
return;
}
}
}
private static async ValueTask WriteAsync(Stream stream, ReadResult result, CancellationToken cancellationToken)
{
if (!result.Buffer.IsEmpty)
{
if (result.Buffer.IsSingleSegment)
{
await stream.WriteAsync(result.Buffer.First, cancellationToken).ConfigureAwait(false);
}
else
{
foreach (var segment in result.Buffer)
{
await stream.WriteAsync(segment, cancellationToken).ConfigureAwait(false);
}
}
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
if (result.IsCompleted || result.IsCanceled)
{
stream.Close();
}
}
private static async ValueTask WriteAsync(PipeWriter writer, ReadResult result, CancellationToken cancellationToken)
{
if (!result.Buffer.IsEmpty)
{
if (result.Buffer.IsSingleSegment)
{
await writer.WriteAsync(result.Buffer.First, cancellationToken).ConfigureAwait(false);
}
else
{
foreach (var segment in result.Buffer)
{
await writer.WriteAsync(segment, cancellationToken).ConfigureAwait(false);
}
}
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
}
if (result.IsCompleted || result.IsCanceled)
{
await writer.CompleteAsync().ConfigureAwait(false);
}
}
}
}
================================================
FILE: src/Chell/Internal/Which.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Chell.Internal
{
public static class Which
{
public static bool TryGetPath(string commandName, out string matchedPath)
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var paths = (Environment.GetEnvironmentVariable("PATH") ?? string.Empty).Split(isWindows ? ';' : ':');
var pathExts = Array.Empty<string>();
if (isWindows)
{
paths = paths.Prepend(Environment.CurrentDirectory).ToArray();
pathExts = (Environment.GetEnvironmentVariable("PATHEXT") ?? ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC").Split(';');
}
foreach (var path in paths)
{
// /path/to/foo.ext
foreach (var ext in pathExts)
{
var fullPath = Path.Combine(path, $"{commandName}{ext}");
if (File.Exists(fullPath))
{
matchedPath = fullPath;
return true;
}
}
// /path/to/foo
{
var fullPath = Path.Combine(path, commandName);
if (File.Exists(fullPath))
{
matchedPath = fullPath;
return true;
}
}
}
matchedPath = string.Empty;
return false;
}
}
}
================================================
FILE: src/Chell/ProcessOutput.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Chell.Internal;
namespace Chell
{
/// <summary>
/// Provides the outputs and results from the process.
/// </summary>
/// <remarks>
/// If the output is redirected or piped, it will not be captured.
/// </remarks>
public class ProcessOutput : IEnumerable<string>
{
/// <summary>
/// Gets the standard outputs as <see cref="String"/>.
/// </summary>
public string Output => Sink.Output;
/// <summary>
/// Gets the standard errors as <see cref="String"/>.
/// </summary>
public string Error => Sink.Error;
/// <summary>
/// Gets the standard outputs and standard errors as <see cref="String"/>.
/// </summary>
public string Combined => Sink.Combined;
/// <summary>
/// Gets the standard outputs as <see cref="byte"/> sequence.
/// </summary>
public ReadOnlyMemory<byte> OutputBinary => Sink.OutputBinary;
/// <summary>
/// Gets the standard errors as <see cref="byte"/> sequence.
/// </summary>
public ReadOnlyMemory<byte> ErrorBinary => Sink.ErrorBinary;
/// <summary>
/// Gets the standard outputs and standard errors as <see cref="byte"/> sequence.
/// </summary>
public ReadOnlyMemory<byte> CombinedBinary => Sink.CombinedBinary;
/// <summary>
/// Get the exit code when the process terminated.
/// </summary>
public int ExitCode { get; internal set; }
internal OutputSink Sink { get; }
public ProcessOutput(Encoding encoding)
{
Sink = new OutputSink(encoding);
}
public static implicit operator string(ProcessOutput processOutput)
=> processOutput.ToString();
/// <summary>
/// Gets the standard outputs and standard errors as <see cref="String"/>.
/// </summary>
public override string ToString() => Combined;
public IEnumerator<string> GetEnumerator()
=> AsLines().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
/// <summary>
/// Gets the standard outputs and standard errors as lines.
/// </summary>
public IEnumerable<string> AsLines(bool trimEnd = false)
=> (trimEnd ? Combined.TrimEnd('\n', '\r') : Combined).Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
}
}
================================================
FILE: src/Chell/ProcessTask.cs
================================================
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Chell.Internal;
namespace Chell
{
/// <summary>
/// Represents the execution task of a process.
/// </summary>
public class ProcessTask
{
private static readonly TimeSpan ProcessStartScheduledDelay = TimeSpan.FromMilliseconds(250);
private static int _idSequence = 0;
private readonly int _id;
private readonly DateTimeOffset _startedAt;
private readonly Lazy<Task<ProcessOutput>> _taskLazy;
private readonly ProcessOutput _output;
private readonly ProcessTaskOptions _options;
private readonly CancellationTokenSource _processCancellation;
private readonly CancellationTokenRegistration _processCancellationRegistration;
private readonly object _syncLock = new object();
private string? _processName;
private Process? _process;
private ExceptionDispatchInfo? _processException;
private Stream? _stdInStream;
private StreamPipe? _stdOutPipe;
private StreamPipe? _stdErrorPipe;
private bool _piped;
private bool _hasStandardIn;
private bool _suppressPipeToConsole;
private bool _suppressExceptionExitCodeNonZero;
/// <summary>
/// Gets the process of the task. If the process is not started yet, the property returns <value>null</value>.
/// </summary>
public Process? Process => _process;
/// <summary>
/// Gets the command line string of the task.
/// </summary>
public string CommandLine { get; }
/// <summary>
/// Gets the command name of the task.
/// </summary>
/// <remarks>
/// This value is passed to <see cref="ProcessStartInfo.FileName"/>.
/// </remarks>
public string Command { get; }
/// <summary>
/// Gets the arguments of the task.
/// </summary>
/// <remarks>
/// The value is passed to <see cref="ProcessStartInfo.Arguments"/>. It may be escaped or quoted.
/// </remarks>
public string Arguments { get; }
/// <summary>
/// Gets the previous task of the task if piped.
/// </summary>
public ProcessTask? PreviousTask { get; private set; }
public ProcessTask(FormattableString commandLine, ProcessTaskOptions? options = default)
: this(default(Stream?), commandLine, options)
{ }
public ProcessTask(Stream? inputStream, FormattableString commandLine, ProcessTaskOptions? options = default)
: this(inputStream, CommandLineHelper.Expand(commandLine, options?.ShellExecutor ?? ChellEnvironment.Current.Shell.Executor), options)
{ }
public ProcessTask(ReadOnlyMemory<byte> inputData, FormattableString commandLine, ProcessTaskOptions? options = default)
: this(new MemoryStream(inputData.ToArray()), CommandLineHelper.Expand(commandLine, options?.ShellExecutor ?? ChellEnvironment.Current.Shell.Executor), options)
{ }
// NOTE: The overload of `string commandLine` cannot be made public due to restrictions.
public ProcessTask(CommandLineString commandLine, ProcessTaskOptions? options = default)
: this(default(Stream?), commandLine, options)
{ }
public ProcessTask(Stream? inputStream, CommandLineString commandLine, ProcessTaskOptions? options = default)
: this(inputStream, commandLine.StringValue ?? CommandLineHelper.Expand(commandLine.FormattableStringValue ?? throw new InvalidOperationException("The command line string cannot be null."), options?.ShellExecutor ?? ChellEnvironment.Current.Shell.Executor), options)
{ }
public ProcessTask(ReadOnlyMemory<byte> inputData, CommandLineString commandLine, ProcessTaskOptions? options = default)
: this(new MemoryStream(inputData.ToArray()), commandLine.StringValue ?? CommandLineHelper.Expand(commandLine.FormattableStringValue ?? throw new InvalidOperationException("The command line string cannot be null."), options?.ShellExecutor ?? ChellEnvironment.Current.Shell.Executor), options)
{ }
private ProcessTask(Stream? inputStream, string commandLine, ProcessTaskOptions? options)
: this(inputStream, commandLine, (options?.ShellExecutor ?? ChellEnvironment.Current.Shell.Executor).GetCommandAndArguments(commandLine), options)
{ }
public ProcessTask(string command, string arguments, ProcessTaskOptions? options = default)
: this(default(Stream?), command, arguments, options)
{ }
public ProcessTask(Stream? inputStream, string command, string arguments, ProcessTaskOptions? options = default)
: this(inputStream, $"{command} {arguments}", (command, arguments), options)
{ }
public ProcessTask(ReadOnlyMemory<byte> inputData, string command, string arguments, ProcessTaskOptions? options = default)
: this(new MemoryStream(inputData.ToArray()), $"{command} {arguments}", (command, arguments), options)
{ }
private ProcessTask(Stream? inputStream, string commandLine, (string Command, string Arguments) commandAndArguments, ProcessTaskOptions? options = default)
{
_startedAt = DateTimeOffset.Now;
_options = options ?? new ProcessTaskOptions();
_id = Interlocked.Increment(ref _idSequence);
_processCancellation = new CancellationTokenSource();
_processCancellationRegistration = _processCancellation.Token.Register(() =>
{
WriteDebugTrace($"ProcessTimedOut: Pid={_process?.Id}; StartedAt={_startedAt}; Elapsed={DateTimeOffset.Now - _startedAt}");
#if NET5_0_OR_GREATER || NETCOREAPP3_0 || NETCOREAPP3_1
_process?.Kill(true);
#else
// TODO: .NET Standard 2.0 or 2.1 does not support kill child processes.
_process?.Kill();
#endif
});
_output = new ProcessOutput(_options.ShellExecutor.Encoding);
_taskLazy = new Lazy<Task<ProcessOutput>>(AsTaskCore, LazyThreadSafetyMode.ExecutionAndPublication);
_suppressPipeToConsole = !_options.Verbosity.HasFlag(ChellVerbosity.ConsoleOutputs);
CommandLine = commandLine ?? throw new ArgumentNullException(nameof(commandLine));
(Command, Arguments) = commandAndArguments;
if (inputStream != null)
{
// Set the stdin stream and start a process immediately.
ConnectStreamToStandardInput(inputStream);
}
else if (_options.RedirectStandardInput)
{
// Enable stdin redirection and start a process immediately.
RedirectStandardInput();
}
else
{
// Delay startup to allow time to configure the stdin stream.
// If a Task is requested (e.g. `await`, `AsTask`, `Pipe` ...), it will be started immediately.
_ = ScheduleStartProcessAsync();
}
WriteDebugTrace($"Created: {commandLine}");
async Task ScheduleStartProcessAsync()
{
await Task.Delay(ProcessStartScheduledDelay).ConfigureAwait(false);
EnsureProcess();
}
}
public static ProcessTask operator |(ProcessTask a, FormattableString b)
=> a.Pipe(new ProcessTask(b));
public static ProcessTask operator |(ProcessTask a, Stream b)
=> a.Pipe(b);
public static ProcessTask operator |(ProcessTask a, ProcessTask b)
=> a.Pipe(b);
public static ProcessTask operator |(Stream a, ProcessTask b)
{
b.ConnectStreamToStandardInput(a);
return b;
}
public static ProcessTask operator |(ReadOnlyMemory<byte> a, ProcessTask b)
{
b.ConnectStreamToStandardInput(new MemoryStream(a.ToArray()));
return b;
}
public static implicit operator Task(ProcessTask task)
=> task.AsTask();
public static implicit operator Task<ProcessOutput>(ProcessTask task)
=> task.AsTask();
/// <summary>
/// Gets the output of the process as Task.
/// </summary>
/// <returns></returns>
public async Task<ProcessOutput> AsTask()
{
try
{
return await _taskLazy.Value.ConfigureAwait(false);
}
catch when (_suppressExceptionExitCodeNonZero)
{
return _output;
}
}
public System.Runtime.CompilerServices.TaskAwaiter<ProcessOutput> GetAwaiter()
{
return AsTask().GetAwaiter();
}
/// <summary>
/// Gets the exit code of the process as Task.
/// </summary>
public Task<int> ExitCode
{
get
{
if (_taskLazy.Value.IsCompleted)
{
return Task.FromResult(_output.ExitCode);
}
return _taskLazy.Value.ContinueWith(x => _output.ExitCode);
}
}
/// <summary>
/// Configures the task to ignore the exception when the process returns exit code with non-zero.
/// </summary>
/// <returns></returns>
public ProcessTask NoThrow()
{
_suppressExceptionExitCodeNonZero = true;
return this;
}
public override string ToString()
{
return PreviousTask != null ? $"{PreviousTask} | {CommandLine}" : $"{CommandLine}";
}
/// <summary>
/// Enables standard output redirection of the process.
/// </summary>
/// <remarks>
/// Redirecting standard input must be done before the process has started. You can also use a <see cref="ProcessTaskOptions"/> that is guaranteed to enable redirection while creating a <see cref="ProcessTask"/>.
/// </remarks>
public void RedirectStandardInput(bool immediateLaunchProcess = true)
{
WriteDebugTrace($"RedirectStandardInput: immediateLaunchProcess={immediateLaunchProcess}");
lock (_syncLock)
{
if (_process != null)
{
throw new InvalidOperationException("The process has already been started. Redirecting standard input must be done before the process has started.");
}
_hasStandardIn = true;
}
if (immediateLaunchProcess)
{
EnsureProcess();
}
}
/// <summary>
/// Connects a stream to the standard input of the process.
/// </summary>
/// <remarks>
/// Connecting standard input must be done before the process has started. You can also use a constructor argument that is guaranteed to receive a stream.
/// </remarks>
/// <param name="stream"></param>
public void ConnectStreamToStandardInput(Stream stream)
{
lock (_syncLock)
{
if (_stdInStream != null) throw new InvalidOperationException("The standard input has already connected to the process.");
_stdInStream = stream ?? throw new ArgumentNullException(nameof(stream));
}
RedirectStandardInput();
}
/// <summary>
/// Suppresses output to the console if the standard output of the process is not configured.
/// </summary>
/// <returns></returns>
public ProcessTask SuppressConsoleOutputs()
{
_suppressPipeToConsole = true;
return this;
}
/// <summary>
/// Pipes the standard output to the stream.
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public ProcessTask Pipe(Stream stream)
{
EnsureProcess();
if (_stdOutPipe != null && _stdErrorPipe != null)
{
_stdOutPipe.Connect(stream);
}
_piped = true;
ReadyPipe();
return this;
}
/// <summary>
/// Pipes the standard output to the another process.
/// </summary>
/// <param name="nextProcess"></param>
/// <returns></returns>
public ProcessTask Pipe(ProcessTask nextProcess)
{
// First, enable standard input redirection before starting a process for the next ProcessTask.
nextProcess.RedirectStandardInput(immediateLaunchProcess: false);
// Second, start a process.
EnsureProcess();
// Third, start a process for the next ProcessTask.
nextProcess.EnsureProcess();
if (_stdOutPipe != null && _stdErrorPipe != null)
{
if (nextProcess.Process != null)
{
WriteDebugTrace($"Pipe: {Process!.Id} -> {nextProcess.Process.Id}");
_stdOutPipe.Connect(nextProcess.Process.StandardInput.BaseStream ?? Stream.Null);
}
}
nextProcess.PreviousTask = this;
_piped = true;
ReadyPipe();
return nextProcess;
}
private void EnsureProcess()
{
lock (_syncLock)
{
if (_process is null && _processException is null)
{
StartProcess();
}
}
}
private void StartProcess()
{
Debug.Assert(_process is null);
Debug.Assert(_processException is null);
// Enable only when stdin is redirected or has input stream.
// If RedirectStandardInput or CreateNoWindow is set to 'true', a process will not be interactive.
var procStartInfo = new ProcessStartInfo
{
FileName = Command,
Arguments = Arguments,
UseShellExecute = false,
CreateNoWindow = _options.Console.IsInputRedirected,
RedirectStandardOutput = true,
RedirectStandardInput = _options.Console.IsInputRedirected || _hasStandardIn,
RedirectStandardError = true,
WorkingDirectory = _options.WorkingDirectory ?? string.Empty,
};
if (_options.Verbosity.HasFlag(ChellVerbosity.CommandLine))
{
CommandLineHelper.WriteCommandLineToConsole(_options.Console, CommandLine, _options.Verbosity);
}
try
{
_processName = procStartInfo.FileName;
_process = Process.Start(procStartInfo)!;
WriteDebugTrace($"Process.Start: Pid={_process.Id}; HasStandardIn={_hasStandardIn}; StandardIn={_stdInStream}; IsInputRedirected={Console.IsInputRedirected}");
// Set a timeout if the option has non-zero or max-value.
if (_options.Timeout != TimeSpan.MaxValue && _options.Timeout != TimeSpan.Zero)
{
_processCancellation.CancelAfter(_options.Timeout);
}
// Connect the stream to the standard input.
if (_stdInStream != null)
{
_ = CopyCoreAsync(_stdInStream, _process.StandardInput.BaseStream);
}
_stdOutPipe = new StreamPipe(Process?.StandardOutput.BaseStream ?? Stream.Null);
_stdErrorPipe = new StreamPipe(Process?.StandardError.BaseStream ?? Stream.Null);
}
catch (Exception e)
{
_processException = ExceptionDispatchInfo.Capture(e);
}
static async Task CopyCoreAsync(Stream src, Stream dest)
{
await UnbufferedCopyToAsync(src, dest).ConfigureAwait(false);
dest.Close();
}
}
private void ReadyPipe()
{
WriteDebugTrace($"ReadyPipe: Pid={Process?.Id}; Piped={_piped}; _stdOutPipe={_stdOutPipe}; _stdErrorPipe={_stdErrorPipe}");
if (!_piped)
{
if (!_suppressPipeToConsole)
{
_stdOutPipe?.Connect(_options.Console.OpenStandardOutput());
_stdErrorPipe?.Connect(_options.Console.OpenStandardError());
}
_stdOutPipe?.Connect(_output.Sink.OutputWriter);
_stdErrorPipe?.Connect(_output.Sink.ErrorWriter);
}
_stdOutPipe?.Ready();
_stdErrorPipe?.Ready();
}
private static async Task UnbufferedCopyToAsync(Stream src, Stream dest, CancellationToken cancellationToken = default)
{
var buffer = new byte[80 * 1024];
while (true)
{
var read = await src.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
if (read == 0)
{
return;
}
await dest.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
await dest.FlushAsync(cancellationToken).ConfigureAwait(false);
}
}
private async Task ThrowIfParentTaskHasThrownProcessException(bool awaitForComplete)
{
if (PreviousTask != null)
{
// First, throw an exception for the parent task.
await PreviousTask.ThrowIfParentTaskHasThrownProcessException(awaitForComplete).ConfigureAwait(false);
// Second, Start the process.
PreviousTask.EnsureProcess();
// Third, If the process is failed to start, the task will be Faulted state immediately. We should throw an exception of prev task here.
var t = PreviousTask.AsTask();
if (t.IsFaulted)
{
await t.ConfigureAwait(false);
}
if (!t.IsCompleted && awaitForComplete)
{
await t.ConfigureAwait(false);
}
}
}
private async Task<ProcessOutput> AsTaskCore()
{
await ThrowIfParentTaskHasThrownProcessException(awaitForComplete:false).ConfigureAwait(false);
EnsureProcess();
if (Process is {} proc)
{
if (_stdOutPipe is null || _stdErrorPipe is null) throw new InvalidOperationException();
// If we have no stdin stream and Console's StandardInput is redirected, connects them automatically.
var connectStdInToPipe = _options.EnableAutoWireStandardInput && !_hasStandardIn && _options.Console.IsInputRedirected /*Non-Interactive*/;
if (connectStdInToPipe)
{
StandardInput.Pipe.Connect(proc.StandardInput.BaseStream);
StandardInput.Pipe.Ready();
}
ReadyPipe();
try
{
#if NET5_0_OR_GREATER
await proc.WaitForExitAsync().ConfigureAwait(false);
#else
await Task.Run(() => proc.WaitForExit()).ConfigureAwait(false);
#endif
}
finally
{
_processCancellationRegistration.Dispose();
}
_output.ExitCode = proc.ExitCode;
WriteDebugTrace($"ProcessExited: Pid={proc.Id}; ExitCode={proc.ExitCode}");
if (connectStdInToPipe)
{
StandardInput.Pipe.Disconnect(proc.StandardInput.BaseStream);
}
// Flush output streams/pipes
WriteDebugTrace($"Pipe/Sink.CompleteAsync: Pid={proc.Id}");
await _stdOutPipe.CompleteAsync().ConfigureAwait(false);
await _stdErrorPipe.CompleteAsync().ConfigureAwait(false);
await _output.Sink.CompleteAsync().ConfigureAwait(false);
WriteDebugTrace($"Pipe/Sink.CompleteAsync:Done: Pid={proc.Id}");
await ThrowIfParentTaskHasThrownProcessException(awaitForComplete: true).ConfigureAwait(false);
if (_output.ExitCode != 0)
{
var ex = new ProcessTaskException(_processName ?? "Unknown", proc.Id, this, _output);
if (_processCancellation.IsCancellationRequested)
{
throw new OperationCanceledException($"The process has reached the timeout. (Timeout: {_options.Timeout})", ex);
}
else
{
throw ex;
}
}
}
else
{
_output.ExitCode = 127;
_processCancellationRegistration.Dispose();
if (_processException != null)
{
throw new ProcessTaskException(this, _output, _processException.SourceException);
}
else
{
throw new ProcessTaskException(this, _output);
}
}
return _output;
}
[Conditional("DEBUG")]
private void WriteDebugTrace(string s)
{
if (_options.Verbosity.HasFlag(ChellVerbosity.Debug))
{
_options.Console.Out.WriteLine($"[DEBUG][{_id}] {s}");
}
}
}
}
================================================
FILE: src/Chell/ProcessTaskException.cs
================================================
using System;
namespace Chell
{
/// <summary>
/// Represents an error that occurs during process execution.
/// </summary>
public class ProcessTaskException : Exception
{
public ProcessTask ProcessTask { get; }
public ProcessOutput Output { get; }
public ProcessTaskException(string processName, int processId, ProcessTask processTask, ProcessOutput output, Exception? innerException = default)
: base($"Process '{processName}' ({processId}) has exited with exit code {output.ExitCode}. (Executed command: {processTask.Command} {processTask.Arguments})", innerException)
{
ProcessTask = processTask;
Output = output;
}
public ProcessTaskException(ProcessTask processTask, ProcessOutput output, Exception? innerException = default)
: base($"Failed to start the process. (Executed command: {processTask.Command} {processTask.Arguments})", innerException)
{
ProcessTask = processTask;
Output = output;
}
}
}
================================================
FILE: src/Chell/ProcessTaskOptions.cs
================================================
using System;
using Chell.IO;
using Chell.Shell;
namespace Chell
{
public class ProcessTaskOptions
{
/// <summary>
/// Gets or sets whether to enable automatic wiring of standard input to the process. The default value is <value>true</value>.
/// </summary>
public bool EnableAutoWireStandardInput { get; set; }
/// <summary>
/// Gets or sets to enable standard input redirection. The default value is <value>false</value>.
/// </summary>
public bool RedirectStandardInput { get; set; }
/// <summary>
/// Gets or sets the shell executor. The default value is <c>ChellEnvironment.Current.Shell.Executor</c>.
/// </summary>
public IShellExecutor ShellExecutor { get; set; }
/// <summary>
/// Gets or sets the console provider. The default value is <c>ChellEnvironment.Current.Console</c>.
/// </summary>
public IConsoleProvider Console { get; set; }
/// <summary>
/// Gets or sets the verbosity. The default value is <c>ChellEnvironment.Current.Verbosity</c>.
/// </summary>
public ChellVerbosity Verbosity { get; set; }
/// <summary>
/// Gets or sets the working directory for the process.
/// </summary>
public string? WorkingDirectory { get; set; }
/// <summary>
/// Gets or sets the duration to timeout the process. The default value is <c>ChellEnvironment.Current.ProcessTimeout</c>.
/// </summary>
/// <remarks>
/// If the value is <see cref="TimeSpan.Zero"/> or <see cref="TimeSpan.MaxValue"/>, the process will not be timed out.
/// </remarks>
public TimeSpan Timeout { get; set; }
public ProcessTaskOptions(
bool? redirectStandardInput = default,
bool? enableAutoWireStandardInput = default,
ChellVerbosity? verbosity = default,
IShellExecutor? shellExecutor = default,
IConsoleProvider? console = default,
string? workingDirectory = default,
TimeSpan? timeout = default
)
{
RedirectStandardInput = redirectStandardInput ?? false;
EnableAutoWireStandardInput = enableAutoWireStandardInput ?? true;
ShellExecutor = shellExecutor ?? ChellEnvironment.Current.Shell.Executor;
Console = console ?? ChellEnvironment.Current.Console;
Verbosity = verbosity ?? ChellEnvironment.Current.Verbosity;
WorkingDirectory = workingDirectory ?? workingDirectory;
Timeout = timeout ?? ChellEnvironment.Current.ProcessTimeout;
}
private ProcessTaskOptions(ProcessTaskOptions orig)
{
RedirectStandardInput = orig.RedirectStandardInput;
EnableAutoWireStandardInput = orig.EnableAutoWireStandardInput;
ShellExecutor = orig.ShellExecutor;
Console = orig.Console;
Verbosity = orig.Verbosity;
WorkingDirectory = orig.WorkingDirectory;
Timeout = orig.Timeout;
}
public ProcessTaskOptions WithRedirectStandardInput(bool redirectStandardInput)
=> new ProcessTaskOptions(this) { RedirectStandardInput = redirectStandardInput };
public ProcessTaskOptions WithEnableAutoWireStandardInput(bool enableAutoWireStandardInput)
=> new ProcessTaskOptions(this) { EnableAutoWireStandardInput = enableAutoWireStandardInput };
public ProcessTaskOptions WithShellExecutor(IShellExecutor shellExecutor)
=> new ProcessTaskOptions(this) { ShellExecutor = shellExecutor };
public ProcessTaskOptions WithVerbosity(ChellVerbosity verbosity)
=> new ProcessTaskOptions(this) { Verbosity = verbosity };
public ProcessTaskOptions WithWorkingDirectory(string? workingDirectory)
=> new ProcessTaskOptions(this) { WorkingDirectory = workingDirectory };
public ProcessTaskOptions WithTimeout(TimeSpan timeout)
=> new ProcessTaskOptions(this) { Timeout = timeout };
}
}
================================================
FILE: src/Chell/Run.cs
================================================
using System;
using System.IO;
using Chell.Shell;
namespace Chell
{
/// <summary>
/// Short cut for <see cref="ProcessTask"/> to launch a process from a <see cref="string"/> or <see cref="FormattableString"/>.
/// </summary>
public class Run : ProcessTask
{
public static implicit operator Run(FormattableString commandLine)
=> new Run(commandLine);
public static implicit operator Run(CommandLineString commandLine)
=> new Run(commandLine);
/// <summary>
/// Launches a process from a <see cref="string"/>.
/// </summary>
/// <param name="commandLine"></param>
public Run(CommandLineString commandLine) : base(commandLine) { }
/// <summary>
/// Launches a process from a <see cref="FormattableString"/>.
/// </summary>
/// <remarks>
/// The interpolated string will be escaped and the array will be expanded.
/// </remarks>
public Run(FormattableString commandLine) : base(commandLine) { }
/// <summary>
/// Launches a process from a <see cref="string"/> and connects the specified <see cref="Stream"/> to the standard input.
/// </summary>
public Run(Stream inputStream, CommandLineString commandLine) : base(inputStream, commandLine) { }
/// <summary>
/// Launches a process from a <see cref="FormattableString"/> and connects the specified <see cref="Stream"/> to the standard input.
/// </summary>
/// <remarks>
/// The interpolated string will be escaped and the array will be expanded.
/// </remarks>
/// <param name="inputStream"></param>
/// <param name="commandLine"></param>
public Run(Stream inputStream, FormattableString commandLine) : base(inputStream, commandLine) { }
/// <summary>
/// Launches a process from a <see cref="string"/> and writes the specified binary data to the standard input.
/// </summary>
/// <param name="inputData"></param>
/// <param name="commandLine"></param>
public Run(ReadOnlyMemory<byte> inputData, CommandLineString commandLine) : base(new MemoryStream(inputData.ToArray()), commandLine) { }
/// <summary>
/// Launches a process from a <see cref="FormattableString"/> and writes the specified binary data to the standard input.
/// </summary>
/// <remarks>
/// The interpolated string will be escaped and the array will be expanded.
/// </remarks>
/// <param name="inputData"></param>
/// <param name="commandLine"></param>
public Run(ReadOnlyMemory<byte> inputData, FormattableString commandLine) : base(new MemoryStream(inputData.ToArray()), commandLine) { }
}
}
================================================
FILE: src/Chell/Shell/BashShellExecutor.cs
================================================
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Chell.Internal;
namespace Chell.Shell
{
public class BashShellExecutor : IShellExecutor
{
public static string? AutoDetectedPath { get; set; }
public string? Path { get; set; } = AutoDetectedPath;
public Encoding Encoding => Encoding.UTF8;
public string Prefix { get; set; }
public (string Command, string Arguments) GetCommandAndArguments(string commandLine)
=> (Path ?? throw new FileNotFoundException("Bash is not found in the PATH."), $"-c \"{Prefix}{commandLine}\"");
// https://unix.stackexchange.com/questions/187651/how-to-echo-single-quote-when-using-single-quote-to-wrap-special-characters-in
public string Escape(string value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
if (Regex.IsMatch(value, "^[a-zA-Z0-9_.-/]+$"))
{
return value;
}
return $"$'{value.Replace("\\", "\\\\").Replace("'", "\\'").Replace("\"", "\\\"")}'";
}
public BashShellExecutor(string? prefix = null)
{
Prefix = prefix ?? "set -euo pipefail;";
}
static BashShellExecutor()
{
if (Which.TryGetPath("bash", out var bashPath))
{
AutoDetectedPath = bashPath;
}
else
{
AutoDetectedPath = null;
}
}
}
}
================================================
FILE: src/Chell/Shell/CmdShellExecutor.cs
================================================
using System.Text;
using System.Text.RegularExpressions;
namespace Chell.Shell
{
public class CmdShellExecutor : IShellExecutor
{
public Encoding Encoding => Encoding.UTF8;
public (string Command, string Arguments) GetCommandAndArguments(string commandLine)
=> ("cmd", $"/c \"{commandLine}\"");
public string Escape(string value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
if (Regex.IsMatch(value, "^[a-zA-Z0-9_.-/\\\\]+$"))
{
return value;
}
value = Regex.Replace(value, "([<>|&^])", "^$1");
value = Regex.Replace(value, "(\\\\)?\"", x => x.Groups[1].Success ? "\\\\\\\"" : "\\\"");
return $"\"{value}\"";
}
}
}
================================================
FILE: src/Chell/Shell/IShellExecutor.cs
================================================
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Chell.Shell
{
public interface IShellExecutor
{
Encoding Encoding { get; }
(string Command, string Arguments) GetCommandAndArguments(string commandLine);
string Escape(string value);
}
}
================================================
FILE: src/Chell/Shell/NoUseShellExecutor.cs
================================================
using System.Text;
using System.Text.RegularExpressions;
using Chell.Internal;
namespace Chell.Shell
{
public class NoUseShellExecutor : IShellExecutor
{
public Encoding Encoding => Encoding.UTF8;
public (string Command, string Arguments) GetCommandAndArguments(string commandLine)
{
return CommandLineHelper.Parse(commandLine);
}
public string Escape(string value)
=> Regex.IsMatch(value, "^[a-zA-Z0-9_.-/]+$")
? value
: $"\"{value.Replace("`", "``").Replace("\"", "`\"")}\"";
}
}
================================================
FILE: src/Chell/Shell/ShellExecutorProvider.cs
================================================
using System;
using System.Runtime.InteropServices;
using Chell.Shell;
namespace Chell.Shell
{
public class ShellExecutorProvider
{
public IShellExecutor Executor { get; private set; } = GetPlatformPreferredExecutor();
public void SetExecutor(IShellExecutor shellExecutor)
{
Executor = shellExecutor ?? throw new ArgumentNullException(nameof(shellExecutor));
}
internal static IShellExecutor GetPlatformPreferredExecutor()
=> RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? new CmdShellExecutor()
: BashShellExecutor.AutoDetectedPath != null
? new BashShellExecutor()
: new NoUseShellExecutor();
}
}
namespace Chell
{
public static class ShellExecutorProviderExtensions
{
public static void NoUseShell(this ShellExecutorProvider provider)
{
provider.SetExecutor(new NoUseShellExecutor());
}
public static void UseBash(this ShellExecutorProvider provider, string? prefix = null)
{
provider.SetExecutor(new BashShellExecutor(prefix));
}
public static void UseCmd(this ShellExecutorProvider provider)
{
provider.SetExecutor(new CmdShellExecutor());
}
public static void UseDefault(this ShellExecutorProvider provider)
{
provider.SetExecutor(ShellExecutorProvider.GetPlatformPreferredExecutor());
}
}
}
================================================
FILE: src/Chell.Run/Chell.Run.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp3.1;net5.0</TargetFrameworks>
<Description>Tool to run C# code like a script. Provides a shell script-like (bash, cmd, ...) experience to .NET application.</Description>
<PackAsTool>true</PackAsTool>
<ToolCommandName>chell</ToolCommandName>
<!--
<PublishReadyToRun>true</PublishReadyToRun>
<TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
-->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Cocona.Lite" Version="1.6.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.11.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.11.0" />
<PackageReference Include="Mono.Options" Version="6.12.0.148" />
<PackageReference Include="Sharprompt" Version="2.3.3" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework) == 'netcoreapp3.1'">
<PackageReference Include="System.Text.Json" Version="5.0.0" />
<PackageReference Include="System.Text.Encodings.Web" Version="5.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Chell\Chell.csproj" />
</ItemGroup>
</Project>
================================================
FILE: src/Chell.Run/Program.cs
================================================
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Cocona;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
namespace Chell.Run
{
partial class Program
{
static Task Main(string[] args)
=> CoconaLiteApp.RunAsync<Program>(args);
public class RunCommandParameterSet : ICommandParameterSet
{
[Option("ref", new[] { 'r' }, Description = "Additional reference assembly")]
[HasDefaultValue]
public string[]? References { get; set; } = null;
[Option("using", new[] { 'u' }, Description = "Additional `using` namespace")]
[HasDefaultValue]
public string[]? Usings { get; set; } = null;
[Option('q')]
[HasDefaultValue]
public bool Silent { get; set; } = false;
}
[IgnoreUnknownOptions]
[Command(Description = "Chell.Run: Run C# script instantly.")]
public async Task RunAsync(
RunCommandParameterSet runParams,
[Option('e', Description = "A one-line program that can be run instantly.")] string? eval = default,
[Argument(Description = "The path to a script file, or arguments to pass to the script")] string[]? filenameOrArgs = default
)
{
var fileName = filenameOrArgs is {Length: > 0} ? filenameOrArgs[0] : null;
// -e ".." or --eval "..."
if (!string.IsNullOrEmpty(eval))
{
var args = Environment.GetCommandLineArgs();
var index = Array.FindIndex(args, x => x == "-e" || x == "--eval");
args = args.Skip(index + 2).ToArray();
await RunScriptAsync("<Inline>", Environment.CurrentDirectory, eval, args, runParams);
}
// Read a script from stdin.
else if (fileName == "-" || (string.IsNullOrWhiteSpace(fileName) && Console.IsInputRedirected))
{
// Pass the strings as arguments after '-'.
var args = Array.Empty<string>();
if (fileName == "-")
{
args = Environment.GetCommandLineArgs();
var index = Array.IndexOf(args, "-");
args = args.Skip(index + 1).ToArray();
}
using var reader = new StreamReader(Console.OpenStandardInput());
var code = await reader.ReadToEndAsync();
await RunScriptAsync("<StdIn>", Environment.CurrentDirectory, code, args, runParams);
}
else
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new CommandExitedException("Error: Specify the path or pass the script from standard input.", -1);
}
if (!File.Exists(fileName))
{
throw new CommandExitedException("Error: No such file or directory.", -1);
}
var ext = Path.GetExtension(fileName);
if (ext == ".cs")
{
// Run .cs script file.
var fullPath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, fileName));
var args = filenameOrArgs?.Skip(1).ToArray() ?? Array.Empty<string>();
await RunScriptAsync(fullPath, Path.GetDirectoryName(fullPath) ?? Environment.CurrentDirectory, await File.ReadAllTextAsync(fileName, Encoding.UTF8), args, runParams);
}
else
{
throw new CommandExitedException("Error: The specified file has unknown extension. Chell accepts a filename with `.cs` extension.", -1);
}
}
}
private async Task RunScriptAsync(string fileName, string executableDirectory, string content, string[] args, RunCommandParameterSet runParams)
{
_ = typeof(System.Text.Json.JsonSerializer).Assembly;
_ = typeof(Chell.ChellEnvironment).Assembly;
_ = typeof(Cocona.CoconaLiteApp).Assembly;
_ = typeof(Sharprompt.Prompt).Assembly;
_ = typeof(Mono.Options.Command).Assembly;
var references = AppDomain.CurrentDomain.GetAssemblies()
.Distinct()
.GroupBy(x => x)
.Select(x => x.Last())
.Select(x => MetadataReference.CreateFromFile(x.Location));
var usings = new[]
{
"System",
"System.Collections",
"System.Collections.Generic",
"System.Diagnostics",
"System.IO",
"System.Text",
"System.Text.RegularExpressions",
"System.Linq",
"System.Threading",
"System.Threading.Tasks",
"Chell",
"Chell.Extensions",
// using static
"Chell.Exports"
}.AsEnumerable();
var scriptOptions = ScriptOptions.Default
.AddImports(usings.Concat(runParams.Usings ?? Array.Empty<string>()))
.AddReferences(references)
.AddReferences(runParams.References ?? Array.Empty<string>());
try
{
if (runParams.Silent)
{
ChellEnvironment.Current.Verbosity = ChellVerbosity.Silent;
}
ChellEnvironment.Current.SetCommandLineArgs(fileName, Path.GetFileName(fileName), executableDirectory, args);
var script = await CSharpScript.RunAsync(content, scriptOptions);
}
catch (CompilationErrorException e)
{
Console.Error.WriteLine($"{fileName}{e.Message}");
}
catch (ProcessTaskException e)
{
Console.Error.WriteLine(e.Message);
}
}
}
}
================================================
FILE: tests/Chell.Tests/Chell.Tests.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Chell\Chell.csproj" />
</ItemGroup>
</Project>
================================================
FILE: tests/Chell.Tests/ChellEnvironmentTest.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;
namespace Chell.Tests
{
public class ChellEnvironmentTest
{
[Fact]
public void HomeDirectory()
{
var env = new ChellEnvironment();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
env.HomeDirectory.Should().Be(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
}
else
{
env.HomeDirectory.Should().Be(Environment.GetEnvironmentVariable("HOME"));
}
}
}
}
================================================
FILE: tests/Chell.Tests/CommandLineStringTest.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using Xunit;
namespace Chell.Tests
{
public class CommandLineStringTest
{
[Fact]
public void StaticMethodPreferString()
{
StaticMethodOverloadedTest.A("Foo {0}").Should().Be($"{nameof(CommandLineString)}: StringValue=Foo {{0}}; FormattableStringValue=");
}
[Fact]
public void StaticMethodPreferFormattableString()
{
StaticMethodOverloadedTest.A($"Foo {0}").Should().Be($"{nameof(FormattableString)}: Foo {{0}}; 1");
}
private static class StaticMethodOverloadedTest
{
public static string A(CommandLineString s) => $"{nameof(CommandLineString)}: StringValue={s.StringValue}; FormattableStringValue={s.FormattableStringValue}";
public static string A(FormattableString s) => $"{nameof(FormattableString)}: {s.Format}; {s.ArgumentCount}";
}
[Fact]
public void InstanceMethodPreferString()
{
new InstanceMethodOverloadedTest().A("Foo {0}").Should().Be($"{nameof(CommandLineString)}: StringValue=Foo {{0}}; FormattableStringValue=");
}
[Fact]
public void InstanceMethodPreferFormattableString()
{
new InstanceMethodOverloadedTest().A($"Foo {0}").Should().Be($"{nameof(FormattableString)}: Foo {{0}}; 1");
}
private class InstanceMethodOverloadedTest
{
public string A(CommandLineString s) => $"{nameof(CommandLineString)}: StringValue={s.StringValue}; FormattableStringValue={s.FormattableStringValue}";
public string A(FormattableString s) => $"{nameof(FormattableString)}: {s.Format}; {s.ArgumentCount}";
}
[Fact]
public void ImplicitCastConstructorPreferString()
{
new ConstructorOverloadedTest("Foo {0}").Result.Should().Be($"{nameof(CommandLineString)}: StringValue=Foo {{0}}; FormattableStringValue=");
}
[Fact]
public void ImplicitCastConstructorPreferFormattableString()
{
new ConstructorOverloadedTest($"Foo {0}").Result.Should().Be($"{nameof(FormattableString)}: Foo {{0}}; 1");
}
private class ConstructorOverloadedTest
{
public string Result { get; }
public ConstructorOverloadedTest(CommandLineString s)
{
Result = $"{nameof(CommandLineString)}: StringValue={s.StringValue}; FormattableStringValue={s.FormattableStringValue}";
}
public ConstructorOverloadedTest(FormattableString s)
{
Result = $"{nameof(FormattableString)}: {s.Format}; {s.ArgumentCount}";
}
}
}
}
================================================
FILE: tests/Chell.Tests/ProcessTaskTest.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Chell.IO;
using Chell.Shell;
using FluentAssertions;
using Kokuban;
using Xunit;
namespace Chell.Tests
{
internal static class ProcessTaskTestFixtureExtensions
{
public static TemporaryAppBuilder.Compilation AddTo(this TemporaryAppBuilder.Compilation compilation,
IList<IDisposable> disposables)
{
disposables.Add(compilation);
return compilation;
}
}
public class ProcessTaskTestFixture : IDisposable
{
private readonly TemporaryAppSolutionBuilder _slnBuilder = new TemporaryAppSolutionBuilder();
public string EchoArg { get; }
public string EchoOutAndErrorArgs { get; }
public string HelloWorld { get; }
public string ExitCodeNonZero { get; }
public string ExitCodeNonZeroWaited { get; }
public string WriteCommandLineArgs { get; }
public string StandardInputPassThroughText { get; }
public string StandardInputPassThroughBinary { get; }
public string WriteSleepWriteExit { get; }
public string ReadOnce { get; }
public string ReadAllLines { get; }
public string WriteCurrentDirectory { get; }
public string Never { get; }
public ProcessTaskTestFixture()
{
EchoArg = _slnBuilder.CreateProject(nameof(EchoArg), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
Console.WriteLine(""["" + Environment.GetCommandLineArgs()[1] + ""]"");
"));
EchoOutAndErrorArgs = _slnBuilder.CreateProject(nameof(EchoOutAndErrorArgs), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
using System.Threading.Tasks;
Console.Out.WriteLine(""["" + Environment.GetCommandLineArgs()[1] + ""]"");
await Task.Delay(100);
Console.Error.WriteLine(""["" + Environment.GetCommandLineArgs()[2] + ""]"");
await Task.Delay(100);
Console.Out.WriteLine(""["" + Environment.GetCommandLineArgs()[3] + ""]"");
await Task.Delay(100);
Console.Error.WriteLine(""["" + Environment.GetCommandLineArgs()[4] + ""]"");
"));
HelloWorld = _slnBuilder.CreateProject(nameof(HelloWorld), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
Console.WriteLine(""Hello World!"");
"));
ExitCodeNonZero = _slnBuilder.CreateProject(nameof(ExitCodeNonZero), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
Environment.ExitCode = 192;
"));
ExitCodeNonZeroWaited = _slnBuilder.CreateProject(nameof(ExitCodeNonZeroWaited), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
using System.Threading.Tasks;
await Task.Delay(100);
Environment.ExitCode = 192;
"));
WriteCommandLineArgs = _slnBuilder.CreateProject(nameof(WriteCommandLineArgs), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
foreach (var line in Environment.GetCommandLineArgs())
{
Console.WriteLine(line);
}
"));
StandardInputPassThroughText = _slnBuilder.CreateProject(nameof(StandardInputPassThroughText), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
Console.InputEncoding = System.Text.Encoding.UTF8;
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.Write(Console.In.ReadToEnd());
"));
StandardInputPassThroughBinary = _slnBuilder.CreateProject(nameof(StandardInputPassThroughBinary), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
Console.OpenStandardInput().CopyTo(Console.OpenStandardOutput());
"));
WriteSleepWriteExit = _slnBuilder.CreateProject(nameof(WriteSleepWriteExit), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
using System.Threading;
Console.WriteLine(""Hello"");
Thread.Sleep(1000);
Console.WriteLine(""Hello"");
"));
ReadOnce = _slnBuilder.CreateProject(nameof(ReadOnce), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
Console.WriteLine(Console.ReadLine());
"));
ReadAllLines = _slnBuilder.CreateProject(nameof(ReadAllLines), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
while (true)
{
var line = Console.ReadLine();
if (line == null) return;
Console.WriteLine(line);
}
"));
WriteCurrentDirectory = _slnBuilder.CreateProject(nameof(WriteCurrentDirectory), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
Console.WriteLine(Environment.CurrentDirectory);
"));
Never = _slnBuilder.CreateProject(nameof(Never), builder =>
builder.WriteSourceFile("Program.cs", @"
using System;
using System.Threading;
Console.WriteLine(""Hello"");
while (true) { Thread.Sleep(1000); }
"));
_slnBuilder.Build();
}
public void Dispose()
{
_slnBuilder.Dispose();
}
}
[Collection("ProcessTaskTest")] // NOTE: Test cases use `Console` and does not run in parallel.
public class ProcessTaskTest : IClassFixture<ProcessTaskTestFixture>
{
private readonly ProcessTaskTestFixture _fixture;
public ProcessTaskTest(ProcessTaskTestFixture fixture)
{
_fixture = fixture;
}
private async Task<(string StandardOut, string StandardError)> RunAsync(Func<IConsoleProvider, Task> func)
{
var fakeConsole = new FakeConsoleProvider();
await func(fakeConsole);
return (fakeConsole.GetStandardOutputAsString(), fakeConsole.GetStandardErrorAsString());
}
[Fact]
public async Task CommandNotFound_UseShell()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var dummyCommandName = Guid.NewGuid().ToString();
var procTask = new ProcessTask($"{dummyCommandName} --help");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
(await procTask.ExitCode).Should().Be(1); // A shell (cmd) will return 1.
}
else
{
(await procTask.ExitCode).Should().Be(127); // A shell (bash) will return 127.
}
}
[Fact]
public async Task CommandNotFound_NoUseShell()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var dummyCommandName = Guid.NewGuid().ToString();
var procTask = new ProcessTask($"{dummyCommandName} --help", new ProcessTaskOptions(shellExecutor: new NoUseShellExecutor()));
(await procTask.ExitCode).Should().Be(127); // System.Diagnostics.Process will return 127.
}
[Fact]
public async Task Execute()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var procTask = new ProcessTask($"{_fixture.HelloWorld}");
(await procTask.ExitCode).Should().Be(0);
var result = await procTask;
result.ExitCode.Should().Be(0);
result.Combined.Should().Be("Hello World!" + Environment.NewLine);
result.Output.Should().Be("Hello World!" + Environment.NewLine);
result.Error.Should().BeEmpty();
}
[Fact]
public async Task ProcessOutputInArgumentShouldBeTrimmed()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var procTask1 = new ProcessTask($"{_fixture.HelloWorld}");
var result1 = await procTask1;
var procTask2 = new ProcessTask($"{_fixture.EchoArg} {result1}");
var result2 = await procTask2;
result1.Combined.Should().Be("Hello World!" + Environment.NewLine);
result2.Combined.Should().Be("[Hello World!]" + Environment.NewLine);
}
[Fact]
public async Task ExpandArguments()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var args = new[] { "Alice", "Karen", "Program Files", @"C:\Program Files (x86)\Microsoft Visual Studio" };
var procTask = new ProcessTask($"{_fixture.WriteCommandLineArgs} {args}");
var result = await procTask;
// NOTE: We need skip the first line which is path of the command.
result.AsLines(trimEnd: true).Skip(1).Should().BeEquivalentTo(args);
}
[Fact]
public async Task ExpandArguments_Escape()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var args = new[] { "Alice", "Karen", "Program Files", @"C:\Program Files (x86)\Microsoft Visual Studio", "\"\\'|<>" };
var procTask = new ProcessTask($"{_fixture.WriteCommandLineArgs} {args}");
var result = await procTask;
// NOTE: We need skip the first line which is path of the command.
result.AsLines(trimEnd: true).Skip(1).Should().BeEquivalentTo(args);
}
[Fact]
public async Task ExitCode()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var procTask = new ProcessTask($"{_fixture.ExitCodeNonZero}");
(await procTask.ExitCode).Should().Be(192);
}
[Fact]
public async Task ExitCode_ThrowIfNonZero()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var procTask = new ProcessTask($"{_fixture.ExitCodeNonZero}");
await Assert.ThrowsAsync<ProcessTaskException>(async () => await procTask);
}
[Fact]
public async Task ProcessOutput_StandardInputPassThroughText()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var memStream = new MemoryStream(Encoding.UTF8.GetBytes("Hello, コンニチハ!\nABCDEFG"));
var procTask = new ProcessTask(memStream, $"{_fixture.StandardInputPassThroughText}");
var result = await procTask;
result.Output.TrimEnd().Should().Be("Hello, コンニチハ!\nABCDEFG");
}
[Fact]
public async Task ProcessOutput_StandardInputOutputCombined()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var procTask = new ProcessTask($"{_fixture.EchoOutAndErrorArgs} Arg0 Arg1 Arg2 Arg3");
var result = await procTask;
result.Output.TrimEnd().Should().Be(string.Join(Environment.NewLine, "[Arg0]", "[Arg2]"));
result.Error.TrimEnd().Should().Be(string.Join(Environment.NewLine, "[Arg1]", "[Arg3]"));
result.Combined.TrimEnd().Should().Be(string.Join(Environment.NewLine, "[Arg0]", "[Arg1]", "[Arg2]", "[Arg3]"));
}
[Fact]
public async Task ProcessOutput_StandardInputPassThroughBinary()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var memStream = new MemoryStream(Encoding.Unicode.GetBytes("Hello, コンニチハ!\nABCDEFG"));
var procTask = new ProcessTask(memStream, $"{_fixture.StandardInputPassThroughBinary}");
var result = await procTask;
result.OutputBinary.ToArray().Should().BeEquivalentTo(memStream.ToArray());
}
[Fact]
public async Task Pipe_StandardInputPassThroughBinary()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var data = Encoding.Unicode.GetBytes("Hello, コンニチハ!\nABCDEFG");
var memStream = new MemoryStream(data);
var procTask = new ProcessTask(memStream, $"{_fixture.StandardInputPassThroughBinary}");
var destStream = new MemoryStream();
var result = await procTask.Pipe(destStream);
result.ExitCode.Should().Be(0);
result.OutputBinary.Length.Should().Be(0);
destStream.ToArray().Should().BeEquivalentTo(data);
}
[Fact]
public async Task Pipe_CloseDestFirst()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var srcTask = new ProcessTask($"{_fixture.WriteSleepWriteExit}");
var destTask = new ProcessTask($"{_fixture.ReadOnce}");
await (srcTask | destTask);
}
[Fact]
public async Task Pipe_CloseSrcFirst()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var srcTask = new ProcessTask($"{_fixture.HelloWorld}");
var destTask = new ProcessTask($"{_fixture.ReadAllLines}");
await (srcTask | destTask);
}
[Fact]
public async Task Pipe_ExitCode_NonZero()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var srcTask = new ProcessTask($"{_fixture.ExitCodeNonZero}");
var destTask = new ProcessTask($"{_fixture.ReadAllLines}");
await Assert.ThrowsAsync<ProcessTaskException>(async () => await (srcTask | destTask));
}
[Fact]
public async Task Pipe_ExitCode_NonZero_ExitTailFirst()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var srcTask = new ProcessTask($"{_fixture.ExitCodeNonZeroWaited}");
var destTask = new ProcessTask($"{_fixture.ReadAllLines}");
await Assert.ThrowsAsync<ProcessTaskException>(async () => await (srcTask | destTask));
}
[Fact]
public async Task Pipe_ExitCode_NonZero_NoThrow()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var srcTask = new ProcessTask($"{_fixture.ExitCodeNonZero}");
var destTask = new ProcessTask($"{_fixture.ReadAllLines}");
await (srcTask.NoThrow() | destTask);
}
[Fact]
public async Task WorkingDirectory()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
{
var currentDirectory = Environment.CurrentDirectory;
var output = await new ProcessTask($"{_fixture.WriteCurrentDirectory}");
output.ToString().Trim().Should().Be(currentDirectory);
}
{
var currentDirectory = Environment.CurrentDirectory;
var workingDirectory = Path.GetFullPath(Path.Combine(currentDirectory, ".."));
var output = await new ProcessTask($"{_fixture.WriteCurrentDirectory}", new ProcessTaskOptions().WithWorkingDirectory(workingDirectory));
output.ToString().Trim().Should().Be(workingDirectory);
}
}
[Fact]
public async Task ProcessTimeout()
{
Func<Task> execute = async () =>
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
await new ProcessTask($"{_fixture.WriteSleepWriteExit}",
new ProcessTaskOptions().WithTimeout(TimeSpan.FromMilliseconds(300)));
});
};
await execute.Should().CompleteWithinAsync(TimeSpan.FromSeconds(2));
}
[Fact]
public async Task ProcessTimeout_Never()
{
Func<Task> execute = async () =>
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
await new ProcessTask($"{_fixture.Never}",
new ProcessTaskOptions().WithTimeout(TimeSpan.FromMilliseconds(300)));
});
};
await execute.Should().CompleteWithinAsync(TimeSpan.FromSeconds(2));
}
[Fact]
public async Task Verbosity_Silent()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var (stdOut, stdErr) = await RunAsync(async (console) =>
{
await new ProcessTask($"{_fixture.HelloWorld}", new ProcessTaskOptions(console: console).WithVerbosity(ChellVerbosity.Silent));
});
stdOut.Should().BeEmpty();
}
[Fact]
public async Task Verbosity_CommandLine()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var (stdOut, stdErr) = await RunAsync(async (console) =>
{
await new ProcessTask($"{_fixture.HelloWorld}", new ProcessTaskOptions(console: console).WithVerbosity(ChellVerbosity.CommandLine));
});
stdOut.Should().StartWith("$ ");
stdOut.Should().NotContain("Hello World!");
}
[Fact]
public async Task Verbosity_ConsoleOutputs()
{
using var fakeConsoleScope = new FakeConsoleProviderScope();
var (stdOut, stdErr) = await RunAsync(async (console) =>
{
await new ProcessTask($"{_fixture.HelloWorld}", new ProcessTaskOptions(console: console).WithVerbosity(ChellVerbosity.ConsoleOutputs));
});
stdOut.Should().Be("Hello World!" + Environment.NewLine);
}
private class FakeConsoleProviderScope : IDisposable
{
private readonly KokubanColorMode _origKokubanColorMode;
private readonly IConsoleProvider _origConsoleProvider;
private readonly FakeConsoleProvider _fakeConsoleProvider;
public string StdOut => _fakeConsoleProvider.GetStandardOutputAsString();
public string StdErr => _fakeConsoleProvider.GetStandardErrorAsString();
public FakeConsoleProviderScope()
{
_origKokubanColorMode = KokubanOptions.Default.Mode;
_origConsoleProvider = ChellEnvironment.Current.Console;
_fakeConsoleProvider = new FakeConsoleProvider();
KokubanOptions.Default.Mode = KokubanColorMode.None;
ChellEnvironment.Current.Cons
gitextract_m746u3r_/
├── .editorconfig
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── build.yaml
│ └── release.yaml
├── .gitignore
├── Chell.sln
├── Directory.Build.props
├── LICENSE
├── README.md
├── samples/
│ ├── GettingStarted.Basic.Unix/
│ │ ├── GettingStarted.Basic.Unix.csproj
│ │ └── Program.cs
│ └── GettingStarted.Basic.Windows/
│ ├── GettingStarted.Basic.Windows.csproj
│ └── Program.cs
├── src/
│ ├── .editorconfig
│ ├── Chell/
│ │ ├── Chell.csproj
│ │ ├── ChellEnvironment.cs
│ │ ├── CommandLineString.cs
│ │ ├── Exports.cs
│ │ ├── Extensions/
│ │ │ ├── ChellExtensions.cs
│ │ │ ├── ProcessOutputExtensions.cs
│ │ │ ├── ProcessTaskExtensions.Generated.cs
│ │ │ ├── ProcessTaskExtensions.cs
│ │ │ ├── ProcessTaskExtensions.tt
│ │ │ └── StringExtensions.cs
│ │ ├── IO/
│ │ │ ├── ChellWrappedStream.cs
│ │ │ ├── ChellWritableStream.Generated.cs
│ │ │ ├── ChellWritableStream.tt
│ │ │ ├── IConsoleProvider.cs
│ │ │ ├── LINQPadConsoleProvider.cs
│ │ │ └── SystemConsoleProvider.cs
│ │ ├── Internal/
│ │ │ ├── CommandLineHelper.cs
│ │ │ ├── EnvironmentVariables.cs
│ │ │ ├── LINQPadHelper.cs
│ │ │ ├── ObjectDumper.cs
│ │ │ ├── OutputSink.cs
│ │ │ ├── StandardInput.cs
│ │ │ ├── StreamPipe.cs
│ │ │ └── Which.cs
│ │ ├── ProcessOutput.cs
│ │ ├── ProcessTask.cs
│ │ ├── ProcessTaskException.cs
│ │ ├── ProcessTaskOptions.cs
│ │ ├── Run.cs
│ │ └── Shell/
│ │ ├── BashShellExecutor.cs
│ │ ├── CmdShellExecutor.cs
│ │ ├── IShellExecutor.cs
│ │ ├── NoUseShellExecutor.cs
│ │ └── ShellExecutorProvider.cs
│ └── Chell.Run/
│ ├── Chell.Run.csproj
│ └── Program.cs
└── tests/
└── Chell.Tests/
├── Chell.Tests.csproj
├── ChellEnvironmentTest.cs
├── CommandLineStringTest.cs
├── ProcessTaskTest.cs
├── Shell/
│ ├── BashShellExecutorTest.cs
│ └── CmdShellExecutorTest.cs
└── TemporaryAppBuilder.cs
SYMBOL INDEX (349 symbols across 38 files)
FILE: src/Chell.Run/Program.cs
class Program (line 16) | partial class Program
method Main (line 18) | static Task Main(string[] args)
class RunCommandParameterSet (line 21) | public class RunCommandParameterSet : ICommandParameterSet
method RunAsync (line 36) | [IgnoreUnknownOptions]
method RunScriptAsync (line 97) | private async Task RunScriptAsync(string fileName, string executableDi...
FILE: src/Chell/ChellEnvironment.cs
type ChellVerbosity (line 16) | [Flags]
class ChellEnvironment (line 43) | public class ChellEnvironment
method ChellEnvironment (line 52) | public ChellEnvironment()
method SetCommandLineArgs (line 154) | [EditorBrowsable(EditorBrowsableState.Never)]
FILE: src/Chell/CommandLineString.cs
type CommandLineString (line 10) | [EditorBrowsable(EditorBrowsableState.Never)]
method CommandLineString (line 17) | public CommandLineString(string value)
method CommandLineString (line 23) | public CommandLineString(FormattableString value)
FILE: src/Chell/Exports.cs
class Exports (line 18) | public static class Exports
class Verbosity (line 20) | public static class Verbosity
method Run (line 111) | public static ProcessTask Run(FormattableString commandLine, ProcessTa...
method Run (line 120) | public static ProcessTask Run(CommandLineString commandLine, ProcessTa...
method Run (line 130) | public static ProcessTask Run(Stream inputStream, FormattableString co...
method Run (line 140) | public static ProcessTask Run(Stream inputStream, CommandLineString co...
method Run (line 150) | public static ProcessTask Run(ReadOnlyMemory<byte> inputData, Formatta...
method Run (line 160) | public static ProcessTask Run(ReadOnlyMemory<byte> inputData, CommandL...
method Echo (line 167) | public static void Echo(object? message = default)
method Dump (line 175) | public static void Dump<T>(T obj)
method ToJson (line 182) | public static string ToJson<T>(T obj, JsonSerializerOptions? options =...
method FromJson (line 189) | public static T? FromJson<T>(string json, T shape)
method FromJson (line 196) | public static T? FromJson<T>(string json)
method Cd (line 206) | public static IDisposable Cd(string path)
class ChangeDirectoryScope (line 209) | private class ChangeDirectoryScope : IDisposable
method ChangeDirectoryScope (line 213) | public ChangeDirectoryScope(string newCurrentDirectory)
method Dispose (line 219) | public void Dispose()
method ChangeDirectory (line 224) | private void ChangeDirectory(string path)
method Sleep (line 236) | public static Task Sleep(TimeSpan timeSpan)
method Sleep (line 244) | public static Task Sleep(int seconds)
method NoThrow (line 252) | public static Task<ProcessOutput> NoThrow(ProcessTask task)
method Exit (line 259) | public static void Exit(int exitCode = 0)
method Mkdirp (line 266) | public static void Mkdirp(string path)
method FetchAsync (line 275) | public static Task<HttpResponseMessage> FetchAsync(string requestUri, ...
method FetchStringAsync (line 288) | public static async Task<string> FetchStringAsync(string requestUri, C...
method FetchByteArrayAsync (line 308) | public static async Task<byte[]> FetchByteArrayAsync(string requestUri...
method FetchStreamAsync (line 328) | public static async Task<Stream> FetchStreamAsync(string requestUri, C...
method Which (line 347) | public static string Which(string commandName)
method TryWhich (line 358) | public static bool TryWhich(string commandName, out string matchedPath)
method Glob (line 369) | public static IEnumerable<string> Glob(params string[] patterns)
method Glob (line 381) | public static IEnumerable<string> Glob(string baseDir, string[] patterns)
method Prompt (line 407) | public static async Task<string?> Prompt(string message)
FILE: src/Chell/Extensions/ChellExtensions.cs
class ChellExtensions (line 11) | public static class ChellExtensions
method Dump (line 19) | public static T Dump<T>(this T value)
method Dump (line 30) | public static async Task<T> Dump<T>(this Task<T> task)
FILE: src/Chell/Extensions/ProcessOutputExtensions.cs
class ProcessOutputExtensions (line 6) | public static class ProcessOutputExtensions
method AsJsonLines (line 14) | public static IEnumerable<T?> AsJsonLines<T>(this ProcessOutput proces...
method AsJsonLines (line 20) | public static IEnumerable<T?> AsJsonLines<T>(this ProcessOutput proces...
method AsJson (line 29) | public static T? AsJson<T>(this ProcessOutput processOutput, T shape, ...
method AsJson (line 35) | public static T? AsJson<T>(this ProcessOutput processOutput, JsonSeria...
FILE: src/Chell/Extensions/ProcessTaskExtensions.Generated.cs
class ProcessTaskExtensions (line 6) | public static partial class ProcessTaskExtensions
method GetAwaiter (line 8) | public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutp...
method GetAwaiter (line 17) | public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutp...
method GetAwaiter (line 26) | public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutp...
method GetAwaiter (line 35) | public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutp...
method GetAwaiter (line 44) | public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutp...
method GetAwaiter (line 53) | public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutp...
method GetAwaiter (line 62) | public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutp...
method GetAwaiter (line 71) | public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutp...
method GetAwaiter (line 80) | public static System.Runtime.CompilerServices.TaskAwaiter<(ProcessOutp...
FILE: src/Chell/Extensions/ProcessTaskExtensions.cs
class ProcessTaskExtensions (line 5) | public static partial class ProcessTaskExtensions
FILE: src/Chell/Extensions/StringExtensions.cs
class StringExtensions (line 10) | public static class StringExtensions
method AsJsonLines (line 18) | public static IEnumerable<T?> AsJsonLines<T>(this IEnumerable<string> ...
method AsJsonLines (line 26) | public static IEnumerable<T?> AsJsonLines<T>(this IEnumerable<string> ...
method AsJson (line 37) | public static T? AsJson<T>(this string json, T shape, JsonSerializerOp...
method AsJson (line 43) | public static T? AsJson<T>(this string json, JsonSerializerOptions? op...
FILE: src/Chell/IO/ChellWrappedStream.cs
class ChellWritableStream (line 12) | public partial class ChellWritableStream : ChellWrappedStream
method ChellWritableStream (line 16) | public ChellWritableStream(Stream baseStream, Encoding encoding)
method Write (line 23) | public void Write(byte[] value) => BaseStream.Write(value);
method Write (line 24) | public new void Write(ReadOnlySpan<byte> value) => BaseStream.Write(va...
method WriteAsync (line 25) | public ValueTask WriteAsync(byte[] value, CancellationToken cancellati...
method WriteAsync (line 26) | public new ValueTask WriteAsync(ReadOnlyMemory<byte> value, Cancellati...
method Dispose (line 28) | protected override void Dispose(bool disposing)
method DisposeAsync (line 34) | public override async ValueTask DisposeAsync()
class ChellReadableStream (line 41) | public partial class ChellReadableStream : ChellWrappedStream
method ChellReadableStream (line 45) | public ChellReadableStream(Stream baseStream, Encoding encoding)
method ReadAllBytesAsync (line 51) | public async Task<byte[]> ReadAllBytesAsync(CancellationToken cancella...
method ReadAllBytes (line 66) | public byte[] ReadAllBytes()
method ReadToEndAsync (line 80) | public async Task<string> ReadToEndAsync()
method ReadToEnd (line 85) | public string ReadToEnd()
method ReadAllLines (line 90) | public IEnumerable<string> ReadAllLines()
method ReadAllLinesAsync (line 101) | public async IAsyncEnumerable<string> ReadAllLinesAsync()
method Dispose (line 112) | protected override void Dispose(bool disposing)
class ChellWrappedStream (line 119) | public abstract class ChellWrappedStream : Stream
method ChellWrappedStream (line 125) | protected ChellWrappedStream(Stream baseStream)
method Flush (line 131) | public override void Flush()
method Read (line 136) | public override int Read(byte[] buffer, int offset, int count)
method Read (line 141) | public override int Read(Span<byte> buffer)
method ReadAsync (line 146) | public override Task<int> ReadAsync(byte[] buffer, int offset, int cou...
method ReadAsync (line 151) | public override ValueTask<int> ReadAsync(Memory<byte> buffer, Cancella...
method Seek (line 156) | public override long Seek(long offset, SeekOrigin origin)
method SetLength (line 161) | public override void SetLength(long value)
method Write (line 166) | public override void Write(byte[] buffer, int offset, int count)
method Write (line 171) | public override void Write(ReadOnlySpan<byte> buffer)
method WriteAsync (line 176) | public override Task WriteAsync(byte[] buffer, int offset, int count, ...
method WriteAsync (line 181) | public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, Canc...
FILE: src/Chell/IO/ChellWritableStream.Generated.cs
class ChellWritableStream (line 9) | public partial class ChellWritableStream
method Write (line 11) | public void Write(string? value) => _writer.Write(value);
method WriteLine (line 12) | public void WriteLine(string? value) => _writer.WriteLine(value);
method WriteAsync (line 13) | public Task WriteAsync(string? value) => _writer.WriteAsync(value);
method WriteLineAsync (line 14) | public Task WriteLineAsync(string? value) => _writer.WriteLineAsync(va...
method Write (line 15) | public void Write(char value) => _writer.Write(value);
method WriteLine (line 16) | public void WriteLine(char value) => _writer.WriteLine(value);
method WriteAsync (line 17) | public Task WriteAsync(char value) => _writer.WriteAsync(value);
method WriteLineAsync (line 18) | public Task WriteLineAsync(char value) => _writer.WriteLineAsync(value);
method Write (line 19) | public void Write(char[]? value) => _writer.Write(value);
method WriteLine (line 20) | public void WriteLine(char[]? value) => _writer.WriteLine(value);
method WriteAsync (line 21) | public Task WriteAsync(char[]? value) => _writer.WriteAsync(value);
method WriteLineAsync (line 22) | public Task WriteLineAsync(char[]? value) => _writer.WriteLineAsync(va...
method Write (line 23) | public void Write(object? value) => _writer.Write(value);
method WriteLine (line 24) | public void WriteLine(object? value) => _writer.WriteLine(value);
method Write (line 25) | public void Write(double value) => _writer.Write(value);
method WriteLine (line 26) | public void WriteLine(double value) => _writer.WriteLine(value);
method Write (line 27) | public void Write(float value) => _writer.Write(value);
method WriteLine (line 28) | public void WriteLine(float value) => _writer.WriteLine(value);
method Write (line 29) | public void Write(long value) => _writer.Write(value);
method WriteLine (line 30) | public void WriteLine(long value) => _writer.WriteLine(value);
method Write (line 31) | public void Write(int value) => _writer.Write(value);
method WriteLine (line 32) | public void WriteLine(int value) => _writer.WriteLine(value);
method Write (line 33) | public void Write(ReadOnlySpan<char> value) => _writer.Write(value);
method WriteLine (line 34) | public void WriteLine(ReadOnlySpan<char> value) => _writer.WriteLine(v...
method WriteAsync (line 35) | public Task WriteAsync(ReadOnlyMemory<char> value) => _writer.WriteAsy...
method WriteLineAsync (line 36) | public Task WriteLineAsync(ReadOnlyMemory<char> value) => _writer.Writ...
FILE: src/Chell/IO/IConsoleProvider.cs
type IConsoleProvider (line 7) | public interface IConsoleProvider
method OpenStandardInput (line 9) | Stream OpenStandardInput();
method OpenStandardOutput (line 10) | Stream OpenStandardOutput();
method OpenStandardError (line 11) | Stream OpenStandardError();
FILE: src/Chell/IO/LINQPadConsoleProvider.cs
class LINQPadConsoleProvider (line 11) | public class LINQPadConsoleProvider : IConsoleProvider
method LINQPadConsoleProvider (line 24) | public LINQPadConsoleProvider()
method OpenStandardInput (line 45) | public Stream OpenStandardInput()
method OpenStandardOutput (line 48) | public Stream OpenStandardOutput()
method OpenStandardError (line 51) | public Stream OpenStandardError()
class PipeTextWriter (line 54) | private class PipeTextWriter : TextWriter
method PipeTextWriter (line 59) | public PipeTextWriter(PipeWriter writer, Encoding encoding)
method Write (line 65) | public override void Write(char value)
FILE: src/Chell/IO/SystemConsoleProvider.cs
class SystemConsoleProvider (line 10) | public sealed class SystemConsoleProvider : IConsoleProvider
method SystemConsoleProvider (line 14) | private SystemConsoleProvider()
method OpenStandardInput (line 17) | public Stream OpenStandardInput()
method OpenStandardOutput (line 20) | public Stream OpenStandardOutput()
method OpenStandardError (line 23) | public Stream OpenStandardError()
FILE: src/Chell/Internal/CommandLineHelper.cs
class CommandLineHelper (line 12) | internal class CommandLineHelper
method WriteCommandLineToConsole (line 14) | public static void WriteCommandLineToConsole(IConsoleProvider console,...
method Expand (line 35) | public static string Expand(FormattableString commandLine, IShellExecu...
method Parse (line 50) | public static (string Command, string Arguments) Parse(FormattableStri...
method Parse (line 63) | public static (string Command, string Arguments) Parse(string commandL...
method Escape (line 81) | public static string Escape(string v)
FILE: src/Chell/Internal/EnvironmentVariables.cs
class EnvironmentVariables (line 8) | internal class EnvironmentVariables : IDictionary<string, string>
method GetEnumerator (line 10) | public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
method GetEnumerator (line 18) | IEnumerator IEnumerable.GetEnumerator()
method Add (line 23) | public void Add(KeyValuePair<string, string> item)
method Clear (line 28) | public void Clear()
method Contains (line 33) | public bool Contains(KeyValuePair<string, string> item)
method CopyTo (line 38) | public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
method Remove (line 43) | public bool Remove(KeyValuePair<string, string> item)
method Add (line 57) | public void Add(string key, string value)
method ContainsKey (line 62) | public bool ContainsKey(string key)
method Remove (line 67) | public bool Remove(string key)
method TryGetValue (line 78) | public bool TryGetValue(string key, out string value)
FILE: src/Chell/Internal/LINQPadHelper.cs
class LINQPadHelper (line 13) | internal class LINQPadHelper
method WriteRawHtml (line 18) | public static void WriteRawHtml(string html)
FILE: src/Chell/Internal/ObjectDumper.cs
class ObjectDumper (line 13) | internal static class ObjectDumper
method Dump (line 15) | public static T Dump<T>(T obj)
class DumpMethodCache (line 18) | private static class DumpMethodCache
method DumpMethodCache (line 22) | static DumpMethodCache()
method DumpMethodCache (line 37) | static DumpMethodCache()
class DumpMethodCache (line 33) | private static class DumpMethodCache<T>
method DumpMethodCache (line 22) | static DumpMethodCache()
method DumpMethodCache (line 37) | static DumpMethodCache()
FILE: src/Chell/Internal/OutputSink.cs
class OutputSink (line 10) | internal class OutputSink : IDisposable, IAsyncDisposable
method OutputSink (line 32) | public OutputSink(Encoding encoding)
method CompleteAsync (line 47) | public async Task CompleteAsync()
method RunReadWriteLoopAsync (line 63) | private async Task RunReadWriteLoopAsync(PipeReader reader, Stream des...
method Dispose (line 94) | public void Dispose()
method DisposeAsync (line 103) | public async ValueTask DisposeAsync()
FILE: src/Chell/Internal/StandardInput.cs
class StandardInput (line 6) | internal class StandardInput
method StandardInput (line 11) | static StandardInput()
FILE: src/Chell/Internal/StreamPipe.cs
class StreamPipe (line 11) | internal class StreamPipe
method StreamPipe (line 24) | public StreamPipe(Stream baseStream)
method CopyStreamToPipeAsync (line 35) | private async Task CopyStreamToPipeAsync(CancellationToken cancellatio...
method CompleteAsync (line 48) | public async Task CompleteAsync()
method Ready (line 64) | public void Ready()
method Connect (line 69) | public StreamPipe Connect(Stream stream)
method Connect (line 77) | public StreamPipe Connect(PipeWriter writer)
method Disconnect (line 86) | public StreamPipe Disconnect(Stream stream)
method Disconnect (line 95) | public StreamPipe Disconnect(PipeWriter writer)
method RunReadLoopAsync (line 104) | private async Task RunReadLoopAsync(CancellationToken cancellationToken)
method WriteAsync (line 190) | private static async ValueTask WriteAsync(Stream stream, ReadResult re...
method WriteAsync (line 215) | private static async ValueTask WriteAsync(PipeWriter writer, ReadResul...
FILE: src/Chell/Internal/Which.cs
class Which (line 11) | public static class Which
method TryGetPath (line 13) | public static bool TryGetPath(string commandName, out string matchedPath)
FILE: src/Chell/ProcessOutput.cs
class ProcessOutput (line 16) | public class ProcessOutput : IEnumerable<string>
method ProcessOutput (line 55) | public ProcessOutput(Encoding encoding)
method ToString (line 66) | public override string ToString() => Combined;
method GetEnumerator (line 68) | public IEnumerator<string> GetEnumerator()
method GetEnumerator (line 71) | IEnumerator IEnumerable.GetEnumerator()
method AsLines (line 77) | public IEnumerable<string> AsLines(bool trimEnd = false)
FILE: src/Chell/ProcessTask.cs
class ProcessTask (line 14) | public class ProcessTask
method ProcessTask (line 71) | public ProcessTask(FormattableString commandLine, ProcessTaskOptions? ...
method ProcessTask (line 74) | public ProcessTask(Stream? inputStream, FormattableString commandLine,...
method ProcessTask (line 77) | public ProcessTask(ReadOnlyMemory<byte> inputData, FormattableString c...
method ProcessTask (line 82) | public ProcessTask(CommandLineString commandLine, ProcessTaskOptions? ...
method ProcessTask (line 85) | public ProcessTask(Stream? inputStream, CommandLineString commandLine,...
method ProcessTask (line 88) | public ProcessTask(ReadOnlyMemory<byte> inputData, CommandLineString c...
method ProcessTask (line 91) | private ProcessTask(Stream? inputStream, string commandLine, ProcessTa...
method ProcessTask (line 95) | public ProcessTask(string command, string arguments, ProcessTaskOption...
method ProcessTask (line 98) | public ProcessTask(Stream? inputStream, string command, string argumen...
method ProcessTask (line 101) | public ProcessTask(ReadOnlyMemory<byte> inputData, string command, str...
method ProcessTask (line 105) | private ProcessTask(Stream? inputStream, string commandLine, (string C...
method AsTask (line 182) | public async Task<ProcessOutput> AsTask()
method GetAwaiter (line 194) | public System.Runtime.CompilerServices.TaskAwaiter<ProcessOutput> GetA...
method NoThrow (line 219) | public ProcessTask NoThrow()
method ToString (line 225) | public override string ToString()
method RedirectStandardInput (line 236) | public void RedirectStandardInput(bool immediateLaunchProcess = true)
method ConnectStreamToStandardInput (line 262) | public void ConnectStreamToStandardInput(Stream stream)
method SuppressConsoleOutputs (line 277) | public ProcessTask SuppressConsoleOutputs()
method Pipe (line 288) | public ProcessTask Pipe(Stream stream)
method Pipe (line 308) | public ProcessTask Pipe(ProcessTask nextProcess)
method EnsureProcess (line 335) | private void EnsureProcess()
method StartProcess (line 346) | private void StartProcess()
method ReadyPipe (line 403) | private void ReadyPipe()
method UnbufferedCopyToAsync (line 422) | private static async Task UnbufferedCopyToAsync(Stream src, Stream des...
method ThrowIfParentTaskHasThrownProcessException (line 438) | private async Task ThrowIfParentTaskHasThrownProcessException(bool awa...
method AsTaskCore (line 462) | private async Task<ProcessOutput> AsTaskCore()
method WriteDebugTrace (line 543) | [Conditional("DEBUG")]
FILE: src/Chell/ProcessTaskException.cs
class ProcessTaskException (line 8) | public class ProcessTaskException : Exception
method ProcessTaskException (line 13) | public ProcessTaskException(string processName, int processId, Process...
method ProcessTaskException (line 20) | public ProcessTaskException(ProcessTask processTask, ProcessOutput out...
FILE: src/Chell/ProcessTaskOptions.cs
class ProcessTaskOptions (line 7) | public class ProcessTaskOptions
method ProcessTaskOptions (line 47) | public ProcessTaskOptions(
method ProcessTaskOptions (line 66) | private ProcessTaskOptions(ProcessTaskOptions orig)
method WithRedirectStandardInput (line 77) | public ProcessTaskOptions WithRedirectStandardInput(bool redirectStand...
method WithEnableAutoWireStandardInput (line 79) | public ProcessTaskOptions WithEnableAutoWireStandardInput(bool enableA...
method WithShellExecutor (line 81) | public ProcessTaskOptions WithShellExecutor(IShellExecutor shellExecutor)
method WithVerbosity (line 83) | public ProcessTaskOptions WithVerbosity(ChellVerbosity verbosity)
method WithWorkingDirectory (line 85) | public ProcessTaskOptions WithWorkingDirectory(string? workingDirectory)
method WithTimeout (line 87) | public ProcessTaskOptions WithTimeout(TimeSpan timeout)
FILE: src/Chell/Run.cs
class Run (line 10) | public class Run : ProcessTask
method Run (line 21) | public Run(CommandLineString commandLine) : base(commandLine) { }
method Run (line 29) | public Run(FormattableString commandLine) : base(commandLine) { }
method Run (line 34) | public Run(Stream inputStream, CommandLineString commandLine) : base(i...
method Run (line 44) | public Run(Stream inputStream, FormattableString commandLine) : base(i...
method Run (line 51) | public Run(ReadOnlyMemory<byte> inputData, CommandLineString commandLi...
method Run (line 61) | public Run(ReadOnlyMemory<byte> inputData, FormattableString commandLi...
FILE: src/Chell/Shell/BashShellExecutor.cs
class BashShellExecutor (line 9) | public class BashShellExecutor : IShellExecutor
method GetCommandAndArguments (line 19) | public (string Command, string Arguments) GetCommandAndArguments(strin...
method Escape (line 23) | public string Escape(string value)
method BashShellExecutor (line 36) | public BashShellExecutor(string? prefix = null)
method BashShellExecutor (line 41) | static BashShellExecutor()
FILE: src/Chell/Shell/CmdShellExecutor.cs
class CmdShellExecutor (line 6) | public class CmdShellExecutor : IShellExecutor
method GetCommandAndArguments (line 10) | public (string Command, string Arguments) GetCommandAndArguments(strin...
method Escape (line 13) | public string Escape(string value)
FILE: src/Chell/Shell/IShellExecutor.cs
type IShellExecutor (line 9) | public interface IShellExecutor
method GetCommandAndArguments (line 12) | (string Command, string Arguments) GetCommandAndArguments(string comma...
method Escape (line 13) | string Escape(string value);
FILE: src/Chell/Shell/NoUseShellExecutor.cs
class NoUseShellExecutor (line 7) | public class NoUseShellExecutor : IShellExecutor
method GetCommandAndArguments (line 11) | public (string Command, string Arguments) GetCommandAndArguments(strin...
method Escape (line 16) | public string Escape(string value)
FILE: src/Chell/Shell/ShellExecutorProvider.cs
class ShellExecutorProvider (line 7) | public class ShellExecutorProvider
method SetExecutor (line 11) | public void SetExecutor(IShellExecutor shellExecutor)
method GetPlatformPreferredExecutor (line 16) | internal static IShellExecutor GetPlatformPreferredExecutor()
class ShellExecutorProviderExtensions (line 27) | public static class ShellExecutorProviderExtensions
method NoUseShell (line 29) | public static void NoUseShell(this ShellExecutorProvider provider)
method UseBash (line 33) | public static void UseBash(this ShellExecutorProvider provider, string...
method UseCmd (line 37) | public static void UseCmd(this ShellExecutorProvider provider)
method UseDefault (line 41) | public static void UseDefault(this ShellExecutorProvider provider)
FILE: tests/Chell.Tests/ChellEnvironmentTest.cs
class ChellEnvironmentTest (line 12) | public class ChellEnvironmentTest
method HomeDirectory (line 14) | [Fact]
FILE: tests/Chell.Tests/CommandLineStringTest.cs
class CommandLineStringTest (line 11) | public class CommandLineStringTest
method StaticMethodPreferString (line 13) | [Fact]
method StaticMethodPreferFormattableString (line 18) | [Fact]
class StaticMethodOverloadedTest (line 24) | private static class StaticMethodOverloadedTest
method A (line 26) | public static string A(CommandLineString s) => $"{nameof(CommandLine...
method A (line 27) | public static string A(FormattableString s) => $"{nameof(Formattable...
method InstanceMethodPreferString (line 30) | [Fact]
method InstanceMethodPreferFormattableString (line 35) | [Fact]
class InstanceMethodOverloadedTest (line 41) | private class InstanceMethodOverloadedTest
method A (line 43) | public string A(CommandLineString s) => $"{nameof(CommandLineString)...
method A (line 44) | public string A(FormattableString s) => $"{nameof(FormattableString)...
method ImplicitCastConstructorPreferString (line 47) | [Fact]
method ImplicitCastConstructorPreferFormattableString (line 52) | [Fact]
class ConstructorOverloadedTest (line 58) | private class ConstructorOverloadedTest
method ConstructorOverloadedTest (line 62) | public ConstructorOverloadedTest(CommandLineString s)
method ConstructorOverloadedTest (line 67) | public ConstructorOverloadedTest(FormattableString s)
FILE: tests/Chell.Tests/ProcessTaskTest.cs
class ProcessTaskTestFixtureExtensions (line 18) | internal static class ProcessTaskTestFixtureExtensions
method AddTo (line 20) | public static TemporaryAppBuilder.Compilation AddTo(this TemporaryAppB...
class ProcessTaskTestFixture (line 28) | public class ProcessTaskTestFixture : IDisposable
method ProcessTaskTestFixture (line 45) | public ProcessTaskTestFixture()
method Dispose (line 139) | public void Dispose()
class ProcessTaskTest (line 145) | [Collection("ProcessTaskTest")] // NOTE: Test cases use `Console` and do...
method ProcessTaskTest (line 150) | public ProcessTaskTest(ProcessTaskTestFixture fixture)
method RunAsync (line 155) | private async Task<(string StandardOut, string StandardError)> RunAsyn...
method CommandNotFound_UseShell (line 164) | [Fact]
method CommandNotFound_NoUseShell (line 180) | [Fact]
method Execute (line 189) | [Fact]
method ProcessOutputInArgumentShouldBeTrimmed (line 203) | [Fact]
method ExpandArguments (line 216) | [Fact]
method ExpandArguments_Escape (line 228) | [Fact]
method ExitCode (line 240) | [Fact]
method ExitCode_ThrowIfNonZero (line 248) | [Fact]
method ProcessOutput_StandardInputPassThroughText (line 256) | [Fact]
method ProcessOutput_StandardInputOutputCombined (line 267) | [Fact]
method ProcessOutput_StandardInputPassThroughBinary (line 280) | [Fact]
method Pipe_StandardInputPassThroughBinary (line 291) | [Fact]
method Pipe_CloseDestFirst (line 306) | [Fact]
method Pipe_CloseSrcFirst (line 316) | [Fact]
method Pipe_ExitCode_NonZero (line 326) | [Fact]
method Pipe_ExitCode_NonZero_ExitTailFirst (line 336) | [Fact]
method Pipe_ExitCode_NonZero_NoThrow (line 346) | [Fact]
method WorkingDirectory (line 356) | [Fact]
method ProcessTimeout (line 373) | [Fact]
method ProcessTimeout_Never (line 388) | [Fact]
method Verbosity_Silent (line 403) | [Fact]
method Verbosity_CommandLine (line 415) | [Fact]
method Verbosity_ConsoleOutputs (line 428) | [Fact]
class FakeConsoleProviderScope (line 440) | private class FakeConsoleProviderScope : IDisposable
method FakeConsoleProviderScope (line 449) | public FakeConsoleProviderScope()
method Dispose (line 459) | public void Dispose()
class FakeConsoleProvider (line 468) | public class FakeConsoleProvider : IConsoleProvider, IDisposable
method FakeConsoleProvider (line 478) | public FakeConsoleProvider()
method GetStandardOutputAsString (line 487) | public string GetStandardOutputAsString() => Encoding.UTF8.GetString(_...
method GetStandardErrorAsString (line 488) | public string GetStandardErrorAsString() => Encoding.UTF8.GetString(_e...
method OpenStandardInput (line 490) | public Stream OpenStandardInput()
method OpenStandardOutput (line 493) | public Stream OpenStandardOutput()
method OpenStandardError (line 496) | public Stream OpenStandardError()
method Dispose (line 507) | public void Dispose()
FILE: tests/Chell.Tests/Shell/BashShellExecutorTest.cs
class BashShellExecutorTest (line 8) | public class BashShellExecutorTest
method Escape (line 10) | [Fact]
method GetCommandAndArguments (line 23) | [Fact]
FILE: tests/Chell.Tests/Shell/CmdShellExecutorTest.cs
class CmdShellExecutorTest (line 8) | public class CmdShellExecutorTest
method Escape (line 10) | [Fact]
method GetCommandAndArguments (line 50) | [Fact]
FILE: tests/Chell.Tests/TemporaryAppBuilder.cs
class TemporaryAppSolutionBuilder (line 11) | public class TemporaryAppSolutionBuilder : IDisposable
method TemporaryAppSolutionBuilder (line 16) | public TemporaryAppSolutionBuilder()
method CreateProject (line 22) | public string CreateProject(string projectName, Action<TemporaryAppBui...
method RunInSolutionDirectory (line 30) | public TemporaryAppSolutionBuilder RunInSolutionDirectory(string fileN...
method Build (line 54) | public void Build()
method Dispose (line 61) | public void Dispose()
class TemporaryAppBuilder (line 66) | public class TemporaryAppBuilder : IDisposable
method TemporaryAppBuilder (line 76) | private TemporaryAppBuilder(string baseSlnDirectory, string projectName)
method Create (line 84) | public static TemporaryAppBuilder Create(string baseSlnDirectory, stri...
method Initialize (line 91) | private void Initialize()
method WriteSourceFile (line 122) | public TemporaryAppBuilder WriteSourceFile(string fileName, string con...
method GetExecutablePath (line 128) | public string GetExecutablePath()
method Build (line 131) | public Compilation Build()
class Compilation (line 138) | public class Compilation : IDisposable
method Compilation (line 143) | public Compilation(TemporaryAppBuilder builder, string executablePath)
method Dispose (line 149) | public void Dispose()
method RunInSourceDirectory (line 155) | public TemporaryAppBuilder RunInSourceDirectory(string fileName, strin...
method Dispose (line 180) | public void Dispose()
Condensed preview — 57 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (216K chars).
[
{
"path": ".editorconfig",
"chars": 5614,
"preview": "# To learn more about .editorconfig see https://aka.ms/editorconfigdocs\n###############################\n# Core EditorCo"
},
{
"path": ".gitattributes",
"chars": 2536,
"preview": "###############################################################################\n# Set default behavior to automatically "
},
{
"path": ".github/workflows/build.yaml",
"chars": 952,
"preview": "name: Build-Development\n\non:\n push:\n branches:\n - main\n - master\n pull_request:\n types:\n - opened"
},
{
"path": ".github/workflows/release.yaml",
"chars": 1277,
"preview": "name: Build-Release\n\non:\n push:\n tags:\n - v*\n\njobs:\n Release:\n if: \"contains(github.ref, 'refs/tags')\"\n "
},
{
"path": ".gitignore",
"chars": 5809,
"preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## G"
},
{
"path": "Chell.sln",
"chars": 7141,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.3011"
},
{
"path": "Directory.Build.props",
"chars": 988,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbui"
},
{
"path": "LICENSE",
"chars": 1087,
"preview": "MIT License\n\nCopyright © Mayuki Sawatari <mayuki@misuzilla.org>\n\nPermission is hereby granted, free of charge, to any pe"
},
{
"path": "README.md",
"chars": 16299,
"preview": "# Chell\nWrite scripts with the power of C# and .NET.\n\nChell is a library and execution tool for providing a shell script"
},
{
"path": "samples/GettingStarted.Basic.Unix/GettingStarted.Basic.Unix.csproj",
"chars": 292,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net5.0</Targe"
},
{
"path": "samples/GettingStarted.Basic.Unix/Program.cs",
"chars": 1193,
"preview": "using System;\nusing System.Linq;\nusing static Chell.Exports;\n\n// Starts a process.\n// The array will be expanded and the"
},
{
"path": "samples/GettingStarted.Basic.Windows/GettingStarted.Basic.Windows.csproj",
"chars": 292,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net5.0</Targe"
},
{
"path": "samples/GettingStarted.Basic.Windows/Program.cs",
"chars": 1281,
"preview": "using System;\nusing System.Linq;\nusing Chell;\nusing static Chell.Exports;\n\n// Starts a process.\n// The array will be exp"
},
{
"path": "src/.editorconfig",
"chars": 1288,
"preview": "###############################\n# C# Nullability #\n###############################\n[*.cs]\n# CS8618: Non-nul"
},
{
"path": "src/Chell/Chell.csproj",
"chars": 1656,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <TargetFrameworks>netstandard2.1;netcoreapp3.1;net5.0</TargetF"
},
{
"path": "src/Chell/ChellEnvironment.cs",
"chars": 5735,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Diagnostics;\nusing System.IO;\n"
},
{
"path": "src/Chell/CommandLineString.cs",
"chars": 1117,
"preview": "using System;\nusing System.ComponentModel;\nusing System.Diagnostics;\n\nnamespace Chell\n{\n /// <summary>\n /// Worka"
},
{
"path": "src/Chell/Exports.cs",
"chars": 16740,
"preview": "using Microsoft.Extensions.FileSystemGlobbing;\nusing Microsoft.Extensions.FileSystemGlobbing.Abstractions;\nusing System;"
},
{
"path": "src/Chell/Extensions/ChellExtensions.cs",
"chars": 995,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\nusing Sy"
},
{
"path": "src/Chell/Extensions/ProcessOutputExtensions.cs",
"chars": 1741,
"preview": "using System.Collections.Generic;\nusing System.Text.Json;\n\nnamespace Chell\n{\n public static class ProcessOutputExten"
},
{
"path": "src/Chell/Extensions/ProcessTaskExtensions.Generated.cs",
"chars": 8850,
"preview": "/// <auto-generated />\nusing System.Threading.Tasks;\n\nnamespace Chell\n{\n public static partial class ProcessTaskExte"
},
{
"path": "src/Chell/Extensions/ProcessTaskExtensions.cs",
"chars": 117,
"preview": "using System.Threading.Tasks;\n\nnamespace Chell\n{\n public static partial class ProcessTaskExtensions\n {\n }\n}"
},
{
"path": "src/Chell/Extensions/ProcessTaskExtensions.tt",
"chars": 1361,
"preview": "<#@ template debug=\"false\" hostspecific=\"false\" language=\"C#\" #>\n<#@ assembly name=\"System.Core\" #>\n<#@ import namespac"
},
{
"path": "src/Chell/Extensions/StringExtensions.cs",
"chars": 1793,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Text.Json;\nusing Sys"
},
{
"path": "src/Chell/IO/ChellWrappedStream.cs",
"chars": 6113,
"preview": "using System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System."
},
{
"path": "src/Chell/IO/ChellWritableStream.Generated.cs",
"chars": 2098,
"preview": "#nullable enable\n/// <auto-generated />\nusing System;\nusing System.IO;\nusing System.Threading.Tasks;\n\nnamespace Chell.I"
},
{
"path": "src/Chell/IO/ChellWritableStream.tt",
"chars": 1166,
"preview": "<#@ template debug=\"false\" hostspecific=\"false\" language=\"C#\" #>\n<#@ assembly name=\"System.Core\" #>\n<#@ import namespac"
},
{
"path": "src/Chell/IO/IConsoleProvider.cs",
"chars": 522,
"preview": "using System.Collections.Generic;\nusing System.IO;\nusing System.Text;\n\nnamespace Chell.IO\n{\n public interface IConsol"
},
{
"path": "src/Chell/IO/LINQPadConsoleProvider.cs",
"chars": 2429,
"preview": "using System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.IO.Pipelines;\nusing "
},
{
"path": "src/Chell/IO/SystemConsoleProvider.cs",
"chars": 1084,
"preview": "using System;\nusing System.IO;\nusing System.Text;\n\nnamespace Chell.IO\n{\n /// <summary>\n /// Encapsulates console i"
},
{
"path": "src/Chell/Internal/CommandLineHelper.cs",
"chars": 3427,
"preview": "using System;\nusing System.Collections;\nusing System.Linq;\nusing Chell.IO;\nusing Chell.Shell;\nusing Kokuban;\n\n// ReSharp"
},
{
"path": "src/Chell/Internal/EnvironmentVariables.cs",
"chars": 2981,
"preview": "using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace Chell.Internal\n"
},
{
"path": "src/Chell/Internal/LINQPadHelper.cs",
"chars": 891,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.IO.Pipelines;\nus"
},
{
"path": "src/Chell/Internal/ObjectDumper.cs",
"chars": 1785,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\nusing Sy"
},
{
"path": "src/Chell/Internal/OutputSink.cs",
"chars": 4170,
"preview": "using System;\nusing System.IO;\nusing System.IO.Pipelines;\nusing System.Text;\nusing System.Threading;\nusing System.Threa"
},
{
"path": "src/Chell/Internal/StandardInput.cs",
"chars": 416,
"preview": "using System;\nusing System.Threading;\n\nnamespace Chell.Internal\n{\n internal class StandardInput\n {\n privat"
},
{
"path": "src/Chell/Internal/StreamPipe.cs",
"chars": 8566,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.IO.Pipelines;\nusing System.Linq;\nusing Sys"
},
{
"path": "src/Chell/Internal/Which.cs",
"chars": 1694,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropService"
},
{
"path": "src/Chell/ProcessOutput.cs",
"chars": 2587,
"preview": "using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Text;\nu"
},
{
"path": "src/Chell/ProcessTask.cs",
"chars": 21944,
"preview": "using System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Runtime.ExceptionServices;\nusing System.Threading;"
},
{
"path": "src/Chell/ProcessTaskException.cs",
"chars": 1068,
"preview": "using System;\n\nnamespace Chell\n{\n /// <summary>\n /// Represents an error that occurs during process execution.\n "
},
{
"path": "src/Chell/ProcessTaskOptions.cs",
"chars": 4117,
"preview": "using System;\nusing Chell.IO;\nusing Chell.Shell;\n\nnamespace Chell\n{\n public class ProcessTaskOptions\n {\n //"
},
{
"path": "src/Chell/Run.cs",
"chars": 2803,
"preview": "using System;\nusing System.IO;\nusing Chell.Shell;\n\nnamespace Chell\n{\n /// <summary>\n /// Short cut for <see cref=\""
},
{
"path": "src/Chell/Shell/BashShellExecutor.cs",
"chars": 1585,
"preview": "using System.Diagnostics;\nusing System.IO;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing Chell.Internal"
},
{
"path": "src/Chell/Shell/CmdShellExecutor.cs",
"chars": 839,
"preview": "using System.Text;\nusing System.Text.RegularExpressions;\n\nnamespace Chell.Shell\n{\n public class CmdShellExecutor : IS"
},
{
"path": "src/Chell/Shell/IShellExecutor.cs",
"chars": 375,
"preview": "using System.Collections.Generic;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing Sys"
},
{
"path": "src/Chell/Shell/NoUseShellExecutor.cs",
"chars": 593,
"preview": "using System.Text;\nusing System.Text.RegularExpressions;\nusing Chell.Internal;\n\nnamespace Chell.Shell\n{\n public clas"
},
{
"path": "src/Chell/Shell/ShellExecutorProvider.cs",
"chars": 1513,
"preview": "using System;\nusing System.Runtime.InteropServices;\nusing Chell.Shell;\n\nnamespace Chell.Shell\n{\n public class ShellEx"
},
{
"path": "src/Chell.Run/Chell.Run.csproj",
"chars": 1262,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFrameworks>netcoreapp3"
},
{
"path": "src/Chell.Run/Program.cs",
"chars": 6191,
"preview": "using System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\n"
},
{
"path": "tests/Chell.Tests/Chell.Tests.csproj",
"chars": 968,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <TargetFramework>net5.0</TargetFramework>\n\n <IsPackable>fal"
},
{
"path": "tests/Chell.Tests/ChellEnvironmentTest.cs",
"chars": 731,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Te"
},
{
"path": "tests/Chell.Tests/CommandLineStringTest.cs",
"chars": 2835,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusi"
},
{
"path": "tests/Chell.Tests/ProcessTaskTest.cs",
"chars": 21965,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.IO.Pipelines;\nusing System.Linq;\nusing Sys"
},
{
"path": "tests/Chell.Tests/Shell/BashShellExecutorTest.cs",
"chars": 1367,
"preview": "using System;\nusing Chell.Shell;\nusing FluentAssertions;\nusing Xunit;\n\nnamespace Chell.Tests.Shell\n{\n public class Ba"
},
{
"path": "tests/Chell.Tests/Shell/CmdShellExecutorTest.cs",
"chars": 2109,
"preview": "using System;\nusing Chell.Shell;\nusing FluentAssertions;\nusing Xunit;\n\nnamespace Chell.Tests.Shell\n{\n public class Cm"
},
{
"path": "tests/Chell.Tests/TemporaryAppBuilder.cs",
"chars": 6716,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing Syst"
}
]
About this extraction
This page contains the full source code of the mayuki/Chell GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 57 files (200.3 KB), approximately 47.0k tokens, and a symbol index with 349 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.