├── .devcontainer
└── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── ci.yml
│ └── pr.yml
├── .gitignore
├── .issuetracker
├── Directory.Build.props
├── LICENSE.md
├── README.md
├── ReleaseNotes.md
├── StrongName.snk
├── System.IO.Abstractions.Extensions.sln
├── global.json
├── images
└── icon_256x256.png
├── renovate.json
├── src
├── Directory.Build.props
└── System.IO.Abstractions.Extensions
│ ├── DisposableDirectory.cs
│ ├── DisposableFile.cs
│ ├── DisposableFileSystemInfo.cs
│ ├── IDirectoryInfoExtensions.cs
│ ├── IFileInfoAsyncExtensions.cs
│ ├── IFileInfoExtensions.cs
│ ├── IFileSystemExtensions.cs
│ ├── LineEnumerator.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── StringResources.cs
│ └── System.IO.Abstractions.Extensions.csproj
├── tests
├── Directory.Build.props
└── System.IO.Abstractions.Extensions.Tests
│ ├── DirectoryInfoExtensionsTests.cs
│ ├── DisposableDirectoryTests.cs
│ ├── DisposableFileTests.cs
│ ├── FileInfoExtensionsTests.cs
│ ├── FileSystemExtensionsTests.cs
│ └── System.IO.Abstractions.Extensions.Tests.csproj
└── version.json
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "C# (.NET Core)",
3 | "image": "mcr.microsoft.com/vscode/devcontainers/dotnet:9.0",
4 | "settings": {
5 | "terminal.integrated.shell.linux": "/bin/bash"
6 | },
7 | "postCreateCommand": "dotnet restore"
8 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; This file is for unifying the coding style for different editors and IDEs.
2 | ; More information at http://EditorConfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = CRLF
8 |
9 | [*.cs]
10 | indent_style = space
11 | indent_size = 4
12 |
13 | dotnet_diagnostic.NUnit2003.severity = suggestion
14 | dotnet_diagnostic.NUnit2004.severity = suggestion
15 | dotnet_diagnostic.NUnit2005.severity = suggestion
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
65 | # Force bash scripts to always use lf line endings so that if a repo is accessed
66 | # in Unix via a file share from Windows, the scripts will work.
67 | *.sh text eol=lf
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: 'state: needs discussion, type: bug'
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Additional context**
24 | Add any other context about the problem here.
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: 'state: needs discussion, type: enhancement'
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Continuous Integration
2 | on:
3 | push:
4 | branches:
5 | - main
6 | - 'feature/**'
7 | pull_request:
8 | branches: [main]
9 | jobs:
10 | test:
11 | name: Test
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | os: [ubuntu-latest, windows-latest, macos-latest]
16 | runs-on: ${{ matrix.os }}
17 | steps:
18 | - name: Checkout sources
19 | uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 | - name: Setup .NET
23 | uses: actions/setup-dotnet@v4
24 | with:
25 | dotnet-version: |
26 | 8.0.x
27 | 9.0.x
28 | - name: Run tests
29 | run: dotnet test --collect:"XPlat Code Coverage" --logger "GitHubActions"
30 | - name: Upload coverage
31 | uses: actions/upload-artifact@v4
32 | with:
33 | name: Code coverage ${{ matrix.os }}
34 | path: "**/coverage.cobertura.xml"
35 | coverage:
36 | name: Coverage
37 | needs: [test]
38 | runs-on: ubuntu-latest
39 | steps:
40 | - name: Checkout sources
41 | uses: actions/checkout@v4
42 | - name: Setup .NET
43 | uses: actions/setup-dotnet@v4
44 | - uses: actions/download-artifact@v4
45 | with:
46 | name: Code coverage ubuntu-latest
47 | path: coverage-ubuntu
48 | - uses: actions/download-artifact@v4
49 | with:
50 | name: Code coverage windows-latest
51 | path: coverage-windows
52 | - uses: actions/download-artifact@v4
53 | with:
54 | name: Code coverage macos-latest
55 | path: coverage-macos
56 | - name: Generate coverage report
57 | uses: danielpalme/ReportGenerator-GitHub-Action@5.2.2
58 | with:
59 | reports: "**/coverage.cobertura.xml"
60 | targetdir: "coverage-report"
61 | reporttypes: "Cobertura"
62 | - name: Publish coverage report to Codacy
63 | uses: codacy/codacy-coverage-reporter-action@master
64 | if: github.repository == 'System-IO-Abstractions/System.IO.Abstractions.Extensions' && github.event_name == 'push'
65 | with:
66 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
67 | coverage-reports: coverage-report/Cobertura.xml
68 | pack:
69 | name: Pack
70 | needs: [test]
71 | runs-on: ubuntu-latest
72 | steps:
73 | - name: Checkout sources
74 | uses: actions/checkout@v4
75 | with:
76 | fetch-depth: 0
77 | - name: Setup .NET
78 | uses: actions/setup-dotnet@v4
79 | - name: Create packages
80 | run: dotnet pack --configuration Release --output ./packages
81 | - name: Upload a Build Artifact
82 | uses: actions/upload-artifact@v4
83 | with:
84 | name: NuGet packages
85 | path: packages/*.*
86 | deploy:
87 | name: Deploy
88 | if: github.ref == 'refs/heads/main' && github.event_name == 'push'
89 | needs: [pack]
90 | runs-on: ubuntu-latest
91 | steps:
92 | - name: Checkout sources
93 | uses: actions/checkout@v4
94 | with:
95 | fetch-depth: 0
96 | - name: Setup .NET
97 | uses: actions/setup-dotnet@v4
98 | - uses: actions/download-artifact@v4
99 | with:
100 | name: NuGet packages
101 | path: packages
102 | - name: Push packages
103 | run: dotnet nuget push "packages/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
104 | - uses: dotnet/nbgv@v0.4.2
105 | id: nbgv
106 | - name: Create GitHub release
107 | uses: actions/create-release@v1
108 | env:
109 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
110 | with:
111 | tag_name: v${{ steps.nbgv.outputs.SemVer2 }}
112 | release_name: v${{ steps.nbgv.outputs.SemVer2 }}
113 | - name: Comment relevant issues and merge requests
114 | uses: apexskier/github-release-commenter@v1.3.6
115 | with:
116 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
117 | comment-template: |
118 | This is addressed in release {release_link}.
119 | label-template: |
120 | state: released
121 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yml:
--------------------------------------------------------------------------------
1 | name: "Pull Request"
2 | on:
3 | pull_request_target:
4 | types:
5 | - opened
6 | - edited
7 | - synchronize
8 | jobs:
9 | main:
10 | name: Check PR title
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: amannn/action-semantic-pull-request@v5.4.0
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/csharp,visualstudio,visualstudiocode
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=csharp,visualstudio,visualstudiocode
4 |
5 | ### Csharp ###
6 | ## Ignore Visual Studio temporary files, build results, and
7 | ## files generated by popular Visual Studio add-ons.
8 | ##
9 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
10 |
11 | # User-specific files
12 | *.rsuser
13 | *.suo
14 | *.user
15 | *.userosscache
16 | *.sln.docstates
17 |
18 | # User-specific files (MonoDevelop/Xamarin Studio)
19 | *.userprefs
20 |
21 | # Mono auto generated files
22 | mono_crash.*
23 |
24 | # Build results
25 | [Dd]ebug/
26 | [Dd]ebugPublic/
27 | [Rr]elease/
28 | [Rr]eleases/
29 | x64/
30 | x86/
31 | [Ww][Ii][Nn]32/
32 | [Aa][Rr][Mm]/
33 | [Aa][Rr][Mm]64/
34 | bld/
35 | [Bb]in/
36 | [Oo]bj/
37 | [Ll]og/
38 | [Ll]ogs/
39 |
40 | # Visual Studio 2015/2017 cache/options directory
41 | .vs/
42 | # Uncomment if you have tasks that create the project's static files in wwwroot
43 | #wwwroot/
44 |
45 | # Visual Studio 2017 auto generated files
46 | Generated\ Files/
47 |
48 | # MSTest test Results
49 | [Tt]est[Rr]esult*/
50 | [Bb]uild[Ll]og.*
51 |
52 | # NUnit
53 | *.VisualState.xml
54 | TestResult.xml
55 | nunit-*.xml
56 |
57 | # Build Results of an ATL Project
58 | [Dd]ebugPS/
59 | [Rr]eleasePS/
60 | dlldata.c
61 |
62 | # Benchmark Results
63 | BenchmarkDotNet.Artifacts/
64 |
65 | # .NET Core
66 | project.lock.json
67 | project.fragment.lock.json
68 | artifacts/
69 |
70 | # ASP.NET Scaffolding
71 | ScaffoldingReadMe.txt
72 |
73 | # StyleCop
74 | StyleCopReport.xml
75 |
76 | # Files built by Visual Studio
77 | *_i.c
78 | *_p.c
79 | *_h.h
80 | *.ilk
81 | *.meta
82 | *.obj
83 | *.iobj
84 | *.pch
85 | *.pdb
86 | *.ipdb
87 | *.pgc
88 | *.pgd
89 | *.rsp
90 | *.sbr
91 | *.tlb
92 | *.tli
93 | *.tlh
94 | *.tmp
95 | *.tmp_proj
96 | *_wpftmp.csproj
97 | *.log
98 | *.tlog
99 | *.vspscc
100 | *.vssscc
101 | .builds
102 | *.pidb
103 | *.svclog
104 | *.scc
105 |
106 | # Chutzpah Test files
107 | _Chutzpah*
108 |
109 | # Visual C++ cache files
110 | ipch/
111 | *.aps
112 | *.ncb
113 | *.opendb
114 | *.opensdf
115 | *.sdf
116 | *.cachefile
117 | *.VC.db
118 | *.VC.VC.opendb
119 |
120 | # Visual Studio profiler
121 | *.psess
122 | *.vsp
123 | *.vspx
124 | *.sap
125 |
126 | # Visual Studio Trace Files
127 | *.e2e
128 |
129 | # TFS 2012 Local Workspace
130 | $tf/
131 |
132 | # Guidance Automation Toolkit
133 | *.gpState
134 |
135 | # ReSharper is a .NET coding add-in
136 | _ReSharper*/
137 | *.[Rr]e[Ss]harper
138 | *.DotSettings.user
139 |
140 | # TeamCity is a build add-in
141 | _TeamCity*
142 |
143 | # DotCover is a Code Coverage Tool
144 | *.dotCover
145 |
146 | # AxoCover is a Code Coverage Tool
147 | .axoCover/*
148 | !.axoCover/settings.json
149 |
150 | # Coverlet is a free, cross platform Code Coverage Tool
151 | coverage*.json
152 | coverage*.xml
153 | coverage*.info
154 |
155 | # Visual Studio code coverage results
156 | *.coverage
157 | *.coveragexml
158 |
159 | # NCrunch
160 | _NCrunch_*
161 | .*crunch*.local.xml
162 | nCrunchTemp_*
163 |
164 | # MightyMoose
165 | *.mm.*
166 | AutoTest.Net/
167 |
168 | # Web workbench (sass)
169 | .sass-cache/
170 |
171 | # Installshield output folder
172 | [Ee]xpress/
173 |
174 | # DocProject is a documentation generator add-in
175 | DocProject/buildhelp/
176 | DocProject/Help/*.HxT
177 | DocProject/Help/*.HxC
178 | DocProject/Help/*.hhc
179 | DocProject/Help/*.hhk
180 | DocProject/Help/*.hhp
181 | DocProject/Help/Html2
182 | DocProject/Help/html
183 |
184 | # Click-Once directory
185 | publish/
186 |
187 | # Publish Web Output
188 | *.[Pp]ublish.xml
189 | *.azurePubxml
190 | # Note: Comment the next line if you want to checkin your web deploy settings,
191 | # but database connection strings (with potential passwords) will be unencrypted
192 | *.pubxml
193 | *.publishproj
194 |
195 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
196 | # checkin your Azure Web App publish settings, but sensitive information contained
197 | # in these scripts will be unencrypted
198 | PublishScripts/
199 |
200 | # NuGet Packages
201 | *.nupkg
202 | # NuGet Symbol Packages
203 | *.snupkg
204 | # The packages folder can be ignored because of Package Restore
205 | **/[Pp]ackages/*
206 | # except build/, which is used as an MSBuild target.
207 | !**/[Pp]ackages/build/
208 | # Uncomment if necessary however generally it will be regenerated when needed
209 | #!**/[Pp]ackages/repositories.config
210 | # NuGet v3's project.json files produces more ignorable files
211 | *.nuget.props
212 | *.nuget.targets
213 |
214 | # Nuget personal access tokens and Credentials
215 | nuget.config
216 |
217 | # Microsoft Azure Build Output
218 | csx/
219 | *.build.csdef
220 |
221 | # Microsoft Azure Emulator
222 | ecf/
223 | rcf/
224 |
225 | # Windows Store app package directories and files
226 | AppPackages/
227 | BundleArtifacts/
228 | Package.StoreAssociation.xml
229 | _pkginfo.txt
230 | *.appx
231 | *.appxbundle
232 | *.appxupload
233 |
234 | # Visual Studio cache files
235 | # files ending in .cache can be ignored
236 | *.[Cc]ache
237 | # but keep track of directories ending in .cache
238 | !?*.[Cc]ache/
239 |
240 | # Others
241 | ClientBin/
242 | ~$*
243 | *~
244 | *.dbmdl
245 | *.dbproj.schemaview
246 | *.jfm
247 | *.pfx
248 | *.publishsettings
249 | orleans.codegen.cs
250 |
251 | # Including strong name files can present a security risk
252 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
253 | #*.snk
254 |
255 | # Since there are multiple workflows, uncomment next line to ignore bower_components
256 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
257 | #bower_components/
258 |
259 | # RIA/Silverlight projects
260 | Generated_Code/
261 |
262 | # Backup & report files from converting an old project file
263 | # to a newer Visual Studio version. Backup files are not needed,
264 | # because we have git ;-)
265 | _UpgradeReport_Files/
266 | Backup*/
267 | UpgradeLog*.XML
268 | UpgradeLog*.htm
269 | ServiceFabricBackup/
270 | *.rptproj.bak
271 |
272 | # SQL Server files
273 | *.mdf
274 | *.ldf
275 | *.ndf
276 |
277 | # Business Intelligence projects
278 | *.rdl.data
279 | *.bim.layout
280 | *.bim_*.settings
281 | *.rptproj.rsuser
282 | *- [Bb]ackup.rdl
283 | *- [Bb]ackup ([0-9]).rdl
284 | *- [Bb]ackup ([0-9][0-9]).rdl
285 |
286 | # Microsoft Fakes
287 | FakesAssemblies/
288 |
289 | # GhostDoc plugin setting file
290 | *.GhostDoc.xml
291 |
292 | # Node.js Tools for Visual Studio
293 | .ntvs_analysis.dat
294 | node_modules/
295 |
296 | # Visual Studio 6 build log
297 | *.plg
298 |
299 | # Visual Studio 6 workspace options file
300 | *.opt
301 |
302 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
303 | *.vbw
304 |
305 | # Visual Studio LightSwitch build output
306 | **/*.HTMLClient/GeneratedArtifacts
307 | **/*.DesktopClient/GeneratedArtifacts
308 | **/*.DesktopClient/ModelManifest.xml
309 | **/*.Server/GeneratedArtifacts
310 | **/*.Server/ModelManifest.xml
311 | _Pvt_Extensions
312 |
313 | # Paket dependency manager
314 | .paket/paket.exe
315 | paket-files/
316 |
317 | # FAKE - F# Make
318 | .fake/
319 |
320 | # CodeRush personal settings
321 | .cr/personal
322 |
323 | # Python Tools for Visual Studio (PTVS)
324 | __pycache__/
325 | *.pyc
326 |
327 | # Cake - Uncomment if you are using it
328 | # tools/**
329 | # !tools/packages.config
330 |
331 | # Tabs Studio
332 | *.tss
333 |
334 | # Telerik's JustMock configuration file
335 | *.jmconfig
336 |
337 | # BizTalk build output
338 | *.btp.cs
339 | *.btm.cs
340 | *.odx.cs
341 | *.xsd.cs
342 |
343 | # OpenCover UI analysis results
344 | OpenCover/
345 |
346 | # Azure Stream Analytics local run output
347 | ASALocalRun/
348 |
349 | # MSBuild Binary and Structured Log
350 | *.binlog
351 |
352 | # NVidia Nsight GPU debugger configuration file
353 | *.nvuser
354 |
355 | # MFractors (Xamarin productivity tool) working folder
356 | .mfractor/
357 |
358 | # Local History for Visual Studio
359 | .localhistory/
360 |
361 | # BeatPulse healthcheck temp database
362 | healthchecksdb
363 |
364 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
365 | MigrationBackup/
366 |
367 | # Ionide (cross platform F# VS Code tools) working folder
368 | .ionide/
369 |
370 | # Fody - auto-generated XML schema
371 | FodyWeavers.xsd
372 |
373 | # VS Code files for those working on multiple tools
374 | .vscode/*
375 | !.vscode/settings.json
376 | !.vscode/tasks.json
377 | !.vscode/launch.json
378 | !.vscode/extensions.json
379 | *.code-workspace
380 |
381 | # Local History for Visual Studio Code
382 | .history/
383 |
384 | # Windows Installer files from build outputs
385 | *.cab
386 | *.msi
387 | *.msix
388 | *.msm
389 | *.msp
390 |
391 | # JetBrains Rider
392 | .idea/
393 | *.sln.iml
394 |
395 | ### VisualStudioCode ###
396 |
397 | # Local History for Visual Studio Code
398 |
399 | ### VisualStudioCode Patch ###
400 | # Ignore all local history of files
401 | .history
402 | .ionide
403 |
404 | ### VisualStudio ###
405 |
406 | # User-specific files
407 |
408 | # User-specific files (MonoDevelop/Xamarin Studio)
409 |
410 | # Mono auto generated files
411 |
412 | # Build results
413 |
414 | # Visual Studio 2015/2017 cache/options directory
415 | # Uncomment if you have tasks that create the project's static files in wwwroot
416 |
417 | # Visual Studio 2017 auto generated files
418 |
419 | # MSTest test Results
420 |
421 | # NUnit
422 |
423 | # Build Results of an ATL Project
424 |
425 | # Benchmark Results
426 |
427 | # .NET Core
428 |
429 | # ASP.NET Scaffolding
430 |
431 | # StyleCop
432 |
433 | # Files built by Visual Studio
434 |
435 | # Chutzpah Test files
436 |
437 | # Visual C++ cache files
438 |
439 | # Visual Studio profiler
440 |
441 | # Visual Studio Trace Files
442 |
443 | # TFS 2012 Local Workspace
444 |
445 | # Guidance Automation Toolkit
446 |
447 | # ReSharper is a .NET coding add-in
448 |
449 | # TeamCity is a build add-in
450 |
451 | # DotCover is a Code Coverage Tool
452 |
453 | # AxoCover is a Code Coverage Tool
454 |
455 | # Coverlet is a free, cross platform Code Coverage Tool
456 |
457 | # Visual Studio code coverage results
458 |
459 | # NCrunch
460 |
461 | # MightyMoose
462 |
463 | # Web workbench (sass)
464 |
465 | # Installshield output folder
466 |
467 | # DocProject is a documentation generator add-in
468 |
469 | # Click-Once directory
470 |
471 | # Publish Web Output
472 | # Note: Comment the next line if you want to checkin your web deploy settings,
473 | # but database connection strings (with potential passwords) will be unencrypted
474 |
475 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
476 | # checkin your Azure Web App publish settings, but sensitive information contained
477 | # in these scripts will be unencrypted
478 |
479 | # NuGet Packages
480 | # NuGet Symbol Packages
481 | # The packages folder can be ignored because of Package Restore
482 | # except build/, which is used as an MSBuild target.
483 | # Uncomment if necessary however generally it will be regenerated when needed
484 | # NuGet v3's project.json files produces more ignorable files
485 |
486 | # Nuget personal access tokens and Credentials
487 |
488 | # Microsoft Azure Build Output
489 |
490 | # Microsoft Azure Emulator
491 |
492 | # Windows Store app package directories and files
493 |
494 | # Visual Studio cache files
495 | # files ending in .cache can be ignored
496 | # but keep track of directories ending in .cache
497 |
498 | # Others
499 |
500 | # Including strong name files can present a security risk
501 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
502 |
503 | # Since there are multiple workflows, uncomment next line to ignore bower_components
504 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
505 |
506 | # RIA/Silverlight projects
507 |
508 | # Backup & report files from converting an old project file
509 | # to a newer Visual Studio version. Backup files are not needed,
510 | # because we have git ;-)
511 |
512 | # SQL Server files
513 |
514 | # Business Intelligence projects
515 |
516 | # Microsoft Fakes
517 |
518 | # GhostDoc plugin setting file
519 |
520 | # Node.js Tools for Visual Studio
521 |
522 | # Visual Studio 6 build log
523 |
524 | # Visual Studio 6 workspace options file
525 |
526 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
527 |
528 | # Visual Studio LightSwitch build output
529 |
530 | # Paket dependency manager
531 |
532 | # FAKE - F# Make
533 |
534 | # CodeRush personal settings
535 |
536 | # Python Tools for Visual Studio (PTVS)
537 |
538 | # Cake - Uncomment if you are using it
539 | # tools/**
540 | # !tools/packages.config
541 |
542 | # Tabs Studio
543 |
544 | # Telerik's JustMock configuration file
545 |
546 | # BizTalk build output
547 |
548 | # OpenCover UI analysis results
549 |
550 | # Azure Stream Analytics local run output
551 |
552 | # MSBuild Binary and Structured Log
553 |
554 | # NVidia Nsight GPU debugger configuration file
555 |
556 | # MFractors (Xamarin productivity tool) working folder
557 |
558 | # Local History for Visual Studio
559 |
560 | # BeatPulse healthcheck temp database
561 |
562 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
563 |
564 | # Ionide (cross platform F# VS Code tools) working folder
565 |
566 | # Fody - auto-generated XML schema
567 |
568 | # VS Code files for those working on multiple tools
569 |
570 | # Local History for Visual Studio Code
571 |
572 | # Windows Installer files from build outputs
573 |
574 | # JetBrains Rider
575 |
576 | ### VisualStudio Patch ###
577 | # Additional files built by Visual Studio
578 |
579 | # End of https://www.toptal.com/developers/gitignore/api/csharp,visualstudio,visualstudiocode
580 | *.sh
581 |
--------------------------------------------------------------------------------
/.issuetracker:
--------------------------------------------------------------------------------
1 | # Integration with Issue Tracker
2 | #
3 | # (note that '\' need to be escaped).
4 |
5 | [issuetracker "GitHub"]
6 | regex = "#(\\d+)"
7 | url = "https://github.com/System-IO-Abstractions/System.IO.Abstractions.Extensions.git/issues/$1"
8 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | System.IO.Abstractions.Extensions
4 | Copyright © Tatham Oddie - Luigi Grilli & friends 2021
5 | Tatham Oddie - Luigi Grilli & friends
6 | True
7 | $(MSBuildThisFileDirectory)StrongName.snk
8 | true
9 | true
10 | snupkg
11 | true
12 | testing
13 | https://github.com/System-IO-Abstractions/System.IO.Abstractions.Extensions
14 | https://github.com/System-IO-Abstractions/System.IO.Abstractions.Extensions.git
15 | git
16 | $(MSBuildThisFileDirectory)
17 | MIT
18 | README.md
19 |
20 |
21 |
22 | runtime; build; native; contentfiles; analyzers
23 | all
24 |
25 |
26 |
27 | runtime; build; native; contentfiles; analyzers; buildtransitive
28 | all
29 |
30 |
31 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Tatham Oddie - Luigi Grilli & friends
4 |
5 | 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:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | 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.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | [](https://www.nuget.org/packages/TestableIO.System.IO.Abstractions.Extensions)
3 | 
4 | [](https://renovatebot.com/)
5 |
6 |
7 |
8 | Convenience functionality on top of System.IO.Abstractions
9 |
10 | ```shell
11 | dotnet add package TestableIO.System.IO.Abstractions.Extensions
12 | ```
13 |
14 | # Examples
15 |
16 | ## CurrentDirectory extension
17 |
18 | ```csharp
19 | var fs = new FileSystem();
20 |
21 | //with extension
22 | var current = fs.CurrentDirectory();
23 |
24 | //without extension
25 | var current = fs.DirectoryInfo.FromDirectoryName(fs.Directory.GetCurrentDirectory());
26 | ```
27 |
28 | ## SubDirectory extension
29 |
30 | ```csharp
31 | var current = new FileSystem().CurrentDirectory();
32 |
33 | //create a "temp" subdirectory with extension
34 | current.SubDirectory("temp").Create();
35 |
36 | //create a "temp" subdirectory without extension
37 | current.FileSystem.DirectoryInfo.FromDirectoryName(current.FileSystem.Path.Combine(current.FullName, "temp")).Create();
38 | ```
39 |
40 | ## File extension
41 |
42 | ```csharp
43 | var current = new FileSystem().CurrentDirectory();
44 |
45 | //create a "test.txt" file with extension
46 | using (var stream = current.File("test.txt").Create())
47 | stream.Dispose();
48 |
49 | //create a "test.txt" file without extension
50 | using (var stream = current.FileSystem.FileInfo.FromFileName(current.FileSystem.Path.Combine(current.FullName, "test.txt")).Create())
51 | stream.Dispose();
52 | ```
53 |
54 | ## Automatic cleanup with Disposable extensions
55 |
56 | Use `CreateDisposableDirectory` or `CreateDisposableFile` to create a `IDirectoryInfo` or `IFileInfo` that's automatically
57 | deleted when the returned `IDisposable` is disposed.
58 |
59 | ```csharp
60 | var fs = new FileSystem();
61 |
62 | //with extension
63 | using (fs.CreateDisposableDirectory(out IDirectoryInfo dir))
64 | {
65 | Console.WriteLine($"This directory will be deleted when control leaves the using block: '{dir.FullName}'");
66 | }
67 |
68 | //without extension
69 | var temp = fs.Path.GetTempPath();
70 | var fileName = fs.Path.GetRandomFileName();
71 | var path = fs.Path.Combine(temp, fileName);
72 |
73 | try
74 | {
75 | IDirectoryInfo dir = fs.Directory.CreateDirectory(path);
76 | Console.WriteLine($"This directory will be deleted in the finally block: '{dir.FullName}'");
77 | }
78 | finally
79 | {
80 | fs.Directory.Delete(path, recursive: true);
81 | }
82 | ```
83 |
84 | ## IDirectoryInfo.CopyTo extension
85 | ```csharp
86 | var fs = new FileSystem();
87 | var current = fs.CurrentDirectory();
88 |
89 | //source
90 | var source = current.SubDirectory("source");
91 | source.Create(); //make sure the source directory exists
92 |
93 | //destination
94 | var dest = current.SubDirectory("destination");
95 |
96 | //copy
97 | source.CopyTo(dest, recursive: true);
98 | ```
99 |
--------------------------------------------------------------------------------
/ReleaseNotes.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | List of notable changes between majors
4 |
5 | ## 22.0.0
6 |
7 | - (Breaking) Removed support for some Async calls in .NET Framework and
8 | legacy versions of .NET to better support .NET 8 and later
9 | - Minimum required TestableIO.System.IO.Abstractions version is now 22.x
10 | - Removed .NET 7 build (should still work with .NET standard build)
11 | - Removed .NET 6 build (should still work with .NET standard build)
12 |
13 | ## 2.0.0
14 |
15 | - (Breaking) Moved all extensions methods to 'System.IO.Abstractions' namespace
16 | - Added ThrowIfNotFound extension methods
17 | - Removed .NET 5 build (should still work with .NET standard build)
--------------------------------------------------------------------------------
/StrongName.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TestableIO/System.IO.Abstractions.Extensions/5caba455d528f93c6e26039dd0f93d930b1d899b/StrongName.snk
--------------------------------------------------------------------------------
/System.IO.Abstractions.Extensions.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.6.33815.320
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E4D67E71-DBAC-41BD-B9FC-477075F82980}"
6 | ProjectSection(SolutionItems) = preProject
7 | src\Directory.Build.props = src\Directory.Build.props
8 | EndProjectSection
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.IO.Abstractions.Extensions", "src\System.IO.Abstractions.Extensions\System.IO.Abstractions.Extensions.csproj", "{89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{32B54437-9DA5-42AF-9922-9AE6ABAC30A8}"
13 | ProjectSection(SolutionItems) = preProject
14 | tests\Directory.Build.props = tests\Directory.Build.props
15 | EndProjectSection
16 | EndProject
17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.IO.Abstractions.Extensions.Tests", "tests\System.IO.Abstractions.Extensions.Tests\System.IO.Abstractions.Extensions.Tests.csproj", "{5145BBFD-BB63-4D70-89DF-0C5B2772752A}"
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F69100BC-0333-4F19-8981-8F17FD4A1A99}"
20 | ProjectSection(SolutionItems) = preProject
21 | .editorconfig = .editorconfig
22 | Directory.Build.props = Directory.Build.props
23 | global.json = global.json
24 | LICENSE.md = LICENSE.md
25 | README.md = README.md
26 | ReleaseNotes.md = ReleaseNotes.md
27 | version.json = version.json
28 | EndProjectSection
29 | EndProject
30 | Global
31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
32 | Debug|Any CPU = Debug|Any CPU
33 | Debug|x64 = Debug|x64
34 | Debug|x86 = Debug|x86
35 | Release|Any CPU = Release|Any CPU
36 | Release|x64 = Release|x64
37 | Release|x86 = Release|x86
38 | EndGlobalSection
39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
40 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Debug|x64.ActiveCfg = Debug|Any CPU
43 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Debug|x64.Build.0 = Debug|Any CPU
44 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Debug|x86.ActiveCfg = Debug|Any CPU
45 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Debug|x86.Build.0 = Debug|Any CPU
46 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Release|x64.ActiveCfg = Release|Any CPU
49 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Release|x64.Build.0 = Release|Any CPU
50 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Release|x86.ActiveCfg = Release|Any CPU
51 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9}.Release|x86.Build.0 = Release|Any CPU
52 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Debug|x64.ActiveCfg = Debug|Any CPU
55 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Debug|x64.Build.0 = Debug|Any CPU
56 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Debug|x86.ActiveCfg = Debug|Any CPU
57 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Debug|x86.Build.0 = Debug|Any CPU
58 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Release|Any CPU.Build.0 = Release|Any CPU
60 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Release|x64.ActiveCfg = Release|Any CPU
61 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Release|x64.Build.0 = Release|Any CPU
62 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Release|x86.ActiveCfg = Release|Any CPU
63 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A}.Release|x86.Build.0 = Release|Any CPU
64 | EndGlobalSection
65 | GlobalSection(SolutionProperties) = preSolution
66 | HideSolutionNode = FALSE
67 | EndGlobalSection
68 | GlobalSection(NestedProjects) = preSolution
69 | {89D9E244-E25D-40AA-A31E-8EDA1EBFDFD9} = {E4D67E71-DBAC-41BD-B9FC-477075F82980}
70 | {5145BBFD-BB63-4D70-89DF-0C5B2772752A} = {32B54437-9DA5-42AF-9922-9AE6ABAC30A8}
71 | EndGlobalSection
72 | GlobalSection(ExtensibilityGlobals) = postSolution
73 | SolutionGuid = {C4FB0DBE-5DAA-4E40-B91A-B37C57893528}
74 | EndGlobalSection
75 | EndGlobal
76 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "9.0",
4 | "rollForward": "latestMinor"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/images/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TestableIO/System.IO.Abstractions.Extensions/5caba455d528f93c6e26039dd0f93d930b1d899b/images/icon_256x256.png
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 | snupkg
7 | true
8 | icon_256x256.png
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/DisposableDirectory.cs:
--------------------------------------------------------------------------------
1 | namespace System.IO.Abstractions
2 | {
3 | ///
4 | /// Creates a class that wraps a . That wrapped directory will be
5 | /// deleted when the method is called.
6 | ///
7 | ///
8 | public class DisposableDirectory : DisposableFileSystemInfo
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | ///
14 | /// The directory to delete when this object is disposed.
15 | ///
16 | public DisposableDirectory(IDirectoryInfo directoryInfo) : base(directoryInfo)
17 | {
18 | }
19 |
20 | ///
21 | protected override void DeleteFileSystemInfo()
22 | {
23 | fileSystemInfo.Delete(recursive: true);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/DisposableFile.cs:
--------------------------------------------------------------------------------
1 | namespace System.IO.Abstractions
2 | {
3 | ///
4 | /// Creates a class that wraps a . That wrapped file will be
5 | /// deleted when the method is called.
6 | ///
7 | ///
8 | public class DisposableFile : DisposableFileSystemInfo
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | ///
14 | /// The file to delete when this object is disposed.
15 | ///
16 | public DisposableFile(IFileInfo fileInfo) : base(fileInfo)
17 | {
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/DisposableFileSystemInfo.cs:
--------------------------------------------------------------------------------
1 | namespace System.IO.Abstractions
2 | {
3 | ///
4 | /// Creates a class that wraps a . That wrapped object will be deleted
5 | /// when the method is called.
6 | ///
7 | ///
8 | /// This class is designed for the using pattern to ensure that a directory is
9 | /// created and then deleted when it is no longer referenced. This is sometimes called
10 | /// the RAII pattern (see https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization).
11 | ///
12 | public class DisposableFileSystemInfo : IDisposable where T : IFileSystemInfo
13 | {
14 | protected T fileSystemInfo;
15 | private bool isDisposed;
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | ///
21 | /// The directory to delete when this object is disposed.
22 | ///
23 | public DisposableFileSystemInfo(T fileSystemInfo)
24 | {
25 | this.fileSystemInfo = fileSystemInfo ?? throw new ArgumentNullException(nameof(fileSystemInfo));
26 |
27 | // Do an attribute refresh so that the object we return to the caller
28 | // has up-to-date properties (like Exists).
29 | this.fileSystemInfo.Refresh();
30 | }
31 |
32 | ///
33 | /// Performs the actual work of releasing resources. This allows for subclasses to participate
34 | /// in resource release.
35 | ///
36 | ///
37 | /// true if if the call comes from a method, false if it
38 | /// comes from a finalizer.
39 | ///
40 | protected virtual void Dispose(bool disposing)
41 | {
42 | if (!isDisposed)
43 | {
44 | if (disposing)
45 | {
46 | DeleteFileSystemInfo();
47 |
48 | // Do an attribute refresh so that the object we returned to the
49 | // caller has up-to-date properties (like Exists).
50 | fileSystemInfo.Refresh();
51 | }
52 |
53 | isDisposed = true;
54 | }
55 | }
56 |
57 | ///
58 | public void Dispose()
59 | {
60 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method.
61 | // See https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose.
62 | Dispose(disposing: true);
63 | GC.SuppressFinalize(this);
64 | }
65 |
66 | ///
67 | /// Deletes the wrapped object.
68 | ///
69 | ///
70 | /// Different types of objects have different ways of deleting themselves (e.g.
71 | /// directories usually need to be deleted recursively). This method is called by the
72 | ///
73 | protected virtual void DeleteFileSystemInfo()
74 | {
75 | fileSystemInfo.Delete();
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/IDirectoryInfoExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace System.IO.Abstractions
5 | {
6 | public static class IDirectoryInfoExtensions
7 | {
8 | ///
9 | /// Get an for the specified sub-directory
10 | ///
11 | ///
12 | /// Sub-directory name (ex. "test")
13 | /// An for the specified sub-directory
14 | public static IDirectoryInfo SubDirectory(this IDirectoryInfo info, string name)
15 | {
16 | return info.FileSystem.DirectoryInfo.New(info.FileSystem.Path.Combine(info.FullName, name));
17 | }
18 |
19 | ///
20 | /// Get an for the specified sub-directories
21 | ///
22 | ///
23 | /// Sub-directory names (ex. "test", "test2"). Empty or null names are automatically removed from this list
24 | /// An for the specified sub-directory
25 | public static IDirectoryInfo SubDirectory(this IDirectoryInfo info, params string[] names)
26 | {
27 | return info.SubDirectory((IEnumerable)names);
28 | }
29 |
30 | ///
31 | /// Get an for the specified sub-directories
32 | ///
33 | ///
34 | /// Sub-directory names (ex. "test", "test2"). Empty or null names are automatically removed from this list
35 | /// An for the specified sub-directory
36 | public static IDirectoryInfo SubDirectory(this IDirectoryInfo info, IEnumerable names)
37 | {
38 | return info.FileSystem.DirectoryInfo.New(info.FileSystem.Path.Combine(GetPaths(info, names)));
39 | }
40 |
41 | ///
42 | /// Get an for the specified file
43 | ///
44 | ///
45 | /// File name (ex. "test.txt")
46 | /// An for the specified file
47 | public static IFileInfo File(this IDirectoryInfo info, string name)
48 | {
49 | return info.FileSystem.FileInfo.New(info.FileSystem.Path.Combine(info.FullName, name));
50 | }
51 |
52 | ///
53 | /// Get the full path for the specified file in the folder
54 | ///
55 | ///
56 | /// File name (ex. "test.txt")
57 | /// A with the full path of the file
58 | public static string GetFilePath(this IDirectoryInfo info, string name)
59 | {
60 | return info.FileSystem.Path.Combine(info.FullName, name);
61 | }
62 |
63 | ///
64 | /// Get an for the specified sub-directories file
65 | ///
66 | ///
67 | /// Sub-directories and file name (ex. "test", "test.txt"). Empty or null names are automatically removed from this list
68 | /// An for the specified file
69 | public static IFileInfo File(this IDirectoryInfo info, params string[] names)
70 | {
71 | return info.File((IEnumerable)names);
72 | }
73 |
74 | ///
75 | /// Get an for the specified sub-directories file
76 | ///
77 | ///
78 | /// Sub-directories and file name (ex. "test", "test.txt"). Empty or null names are automatically removed from this list
79 | /// An for the specified file
80 | public static IFileInfo File(this IDirectoryInfo info, IEnumerable names)
81 | {
82 | return info.FileSystem.FileInfo.New(info.FileSystem.Path.Combine(GetPaths(info, names)));
83 | }
84 |
85 | ///
86 | /// Throws an exception if the directory doesn't exists
87 | ///
88 | /// Directory that will be checked for existance
89 | /// Exception thrown if the directory is not found
90 | public static void ThrowIfNotFound(this IDirectoryInfo info)
91 | {
92 | if (!info.Exists)
93 | {
94 | throw new DirectoryNotFoundException(StringResources.Format("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION", info.FullName));
95 | }
96 | }
97 |
98 | ///
99 | /// Checks if is an ancestor of .
100 | /// If is a parent this method will return
101 | /// If and are the same directory, this will return
102 | ///
103 | /// Ancestor directory
104 | /// Child directory (sub-directory)
105 | /// True if is an ancestor of otherwise false
106 | public static bool IsAncestorOf(this IDirectoryInfo ancestor, IDirectoryInfo child)
107 | {
108 | return child.FullName.Length > ancestor.FullName.Length &&
109 | child.FullName.StartsWith(ancestor.FullName);
110 | }
111 |
112 | ///
113 | /// Checks if is an ancestor of and returns
114 | /// a list of segments of the paths of that are not in common with
115 | ///
116 | /// Ancestor directory
117 | /// Child directory (sub-directory)
118 | /// A with the segments of the paths of not in common with
119 | /// Exception thrown if is not an ancestor of
120 | public static string[] DiffPaths(this IDirectoryInfo ancestor, IDirectoryInfo child)
121 | {
122 | if (!ancestor.IsAncestorOf(child))
123 | {
124 | throw new ArgumentException(StringResources.Format("NOT_AN_ANCESTOR", ancestor.FullName, child.FullName), nameof(child));
125 | }
126 |
127 | return child.FullName.Substring(ancestor.FullName.Length + 1)
128 | .Split(ancestor.FileSystem.Path.PathSeparator);
129 | }
130 |
131 | ///
132 | /// Applies a between and .
133 | /// The resulting diff of path segments is applied to and returned.
134 | /// If the flag is set to true the resulting subdirectory of will also be created.
135 | /// must be the same directory or an ancestor of otherwise this method will throw an
136 | ///
137 | /// Ancestor directory
138 | /// Child directory (sub-directory)
139 | /// Directory to apply the diff to
140 | /// If set to true, the resulting directory will also be created
141 | /// An which is either a child of or ifself
142 | public static IDirectoryInfo TranslatePaths(
143 | this IDirectoryInfo ancestor1,
144 | IDirectoryInfo child,
145 | IDirectoryInfo ancestor2,
146 | bool create = false)
147 | {
148 | var ret = ancestor1.Equals(child)
149 | ? ancestor2
150 | : ancestor2.SubDirectory(ancestor1.DiffPaths(child));
151 |
152 | if (create)
153 | {
154 | ret.Create();
155 | }
156 |
157 | return ret;
158 | }
159 |
160 | ///
161 | /// Executes a for each file in the directory
162 | /// A action is also executed before entering any directory, including
163 | /// The returned by is passed as parameter into
164 | ///
165 | /// Directory where to search for files
166 | /// Action to apply for each file found in
167 | /// Action to apply upon entering any directory including
168 | /// If true the search will be recursive and will include subfolders of . Defaults to false
169 | /// Search pattern to apply when searching files, defaults to '*'
170 | /// Search pattern to apply when searching directories, defaults to '*'
171 | public static void ForEachFile(
172 | this IDirectoryInfo info, Action fileAction,
173 | Func directoryAction,
174 | bool recursive = false,
175 | string filesSearchPattern = "*",
176 | string directoriesSearchPattern = "*")
177 | {
178 | info.ThrowIfNotFound();
179 |
180 | var d = directoryAction?.Invoke(info) ?? info;
181 | foreach (var file in info.EnumerateFiles(filesSearchPattern))
182 | {
183 | fileAction.Invoke(file, d);
184 | }
185 |
186 | if (!recursive)
187 | {
188 | return;
189 | }
190 |
191 | foreach (var dir in info.EnumerateDirectories(directoriesSearchPattern))
192 | {
193 | dir.ForEachFile(fileAction, directoryAction, recursive, filesSearchPattern, directoriesSearchPattern);
194 | }
195 | }
196 |
197 | ///
198 | /// Copies files from to
199 | ///
200 | /// Source directory
201 | /// Destination directory
202 | /// If true the copy will be recursive and will include subfolders of . Defaults to false
203 | /// If true the copy will overwrite any existing files in . Defaults to false
204 | /// Search pattern to apply when searching files, defaults to '*'
205 | /// Search pattern to apply when searching directories, defaults to '*'
206 | public static void CopyTo(
207 | this IDirectoryInfo source,
208 | IDirectoryInfo destination,
209 | bool recursive = false,
210 | string filesSearchPattern = "*",
211 | string directoriesSearchPattern = "*",
212 | bool overwrite = false)
213 | {
214 | source.ForEachFile(
215 | (file, destDir) => file.CopyTo(destDir.GetFilePath(file.Name), overwrite),
216 | subDirectory => source.TranslatePaths(subDirectory, destination, true),
217 | recursive,
218 | filesSearchPattern,
219 | directoriesSearchPattern);
220 | }
221 |
222 | private static string[] GetPaths(IDirectoryInfo info, IEnumerable names)
223 | {
224 | return new[] { info.FullName }
225 | .Concat(names.Where(n => !String.IsNullOrEmpty(n)))
226 | .ToArray();
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/IFileInfoAsyncExtensions.cs:
--------------------------------------------------------------------------------
1 | #if NET8_0_OR_GREATER
2 | #nullable enable
3 |
4 | using System.Collections.Generic;
5 | using System.Runtime.CompilerServices;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace System.IO.Abstractions
11 | {
12 | public static class FileInfoFileAsyncExtensions
13 | {
14 | ///
15 | public static async Task AppendAllLinesAsync(this IFileInfo file, IEnumerable contents, CancellationToken cancellationToken = default)
16 | {
17 | await file.FileSystem.File.AppendAllLinesAsync(file.FullName, contents, cancellationToken);
18 | }
19 |
20 | ///
21 | public static async Task AppendAllTextAsync(this IFileInfo file, string? contents, CancellationToken cancellationToken = default)
22 | {
23 | await file.FileSystem.File.AppendAllTextAsync(file.FullName, contents, cancellationToken);
24 | }
25 |
26 | ///
27 | public static async Task ReadAllBytesAsync(this IFileInfo file, CancellationToken cancellationToken = default)
28 | {
29 | return await file.FileSystem.File.ReadAllBytesAsync(file.FullName, cancellationToken);
30 | }
31 |
32 | ///
33 | public static async Task ReadAllLinesAsync(this IFileInfo file, CancellationToken cancellationToken = default)
34 | {
35 | return await file.FileSystem.File.ReadAllLinesAsync(file.FullName, cancellationToken);
36 | }
37 |
38 | ///
39 | public static async Task ReadAllTextAsync(this IFileInfo file, CancellationToken cancellationToken = default)
40 | {
41 | return await file.FileSystem.File.ReadAllTextAsync(file.FullName, cancellationToken);
42 | }
43 |
44 | ///
45 | public static async Task WriteAllBytesAsync(this IFileInfo file, byte[] bytes, CancellationToken cancellationToken = default)
46 | {
47 | await file.FileSystem.File.WriteAllBytesAsync(file.FullName, bytes, cancellationToken);
48 | }
49 |
50 | ///
51 | public static async Task WriteAllLinesAsync(this IFileInfo file, IEnumerable contents, CancellationToken cancellationToken = default)
52 | {
53 | await file.FileSystem.File.WriteAllLinesAsync(file.FullName, contents, cancellationToken);
54 | }
55 |
56 | ///
57 | public static async Task WriteAllTextAsync(this IFileInfo file, string? contents, CancellationToken cancellationToken = default)
58 | {
59 | await file.FileSystem.File.WriteAllTextAsync(file.FullName, contents, cancellationToken);
60 | }
61 |
62 | ///
63 | /// Creates an that can enumerate asynchronously the lines of text in the specified
64 | ///
65 | /// File to enumerate content
66 | ///
67 | /// Returns an to enumerate the content of the file
68 | public static IAsyncEnumerable EnumerateLinesAsync(this IFileInfo file, CancellationToken cancellationToken)
69 | {
70 | return EnumerateLinesAsync(file, null, cancellationToken);
71 | }
72 |
73 | ///
74 | /// Creates an that can enumerate asynchronously the lines of text in the specified
75 | /// using the specified
76 | ///
77 | /// File to enumerate content
78 | /// Encoding to use to read the file
79 | ///
80 | /// Returns an to enumerate the content of the file
81 | public static async IAsyncEnumerable EnumerateLinesAsync(this IFileInfo file, Encoding? encoding, [EnumeratorCancellation] CancellationToken cancellationToken)
82 | {
83 | await using var stream = file.OpenRead();
84 | using var reader = encoding == null
85 | ? new StreamReader(stream)
86 | : new StreamReader(stream, encoding);
87 |
88 | var line = await reader.ReadLineAsync(cancellationToken);
89 | while (line != null)
90 | {
91 | yield return line;
92 | cancellationToken.ThrowIfCancellationRequested();
93 | line = await reader.ReadLineAsync(cancellationToken);
94 | }
95 | }
96 | }
97 | }
98 | #endif
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/IFileInfoExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 |
4 | namespace System.IO.Abstractions
5 | {
6 | public static class IFileInfoExtensions
7 | {
8 | ///
9 | /// Throws an exception if the doesn't exists
10 | ///
11 | /// File that will be checked for existance
12 | /// Exception thrown if the file is not found
13 | public static void ThrowIfNotFound(this IFileInfo file)
14 | {
15 | if (!file.Exists)
16 | throw new FileNotFoundException(StringResources.Format("COULD_NOT_FIND_FILE_EXCEPTION", file.FullName));
17 | }
18 |
19 | ///
20 | /// Creates an that can enumerate the lines of text in the
21 | ///
22 | /// File to enumerate content
23 | /// Returns an to enumerate the content of the file
24 | public static IEnumerable EnumerateLines(this IFileInfo file)
25 | {
26 | return new LineEnumerable(file, null);
27 | }
28 |
29 | ///
30 | /// Creates an that can enumerate the lines of text in the specified
31 | /// using the specified
32 | ///
33 | /// File to enumerate content
34 | /// Encoding to use to read the file
35 | /// Returns an to enumerate the content of the file
36 | public static IEnumerable EnumerateLines(this IFileInfo file, Encoding encoding)
37 | {
38 | return new LineEnumerable(file, encoding);
39 | }
40 |
41 | ///
42 | /// Opens a for the in the specified
43 | ///
44 | /// File to open stream on
45 | /// Mode to use when opening the file
46 | /// A that can read or write data to the specified
47 | public static FileSystemStream OpenFileStream(this IFileInfo file, FileMode mode)
48 | {
49 | return file.FileSystem.FileStream.New(file.FullName, mode);
50 | }
51 |
52 | ///
53 | /// Creates a new empty .
54 | /// If the file already exists, the file is truncated.
55 | ///
56 | /// File to create
57 | /// The original so that methods calls can be chained
58 | public static IFileInfo Truncate(this IFileInfo file)
59 | {
60 | using(var stream = file.OpenFileStream(FileMode.Create))
61 | {
62 | stream.Dispose();
63 | }
64 |
65 | return file;
66 | }
67 |
68 | ///
69 | /// Writes the specified to the specified using the UTF-8 encoding.
70 | /// If the file already exists and the flag is set to true, the file will be truncated.
71 | ///
72 | /// File to write to
73 | /// Lines to write to file as text
74 | /// Flag that specifies if the file can be overwritten if it exists
75 | /// Exception thrown if the file already exists and the flag is set to
76 | public static void WriteLines(this IFileInfo file, IEnumerable lines, bool overwrite = false)
77 | {
78 | using (var stream = file.OpenFileStream(GetWriteFileMode(file, overwrite)))
79 | using (var writer = new StreamWriter(stream))
80 | foreach(var line in lines)
81 | {
82 | writer.WriteLine(line);
83 | }
84 | }
85 |
86 | ///
87 | /// Writes the specified to the specified
88 | /// using the specified .
89 | /// If the file already exists and the flag is set to true, the file will be truncated.
90 | ///
91 | /// File to write to
92 | /// Lines to write to file as text
93 | /// Encoding to use when writing the to the text file
94 | /// Flag that specifies if the file can be overwritten if it exists
95 | /// Exception thrown if the file already exists and the flag is set to
96 | public static void WriteLines(this IFileInfo file, IEnumerable lines, Encoding encoding, bool overwrite = false)
97 | {
98 | using (var stream = file.OpenFileStream(GetWriteFileMode(file, overwrite)))
99 | using (var writer = new StreamWriter(stream, encoding))
100 | foreach (var line in lines)
101 | {
102 | writer.WriteLine(line);
103 | }
104 | }
105 |
106 | ///
107 | /// Appends the specified to the specified
108 | ///
109 | /// File to append to
110 | /// Lines to append to file as text
111 | public static void AppendLines(this IFileInfo file, IEnumerable lines)
112 | {
113 | using (var writer = file.AppendText())
114 | foreach (var line in lines)
115 | {
116 | writer.WriteLine(line);
117 | }
118 | }
119 |
120 | ///
121 | public static void AppendAllLines(this IFileInfo file, IEnumerable contents)
122 | {
123 | file.FileSystem.File.AppendAllLines(file.FullName, contents);
124 | }
125 |
126 | ///
127 | public static void AppendAllText(this IFileInfo file, string contents)
128 | {
129 | file.FileSystem.File.AppendAllText(file.FullName, contents);
130 | }
131 |
132 | ///
133 | public static byte[] ReadAllBytes(this IFileInfo file)
134 | {
135 | return file.FileSystem.File.ReadAllBytes(file.FullName);
136 | }
137 |
138 | ///
139 | public static string[] ReadAllLines(this IFileInfo file)
140 | {
141 | return file.FileSystem.File.ReadAllLines(file.FullName);
142 | }
143 |
144 | ///
145 | public static string ReadAllText(this IFileInfo file)
146 | {
147 | return file.FileSystem.File.ReadAllText(file.FullName);
148 | }
149 |
150 | ///
151 | public static void WriteAllBytes(this IFileInfo file, byte[] bytes)
152 | {
153 | file.FileSystem.File.WriteAllBytes(file.FullName, bytes);
154 | }
155 |
156 | ///
157 | public static void WriteAllLines(this IFileInfo file, IEnumerable contents)
158 | {
159 | file.FileSystem.File.WriteAllLines(file.FullName, contents);
160 | }
161 |
162 | ///
163 | public static void WriteAllText(this IFileInfo file, string contents)
164 | {
165 | file.FileSystem.File.WriteAllText(file.FullName, contents);
166 | }
167 |
168 | private static FileMode GetWriteFileMode(IFileInfo info, bool overwrite)
169 | {
170 | if (!overwrite && info.Exists)
171 | {
172 | throw new IOException(StringResources.Format("CANNOT_OVERWRITE", info.FullName));
173 | }
174 |
175 | //if the file already exists it will be truncated
176 | return FileMode.Create;
177 | }
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/IFileSystemExtensions.cs:
--------------------------------------------------------------------------------
1 | // S3874: "out" and "ref" parameters should not be used
2 | // https://rules.sonarsource.com/csharp/RSPEC-3874/
3 | //
4 | // Our CreateDisposableDirectory / CreateDisposableFile extensions
5 | // intentionally use an out param so that the DirectoryInfo / FileInfo
6 | // is passed out (similar to a Try* method) to the caller while the
7 | // returned object implements IDisposable to leverage the using statement.
8 | #pragma warning disable S3874
9 |
10 | namespace System.IO.Abstractions
11 | {
12 | public static class IFileSystemExtensions
13 | {
14 | ///
15 | /// Get the current directory for the specified
16 | ///
17 | /// FileSystem in use
18 | /// An for the current directory
19 | public static IDirectoryInfo CurrentDirectory(this IFileSystem fileSystem)
20 | {
21 | return fileSystem.DirectoryInfo.New(fileSystem.Directory.GetCurrentDirectory());
22 | }
23 |
24 | ///
25 | /// Creates a new using a random name from the temp path, and returns an
26 | /// that deletes the directory when disposed.
27 | ///
28 | ///
29 | /// The in use.
30 | ///
31 | ///
32 | /// The for the directory that was created.
33 | ///
34 | ///
35 | /// An to manage the directory's lifetime.
36 | ///
37 | public static IDisposable CreateDisposableDirectory(this IFileSystem fileSystem, out IDirectoryInfo directoryInfo)
38 | {
39 | return fileSystem.CreateDisposableDirectory(fileSystem.Path.GetRandomTempPath(), out directoryInfo);
40 | }
41 |
42 | ///
43 | ///
44 | /// Creates a new using a path provided by , and returns an
45 | /// that deletes the directory when disposed.
46 | ///
47 | ///
48 | /// The full path to the directory to create.
49 | ///
50 | ///
51 | /// If the directory already exists.
52 | ///
53 | public static IDisposable CreateDisposableDirectory(this IFileSystem fileSystem, string path, out IDirectoryInfo directoryInfo)
54 | {
55 | return fileSystem.CreateDisposableDirectory(path, dir => new DisposableDirectory(dir), out directoryInfo);
56 | }
57 |
58 | ///
59 | ///
60 | /// Creates a new using a random name from the temp path and returns an
61 | /// created by , that should delete the directory when disposed.
62 | ///
63 | ///
64 | /// A that acts as a factory method. Given the , create the
65 | /// that will manage the its lifetime.
66 | ///
67 | public static T CreateDisposableDirectory(
68 | this IFileSystem fileSystem,
69 | Func disposableFactory,
70 | out IDirectoryInfo directoryInfo) where T : IDisposable
71 | {
72 | return fileSystem.CreateDisposableDirectory(fileSystem.Path.GetRandomTempPath(), disposableFactory, out directoryInfo);
73 | }
74 |
75 | ///
76 | ///
77 | /// Creates a new using a path provided by , and returns an
78 | /// created by , that should delete the directory when disposed.
79 | ///
80 | ///
81 | /// A that acts as a factory method. Given the , create the
82 | /// that will manage the its lifetime.
83 | ///
84 | public static T CreateDisposableDirectory(
85 | this IFileSystem fileSystem,
86 | string path,
87 | Func disposableFactory,
88 | out IDirectoryInfo directoryInfo) where T : IDisposable
89 | {
90 | directoryInfo = fileSystem.DirectoryInfo.New(path);
91 |
92 | if (directoryInfo.Exists)
93 | {
94 | throw CreateAlreadyExistsException(nameof(path), path);
95 | }
96 |
97 | directoryInfo.Create();
98 |
99 | return disposableFactory(directoryInfo);
100 | }
101 |
102 | ///
103 | /// Creates a new using a random name from the temp path, and returns an
104 | /// that deletes the file when disposed.
105 | ///
106 | ///
107 | /// The in use.
108 | ///
109 | ///
110 | /// The for the file that was created.
111 | ///
112 | ///
113 | /// An to manage the file's lifetime.
114 | ///
115 | public static IDisposable CreateDisposableFile(this IFileSystem fileSystem, out IFileInfo fileInfo)
116 | {
117 | return fileSystem.CreateDisposableFile(fileSystem.Path.GetRandomTempPath(), out fileInfo);
118 | }
119 |
120 | ///
121 | ///
122 | /// Creates a new using a path provided by , and returns an
123 | /// that deletes the file when disposed.
124 | ///
125 | ///
126 | /// The full path to the file to create.
127 | ///
128 | ///
129 | /// If the file already exists.
130 | ///
131 | public static IDisposable CreateDisposableFile(this IFileSystem fileSystem, string path, out IFileInfo fileInfo)
132 | {
133 | return fileSystem.CreateDisposableFile(path, file => new DisposableFile(file), out fileInfo);
134 | }
135 |
136 | ///
137 | ///
138 | /// Creates a new using a random name from the temp path and returns an
139 | /// created by , that should delete the file when disposed.
140 | ///
141 | ///
142 | /// A that acts as a factory method. Given the , create the
143 | /// that will manage the its lifetime.
144 | ///
145 | public static T CreateDisposableFile(
146 | this IFileSystem fileSystem,
147 | Func disposableFactory,
148 | out IFileInfo fileInfo) where T : IDisposable
149 | {
150 | return fileSystem.CreateDisposableFile(fileSystem.Path.GetRandomTempPath(), disposableFactory, out fileInfo);
151 | }
152 |
153 | ///
154 | ///
155 | /// Creates a new using a path provided by , and returns an
156 | /// created by , that should delete the file when disposed.
157 | ///
158 | ///
159 | /// A that acts as a factory method. Given the , create the
160 | /// that will manage the its lifetime.
161 | ///
162 | public static T CreateDisposableFile(
163 | this IFileSystem fileSystem,
164 | string path,
165 | Func disposableFactory,
166 | out IFileInfo fileInfo) where T : IDisposable
167 | {
168 | fileInfo = fileSystem.FileInfo.New(path);
169 |
170 | if (fileInfo.Exists)
171 | {
172 | throw CreateAlreadyExistsException(nameof(path), path);
173 | }
174 |
175 | // Ensure we close the handle to the file after we create it, otherwise
176 | // callers may get an access denied error.
177 | fileInfo.Create().Dispose();
178 |
179 | return disposableFactory(fileInfo);
180 | }
181 |
182 | private static string GetRandomTempPath(this IPath path)
183 | {
184 | var temp = path.GetTempPath();
185 | var fileName = path.GetRandomFileName();
186 | return path.Combine(temp, fileName);
187 | }
188 |
189 | private static ArgumentException CreateAlreadyExistsException(string argumentName, string path)
190 | {
191 | // Having the colliding path availabe as part of the exception is very useful for debugging.
192 | // However, paths can be considered sensitive information in some contexts (like web servers).
193 | // Thus, we add the path to the exception's data , rather than the message.
194 | var ex = new ArgumentException("File already exists", argumentName);
195 | ex.Data.Add("path", path);
196 |
197 | return ex;
198 | }
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/LineEnumerator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace System.IO.Abstractions
6 | {
7 | internal sealed class LineEnumerable : IEnumerable
8 | {
9 | private readonly IFileInfo _file;
10 | private readonly Encoding _encoding;
11 |
12 | public LineEnumerable(IFileInfo file, Encoding encoding)
13 | {
14 | _file = file;
15 | _encoding = encoding;
16 | }
17 |
18 | public IEnumerator GetEnumerator() => new LineEnumerator(_file, _encoding);
19 |
20 | IEnumerator IEnumerable.GetEnumerator() => new LineEnumerator(_file, _encoding);
21 | }
22 |
23 | internal sealed class LineEnumerator : IEnumerator
24 | {
25 | private Stream _stream;
26 | private StreamReader _reader;
27 | private string _current;
28 |
29 | public LineEnumerator(IFileInfo file, Encoding encoding)
30 | {
31 | _stream = file.OpenRead();
32 | _reader = encoding == null
33 | ? new StreamReader(_stream)
34 | : new StreamReader(_stream, encoding);
35 | }
36 |
37 | public string Current => _current;
38 |
39 | object IEnumerator.Current => _current;
40 |
41 | public void Dispose()
42 | {
43 | _reader?.Dispose();
44 | _reader = null;
45 | _stream?.Dispose();
46 | _stream = null;
47 | }
48 |
49 | public bool MoveNext()
50 | {
51 | _current = _reader.ReadLine();
52 | return _current != null;
53 | }
54 |
55 | public void Reset()
56 | {
57 | throw new InvalidOperationException();
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace System.IO.Abstractions {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.IO.Abstractions.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to The file '{0}' already exists..
65 | ///
66 | internal static string CANNOT_OVERWRITE {
67 | get {
68 | return ResourceManager.GetString("CANNOT_OVERWRITE", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to Could not find file '{0}'..
74 | ///
75 | internal static string COULD_NOT_FIND_FILE_EXCEPTION {
76 | get {
77 | return ResourceManager.GetString("COULD_NOT_FIND_FILE_EXCEPTION", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to Could not find a part of the path '{0}'..
83 | ///
84 | internal static string COULD_NOT_FIND_PART_OF_PATH_EXCEPTION {
85 | get {
86 | return ResourceManager.GetString("COULD_NOT_FIND_PART_OF_PATH_EXCEPTION", resourceCulture);
87 | }
88 | }
89 |
90 | ///
91 | /// Looks up a localized string similar to '{0}' is not an ancestor of '{1}'..
92 | ///
93 | internal static string NOT_AN_ANCESTOR {
94 | get {
95 | return ResourceManager.GetString("NOT_AN_ANCESTOR", resourceCulture);
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | The file '{0}' already exists.
122 |
123 |
124 | Could not find file '{0}'.
125 |
126 |
127 | Could not find a part of the path '{0}'.
128 |
129 |
130 | '{0}' is not an ancestor of '{1}'.
131 |
132 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/StringResources.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 |
4 | namespace System.IO.Abstractions
5 | {
6 | internal static class StringResources
7 | {
8 | public static ResourceManager Manager { get; } = new ResourceManager(
9 | $"{typeof(StringResources).Namespace}.Resources",
10 | typeof(StringResources).GetTypeInfo().Assembly);
11 |
12 | public static string Format(string name, params object[] objects)
13 | {
14 | return String.Format(Manager.GetString(name), objects);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/System.IO.Abstractions.Extensions/System.IO.Abstractions.Extensions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TestableIO.System.IO.Abstractions.Extensions
5 | net8.0;netstandard2.1;netstandard2.0;net472
6 | Convenience functionalities on top of System.IO.Abstractions
7 | System.IO.Abstractions
8 | $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/../../ReleaseNotes.md"))
9 | 9.0
10 | README.md
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | True
25 | True
26 | Resources.resx
27 |
28 |
29 |
30 |
31 |
32 | ResXFileCodeGenerator
33 | Resources.Designer.cs
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/tests/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 |
10 | true
11 |
12 |
--------------------------------------------------------------------------------
/tests/System.IO.Abstractions.Extensions.Tests/DirectoryInfoExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System.Collections.Generic;
3 | using Assert = NUnit.Framework.Legacy.ClassicAssert;
4 |
5 | namespace System.IO.Abstractions.Extensions.Tests
6 | {
7 | [TestFixture]
8 | public class DirectoryInfoExtensionsTests
9 | {
10 | [Test]
11 | public void SubDirectory_Extension_Test()
12 | {
13 | //arrange
14 | var fs = new FileSystem();
15 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
16 | var guid = Guid.NewGuid().ToString();
17 | var expectedPath = fs.Path.Combine(current.FullName, guid);
18 |
19 | //make sure directory doesn't exists
20 | Assert.IsFalse(fs.Directory.Exists(expectedPath));
21 |
22 | //create directory
23 | var created = current.SubDirectory(guid);
24 | created.Create();
25 |
26 | //assert it exists
27 | Assert.IsTrue(fs.Directory.Exists(expectedPath));
28 | Assert.AreEqual(expectedPath, created.FullName);
29 |
30 | //delete directory
31 | created.Delete();
32 | Assert.IsFalse(fs.Directory.Exists(expectedPath));
33 | }
34 |
35 | [TestCase("test1", "test2")]
36 | [TestCase("test1", "", "test2")]
37 | [TestCase("test1", null, "test2")]
38 | public void SubDirectoryWithParams_Extension_Test(params string[] subFolders)
39 | {
40 | //arrange
41 | var fs = new FileSystem();
42 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
43 | var expectedPath = fs.Path.Combine(current.FullName, "test1", "test2");
44 |
45 | //make sure directory doesn't exists
46 | Assert.IsFalse(fs.Directory.Exists(expectedPath));
47 |
48 | //create directory
49 | var created = current.SubDirectory(subFolders);
50 | created.Create();
51 |
52 | //assert it exists
53 | Assert.IsTrue(fs.Directory.Exists(expectedPath));
54 | Assert.AreEqual(expectedPath, created.FullName);
55 |
56 | //delete directory
57 | created.Delete();
58 | Assert.IsFalse(fs.Directory.Exists(expectedPath));
59 | }
60 |
61 | [TestCase("test1", "test2")]
62 | [TestCase("test1", "", "test2")]
63 | [TestCase("test1", null, "test2")]
64 | public void SubDirectoryWithIEnumerable_Extension_Test(params string[] names)
65 | {
66 | //arrange
67 | var fs = new FileSystem();
68 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
69 | var expectedPath = fs.Path.Combine(current.FullName, "test1", "test2");
70 |
71 | //make sure directory doesn't exists
72 | Assert.IsFalse(fs.Directory.Exists(expectedPath));
73 |
74 | //create directory
75 | var list = new List(names);
76 | var created = current.SubDirectory(list);
77 | created.Create();
78 |
79 | //assert it exists
80 | Assert.IsTrue(fs.Directory.Exists(expectedPath));
81 | Assert.AreEqual(expectedPath, created.FullName);
82 |
83 | //delete directory
84 | created.Delete();
85 | Assert.IsFalse(fs.Directory.Exists(expectedPath));
86 | }
87 |
88 | [Test]
89 | public void File_Extension_Test()
90 | {
91 | //arrange
92 | var fs = new FileSystem();
93 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
94 | var guid = Guid.NewGuid().ToString();
95 | var expectedPath = fs.Path.Combine(current.FullName, guid);
96 |
97 | //make sure file doesn't exists
98 | Assert.IsFalse(fs.File.Exists(expectedPath));
99 |
100 | //create file
101 | var created = current.File(guid);
102 | using (var stream = created.Create())
103 | {
104 | stream.Dispose();
105 | }
106 |
107 | //assert it exists
108 | Assert.IsTrue(fs.File.Exists(expectedPath));
109 | Assert.AreEqual(expectedPath, created.FullName);
110 |
111 | //delete file
112 | created.Delete();
113 | Assert.IsFalse(fs.File.Exists(expectedPath));
114 | }
115 |
116 | [TestCase("test1", "test2", "test.txt")]
117 | [TestCase("test1", "", "test2", "test.txt")]
118 | [TestCase("test1", null, "test2", "test.txt")]
119 |
120 | public void FileWithParams_Extension_Test(params string[] names)
121 | {
122 | //arrange
123 | var fs = new FileSystem();
124 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
125 | var expectedPath = fs.Path.Combine(current.FullName, "test1", "test2", "test.txt");
126 |
127 | //make sure file doesn't exists
128 | Assert.IsFalse(fs.File.Exists(expectedPath));
129 |
130 | //act, create file
131 | var created = current.File(names);
132 | created.Directory.Create();
133 | using (var stream = created.Create())
134 | {
135 | stream.Dispose();
136 | }
137 |
138 | //assert it exists
139 | Assert.IsTrue(fs.File.Exists(expectedPath));
140 | Assert.AreEqual(expectedPath, created.FullName);
141 |
142 | //delete file
143 | created.Delete();
144 | created.Directory.Delete();
145 | Assert.IsFalse(fs.File.Exists(expectedPath));
146 | }
147 |
148 | [Test]
149 | public void ThrowIfNotFound_IfDirectoryDoesNotExists_ThrowsException()
150 | {
151 | //arrange
152 | var fs = new FileSystem();
153 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
154 | var guid = Guid.NewGuid().ToString();
155 | var directory = current.SubDirectory(guid);
156 |
157 | //act
158 | var exception = Assert.Throws(() => directory.ThrowIfNotFound());
159 |
160 | //assert
161 | Assert.IsTrue(exception.Message.Contains(directory.FullName));
162 | }
163 |
164 | [Test]
165 | public void ThrowIfNotFound_IfDirectoryExists_DoesNotThrowException()
166 | {
167 | //arrange
168 | var fs = new FileSystem();
169 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
170 | var guid = Guid.NewGuid().ToString();
171 | var directory = current.SubDirectory(guid);
172 |
173 | //act
174 | directory.Create();
175 | directory.ThrowIfNotFound();
176 |
177 | //assert
178 | Assert.IsTrue(directory.Exists);
179 |
180 | //cleanup
181 | directory.Delete();
182 | }
183 |
184 | [Test]
185 | public void CopyTo_NonRecursiveWithSubfolder_DoesNotCopySubfolder()
186 | {
187 | //arrange
188 | var fs = new FileSystem();
189 | var workingDir = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory()).CreateSubdirectory(Guid.NewGuid().ToString());
190 |
191 | //create directories
192 | var source = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir"));
193 | var sourceSubDir = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir", "SubDir"));
194 | var dest = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "DestDir"));
195 |
196 | source.Create();
197 | sourceSubDir.Create();
198 |
199 | //make sure everything is set up as expected
200 | Assert.IsTrue(fs.Directory.Exists(source.FullName));
201 | Assert.IsTrue(fs.Directory.Exists(sourceSubDir.FullName));
202 | Assert.IsFalse(fs.Directory.Exists(dest.FullName));
203 |
204 | //act
205 | source.CopyTo(dest);
206 |
207 | var destSubDir = fs.DirectoryInfo.New(fs.Path.Combine(dest.FullName, "SubDir"));
208 | Assert.IsTrue(fs.Directory.Exists(dest.FullName));
209 | Assert.IsFalse(fs.Directory.Exists(destSubDir.FullName));
210 |
211 | //cleanup
212 | workingDir.Delete(recursive: true);
213 |
214 | Assert.IsFalse(fs.File.Exists(workingDir.FullName));
215 | }
216 |
217 | [Test]
218 | public void CopyTo_NonRecursiveWithSubfolderWithFiles_DoesNotCopySubfolderAndSubfolderFiles()
219 | {
220 | //arrange
221 | var fs = new FileSystem();
222 | var workingDir = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory()).CreateSubdirectory(Guid.NewGuid().ToString());
223 |
224 | //create directories
225 | var source = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir"));
226 | var sourceSubDir = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir", "SubDir"));
227 | var dest = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "DestDir"));
228 |
229 | source.Create();
230 | sourceSubDir.Create();
231 |
232 | //create files
233 | var sourceFile = fs.FileInfo.New(fs.Path.Combine(source.FullName, "file.txt"));
234 | var sourceSubDirFile = fs.FileInfo.New(fs.Path.Combine(sourceSubDir.FullName, "file.txt"));
235 |
236 | sourceFile.Create().Dispose();
237 | sourceSubDirFile.Create().Dispose();
238 |
239 | //make sure everything is set up as expected
240 | Assert.IsTrue(fs.Directory.Exists(source.FullName));
241 | Assert.IsTrue(fs.Directory.Exists(sourceSubDir.FullName));
242 | Assert.IsTrue(fs.File.Exists(sourceFile.FullName));
243 | Assert.IsTrue(fs.File.Exists(sourceSubDirFile.FullName));
244 | Assert.IsFalse(fs.Directory.Exists(dest.FullName));
245 |
246 | //act
247 | source.CopyTo(dest);
248 |
249 | var destFile = fs.FileInfo.New(fs.Path.Combine(dest.FullName, "file.txt"));
250 | var destSubDir = fs.DirectoryInfo.New(fs.Path.Combine(dest.FullName, "SubDir"));
251 | var destSubDirFile = fs.FileInfo.New(fs.Path.Combine(destSubDir.FullName, "file.txt"));
252 | Assert.IsTrue(fs.Directory.Exists(dest.FullName));
253 | Assert.IsTrue(fs.File.Exists(destFile.FullName));
254 | Assert.IsFalse(fs.Directory.Exists(destSubDir.FullName));
255 | Assert.IsFalse(fs.File.Exists(destSubDirFile.FullName));
256 |
257 | //cleanup
258 | workingDir.Delete(recursive: true);
259 |
260 | Assert.IsFalse(fs.File.Exists(workingDir.FullName));
261 | }
262 |
263 | [Test]
264 | public void CopyTo_RecursiveWithSubfolder_DoesCopySubfolder()
265 | {
266 | //arrange
267 | var fs = new FileSystem();
268 | var workingDir = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory()).CreateSubdirectory(Guid.NewGuid().ToString());
269 |
270 | //create directories
271 | var source = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir"));
272 | var sourceSubDir = fs.DirectoryInfo.New(fs.Path.Combine(source.FullName, "SubDir"));
273 | var dest = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "DestDir"));
274 |
275 | source.Create();
276 | sourceSubDir.Create();
277 |
278 | //make sure everything is set up as expected
279 | Assert.IsTrue(fs.Directory.Exists(source.FullName));
280 | Assert.IsTrue(fs.Directory.Exists(sourceSubDir.FullName));
281 | Assert.IsFalse(fs.Directory.Exists(dest.FullName));
282 |
283 | //act
284 | source.CopyTo(dest, recursive: true);
285 |
286 | var destSubDir = fs.DirectoryInfo.New(fs.Path.Combine(dest.FullName, "SubDir"));
287 | Assert.IsTrue(fs.Directory.Exists(dest.FullName));
288 | Assert.IsTrue(fs.Directory.Exists(destSubDir.FullName));
289 |
290 | //cleanup
291 | workingDir.Delete(recursive: true);
292 |
293 | Assert.IsFalse(fs.File.Exists(workingDir.FullName));
294 | }
295 |
296 | [Test]
297 | public void CopyTo_RecursiveWithSubfolderWithFiles_DoesCopySubfolderAndSubfolderFiles()
298 | {
299 | //arrange
300 | var fs = new FileSystem();
301 | var workingDir = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory()).CreateSubdirectory(Guid.NewGuid().ToString());
302 |
303 | //create directories
304 | var source = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir"));
305 | var sourceSubDir = fs.DirectoryInfo.New(fs.Path.Combine(source.FullName, "SubDir"));
306 | var dest = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "DestDir"));
307 |
308 | source.Create();
309 | sourceSubDir.Create();
310 |
311 | //create files
312 | var sourceFile = fs.FileInfo.New(fs.Path.Combine(source.FullName, "file.txt"));
313 | var sourceSubDirFile = fs.FileInfo.New(fs.Path.Combine(sourceSubDir.FullName, "file.txt"));
314 |
315 | sourceFile.Create().Dispose();
316 | sourceSubDirFile.Create().Dispose();
317 |
318 | //make sure everything is set up as expected
319 | Assert.IsTrue(fs.Directory.Exists(source.FullName));
320 | Assert.IsTrue(fs.Directory.Exists(sourceSubDir.FullName));
321 | Assert.IsTrue(fs.File.Exists(sourceFile.FullName));
322 | Assert.IsTrue(fs.File.Exists(sourceSubDirFile.FullName));
323 | Assert.IsFalse(fs.Directory.Exists(dest.FullName));
324 |
325 | //act
326 | source.CopyTo(dest, recursive: true);
327 |
328 | var destFile = fs.FileInfo.New(fs.Path.Combine(dest.FullName, "file.txt"));
329 | var destSubDir = fs.DirectoryInfo.New(fs.Path.Combine(dest.FullName, "SubDir"));
330 | var destSubDirFile = fs.FileInfo.New(fs.Path.Combine(destSubDir.FullName, "file.txt"));
331 | Assert.IsTrue(fs.Directory.Exists(dest.FullName));
332 | Assert.IsTrue(fs.File.Exists(destFile.FullName));
333 | Assert.IsTrue(fs.Directory.Exists(destSubDir.FullName));
334 | Assert.IsTrue(fs.File.Exists(destSubDirFile.FullName));
335 |
336 | //cleanup
337 | workingDir.Delete(recursive: true);
338 |
339 | Assert.IsFalse(fs.File.Exists(workingDir.FullName));
340 | }
341 |
342 | [Test]
343 | public void CopyTo_SourceDirDoesNotExists_ThrowsDirectoryNotFoundException()
344 | {
345 | //arrange
346 | var fs = new FileSystem();
347 | var workingDir = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory()).CreateSubdirectory(Guid.NewGuid().ToString());
348 |
349 | //create directories
350 | var source = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir"));
351 | var dest = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "DestDir"));
352 |
353 | //make sure everything is set up as expected
354 | Assert.IsFalse(fs.Directory.Exists(source.FullName));
355 | Assert.IsFalse(fs.Directory.Exists(dest.FullName));
356 |
357 | //act
358 | Assert.That(() => source.CopyTo(dest), Throws.Exception.TypeOf().And.Message.Contains(source.FullName));
359 |
360 | Assert.IsFalse(fs.File.Exists(source.FullName));
361 | Assert.IsFalse(fs.File.Exists(dest.FullName));
362 | }
363 |
364 | [Test]
365 | public void CopyTo_TargetDirAndParentDoesNotExist_CreatesTargetDirectoryHierarchy()
366 | {
367 | //arrange
368 | var fs = new FileSystem();
369 | var workingDir = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory()).CreateSubdirectory(Guid.NewGuid().ToString());
370 |
371 | //create directories
372 | var source = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir"));
373 | var dest = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "ParentDir", "DestDir"));
374 |
375 | source.Create();
376 |
377 | //make sure everything is set up as expected
378 | Assert.IsTrue(fs.Directory.Exists(source.FullName));
379 | Assert.IsFalse(fs.Directory.Exists(dest.FullName));
380 |
381 | //act
382 | source.CopyTo(dest);
383 |
384 | //assert
385 | Assert.IsTrue(fs.Directory.Exists(dest.FullName));
386 |
387 | //cleanup
388 | workingDir.Delete(recursive: true);
389 |
390 | Assert.IsFalse(fs.File.Exists(workingDir.FullName));
391 | }
392 |
393 | [Test]
394 | public void CopyTo_Overwrite_OverwritesWhenSet()
395 | {
396 | //arrange
397 | var fs = new FileSystem();
398 | var workingDir = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory()).CreateSubdirectory(Guid.NewGuid().ToString());
399 |
400 | //create directories
401 | var source = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir"));
402 | var dest = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "DestDir"));
403 |
404 | source.Create();
405 | dest.Create();
406 |
407 | //create files
408 | var sourceFile = fs.FileInfo.New(fs.Path.Combine(source.FullName, "file.txt"));
409 | var destFile = fs.FileInfo.New(fs.Path.Combine(dest.FullName, "file.txt"));
410 |
411 | var sourceFileContent = new[] { nameof(sourceFile) };
412 | sourceFile.WriteLines(sourceFileContent);
413 | var destFileContent = new[] { nameof(destFile) };
414 | destFile.WriteLines(destFileContent);
415 |
416 | //make sure everything is set up as expected
417 | Assert.IsTrue(fs.Directory.Exists(source.FullName));
418 | Assert.IsTrue(fs.File.Exists(sourceFile.FullName));
419 | Assert.AreEqual(fs.File.ReadAllLines(sourceFile.FullName), sourceFileContent);
420 | Assert.IsTrue(fs.Directory.Exists(dest.FullName));
421 | Assert.IsTrue(fs.File.Exists(destFile.FullName));
422 | Assert.AreEqual(fs.File.ReadAllLines(destFile.FullName), destFileContent);
423 |
424 | //act
425 | source.CopyTo(dest, overwrite: true);
426 |
427 | //assert
428 | Assert.AreEqual(fs.File.ReadAllLines(destFile.FullName), sourceFileContent);
429 |
430 | //cleanup
431 | workingDir.Delete(recursive: true);
432 |
433 | Assert.IsFalse(fs.File.Exists(workingDir.FullName));
434 | }
435 |
436 | [Test]
437 | public void CopyTo_Overwrite_DoesNotOverwritesWhenNotSet()
438 | {
439 | //arrange
440 | var fs = new FileSystem();
441 | var workingDir = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory()).CreateSubdirectory(Guid.NewGuid().ToString());
442 |
443 | //create directories
444 | var source = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "SourceDir"));
445 | var dest = fs.DirectoryInfo.New(fs.Path.Combine(workingDir.FullName, "DestDir"));
446 |
447 | source.Create();
448 | dest.Create();
449 |
450 | //create files
451 | var sourceFile = fs.FileInfo.New(fs.Path.Combine(source.FullName, "file.txt"));
452 | var destFile = fs.FileInfo.New(fs.Path.Combine(dest.FullName, "file.txt"));
453 |
454 | var sourceFileContent = new[] { nameof(sourceFile) };
455 | sourceFile.WriteLines(sourceFileContent);
456 | var destFileContent = new[] { nameof(destFile) };
457 | destFile.WriteLines(destFileContent);
458 |
459 | //make sure everything is set up as expected
460 | Assert.IsTrue(fs.Directory.Exists(source.FullName));
461 | Assert.IsTrue(fs.File.Exists(sourceFile.FullName));
462 | Assert.AreEqual(fs.File.ReadAllLines(sourceFile.FullName), sourceFileContent);
463 | Assert.IsTrue(fs.Directory.Exists(dest.FullName));
464 | Assert.IsTrue(fs.File.Exists(destFile.FullName));
465 | Assert.AreEqual(fs.File.ReadAllLines(destFile.FullName), destFileContent);
466 |
467 | //act
468 | Assert.That(() => source.CopyTo(dest, overwrite: false), Throws.Exception.TypeOf().And.Message.Contains(destFile.FullName));
469 |
470 | //assert
471 | Assert.AreEqual(fs.File.ReadAllLines(destFile.FullName), destFileContent);
472 |
473 | //cleanup
474 | workingDir.Delete(recursive: true);
475 |
476 | Assert.IsFalse(fs.File.Exists(workingDir.FullName));
477 | }
478 | }
479 | }
480 |
--------------------------------------------------------------------------------
/tests/System.IO.Abstractions.Extensions.Tests/DisposableDirectoryTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Assert = NUnit.Framework.Legacy.ClassicAssert;
3 |
4 | namespace System.IO.Abstractions.Extensions.Tests
5 | {
6 | [TestFixture]
7 | public class DisposableDirectoryTests
8 | {
9 | [Test]
10 | public void DisposableDirectory_Throws_ArgumentNullException_For_Null_IDirectoryInfo_Test()
11 | {
12 | Assert.Throws(() => new DisposableDirectory(null));
13 | }
14 |
15 | [Test]
16 | public void DisposableDirectory_DeleteRecursive_On_Dispose_Test()
17 | {
18 | // Arrange
19 | var fs = new FileSystem();
20 | var path = fs.Path.Combine(fs.Directory.GetCurrentDirectory(), fs.Path.GetRandomFileName());
21 | var dirInfo = fs.DirectoryInfo.New(path);
22 |
23 | // Create a subdirectory to ensure recursive delete
24 | dirInfo.CreateSubdirectory(Guid.NewGuid().ToString());
25 |
26 | // Assert directory exists
27 | Assert.IsTrue(fs.Directory.Exists(path), "Directory should exist");
28 | Assert.IsTrue(dirInfo.Exists, "IDirectoryInfo.Exists should be true");
29 |
30 | // Act
31 | var disposableDirectory = new DisposableDirectory(dirInfo);
32 | disposableDirectory.Dispose();
33 |
34 | // Assert directory is deleted
35 | Assert.IsFalse(fs.Directory.Exists(path), "Directory should not exist");
36 | Assert.IsFalse(dirInfo.Exists, "IDirectoryInfo.Exists should be false");
37 |
38 | // Assert a second dispose does not throw
39 | Assert.DoesNotThrow(() => disposableDirectory.Dispose());
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/System.IO.Abstractions.Extensions.Tests/DisposableFileTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Assert = NUnit.Framework.Legacy.ClassicAssert;
3 |
4 | namespace System.IO.Abstractions.Extensions.Tests
5 | {
6 | [TestFixture]
7 | public class DisposableFileTests
8 | {
9 | [Test]
10 | public void DisposableFile_Throws_ArgumentNullException_For_Null_IFileInfo_Test()
11 | {
12 | Assert.Throws(() => new DisposableFile(null));
13 | }
14 |
15 | [Test]
16 | public void DisposableFile_Delete_On_Dispose_Test()
17 | {
18 | // Arrange
19 | var fs = new FileSystem();
20 | var path = fs.Path.Combine(fs.Directory.GetCurrentDirectory(), fs.Path.GetRandomFileName());
21 | var fileInfo = fs.FileInfo.New(path);
22 | fileInfo.Create().Dispose();
23 |
24 | // Assert file exists
25 | Assert.IsTrue(fs.File.Exists(path), "File exists");
26 | Assert.IsTrue(fileInfo.Exists, "IFileInfo.Exists should be true");
27 |
28 | // Act
29 | var disposableFile = new DisposableFile(fileInfo);
30 | disposableFile.Dispose();
31 |
32 | // Assert directory is deleted
33 | Assert.IsFalse(fs.File.Exists(path), "File does not exist");
34 | Assert.IsFalse(fileInfo.Exists, "IFileInfo.Exists should be false");
35 |
36 | // Assert a second dispose does not throw
37 | Assert.DoesNotThrow(() => disposableFile.Dispose());
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/System.IO.Abstractions.Extensions.Tests/FileInfoExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System.Linq;
3 | using System.Text;
4 | using Assert = NUnit.Framework.Legacy.ClassicAssert;
5 |
6 | namespace System.IO.Abstractions.Extensions.Tests
7 | {
8 | [TestFixture]
9 | public class FileInfoExtensionsTests
10 | {
11 | [Test]
12 | public void ThrowIfNotFound_IfFileDoesNotExists_ThrowsException()
13 | {
14 | //arrange
15 | var fs = new FileSystem();
16 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
17 | var guid = Guid.NewGuid().ToString();
18 | var file = current.File(guid);
19 |
20 | //act
21 | var exception = Assert.Throws(() => file.ThrowIfNotFound());
22 |
23 | //assert
24 | Assert.IsTrue(exception.Message.Contains(file.FullName));
25 | }
26 |
27 | [Test]
28 | public void ThrowIfNotFound_IfFileExists_DoesNotThrowException()
29 | {
30 | //arrange
31 | var fs = new FileSystem();
32 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
33 | var guid = Guid.NewGuid().ToString();
34 | var file = current.File(guid);
35 |
36 | //act
37 | file.Truncate();
38 | file.ThrowIfNotFound();
39 |
40 | //assert
41 | Assert.IsTrue(file.Exists);
42 |
43 | //cleanup
44 | file.Delete();
45 | }
46 |
47 | [Test]
48 | public void Truncate_AnExistingFileWithContent_FileExistsAndIsEmpty()
49 | {
50 | //arrange
51 | var fs = new FileSystem();
52 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
53 | var guid = Guid.NewGuid().ToString();
54 | var file = current.File(guid);
55 | //create file
56 | using (var stream = file.OpenWrite())
57 | using (var writer = new StreamWriter(stream, Encoding.UTF8))
58 | {
59 | writer.WriteLine("test");
60 | }
61 | file.Refresh();
62 | Assert.IsTrue(file.Exists);
63 | Assert.IsTrue(file.Length >= 4);
64 |
65 | //act
66 | file.Truncate();
67 |
68 | //assert
69 | file.Refresh();
70 | Assert.AreEqual(0, file.Length);
71 | }
72 |
73 | [Test]
74 | public void Truncate_ANewFile_FileExistsAndIsEmpty()
75 | {
76 | //arrange
77 | var fs = new FileSystem();
78 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
79 | var guid = Guid.NewGuid().ToString();
80 | var file = current.File(guid);
81 | Assert.IsFalse(file.Exists);
82 |
83 | //act
84 | file.Truncate();
85 |
86 | //assert
87 | file.Refresh();
88 | Assert.AreEqual(0, file.Length);
89 | }
90 |
91 | [TestCase("line1", "line2", "line3")]
92 | [TestCase("line1", "", "line3")]
93 | public void EnumerateLines_ReadFromExistingFile_ReturnsLines(params string[] content)
94 | {
95 | //arrange
96 | var fs = new FileSystem();
97 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
98 | var guid = Guid.NewGuid().ToString();
99 | var file = current.File(guid);
100 | //create file
101 | using (var stream = file.OpenWrite())
102 | using (var writer = new StreamWriter(stream, Encoding.UTF8))
103 | {
104 | foreach (var line in content)
105 | writer.WriteLine(line);
106 | }
107 |
108 | //act
109 | var actual = file.EnumerateLines().ToArray();
110 |
111 | //assert
112 | Assert.AreEqual(content.Length, actual.Length);
113 | for (int i = 0; i < content.Length; i++)
114 | {
115 | Assert.AreEqual(content[i], actual[i]);
116 | }
117 | }
118 |
119 | [TestCase("line1", "line2", "line3")]
120 | [TestCase("line1", "", "line3")]
121 | public void WriteLines_WriteLinesToNewFile_LinesAreWritten(params string[] content)
122 | {
123 | //arrange
124 | var fs = new FileSystem();
125 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
126 | var guid = Guid.NewGuid().ToString();
127 | var file = current.File(guid);
128 |
129 | //act
130 | Assert.IsFalse(file.Exists);
131 | file.WriteLines(content);
132 | var actual = file.EnumerateLines().ToArray();
133 |
134 | //assert
135 | Assert.AreEqual(content.Length, actual.Length);
136 | for (int i = 0; i < content.Length; i++)
137 | {
138 | Assert.AreEqual(content[i], actual[i]);
139 | }
140 | }
141 |
142 | [TestCase("line1", "line2", "line3")]
143 | [TestCase("line1", "", "line3")]
144 | public void WriteLines_WriteLinesToExistingFileWithOverwriteDisabled_ThrowsIOException(params string[] content)
145 | {
146 | //arrange
147 | var fs = new FileSystem();
148 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
149 | var guid = Guid.NewGuid().ToString();
150 | var file = current.File(guid);
151 | file.Truncate();
152 |
153 | //act & assert
154 | Assert.IsTrue(file.Exists);
155 | //call WriteLines with both overwrite parameter ommitted or set to false
156 | var ex1 = Assert.Throws(() => file.WriteLines(content));
157 | var ex2 = Assert.Throws(() => file.WriteLines(content, false));
158 |
159 | Assert.IsTrue(ex1.Message.Contains(file.FullName));
160 | Assert.IsTrue(ex2.Message.Contains(file.FullName));
161 | }
162 |
163 | [TestCase("line1", "line2", "line3")]
164 | [TestCase("line1", "", "line3")]
165 | public void WriteLines_WriteLinesToExistingFileWithOverwriteEnabled_FileIsTruncatedAndLinesAreWritten(params string[] content)
166 | {
167 | //arrange
168 | var fs = new FileSystem();
169 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
170 | var guid = Guid.NewGuid().ToString();
171 | var file = current.File(guid);
172 |
173 | //create file with long content
174 | var data = Encoding.UTF8.GetBytes("line5 line4 line3 line2 line1");
175 | using (var stream = file.OpenWrite())
176 | {
177 | stream.Write(data, 0, data.Length);
178 | stream.Dispose();
179 | }
180 |
181 | //act
182 | Assert.IsTrue(file.Exists);
183 | file.WriteLines(content, overwrite: true);
184 | var actual = file.EnumerateLines().ToArray();
185 |
186 | //assert
187 | Assert.AreEqual(content.Length, actual.Length);
188 | for (int i = 0; i < content.Length; i++)
189 | {
190 | Assert.AreEqual(content[i], actual[i]);
191 | }
192 | }
193 |
194 | [TestCase("line1", "line2", "line3")]
195 | [TestCase("line1", "", "line3")]
196 | public void WriteLinesWithUTF16Encoding_WriteLinesToNewFile_LinesAreWritten(params string[] content)
197 | {
198 | //arrange
199 | var fs = new FileSystem();
200 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
201 | var guid = Guid.NewGuid().ToString();
202 | var file = current.File(guid);
203 |
204 | //act
205 | Assert.IsFalse(file.Exists);
206 | file.WriteLines(content, Encoding.Unicode);
207 | var actual = file.EnumerateLines(Encoding.Unicode).ToArray();
208 |
209 | //assert
210 | Assert.AreEqual(content.Length, actual.Length);
211 | for (int i = 0; i < content.Length; i++)
212 | {
213 | Assert.AreEqual(content[i], actual[i]);
214 | }
215 | }
216 |
217 | [TestCase("line1", "line2", "line3")]
218 | [TestCase("line1", "", "line3")]
219 | public void WriteLinesWithUTF16Encoding_WriteLinesToExistingFileWithOverwriteDisabled_ThrowsIOException(params string[] content)
220 | {
221 | //arrange
222 | var fs = new FileSystem();
223 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
224 | var guid = Guid.NewGuid().ToString();
225 | var file = current.File(guid);
226 | file.Truncate();
227 |
228 | //act & assert
229 | Assert.IsTrue(file.Exists);
230 | //call WriteLines with both overwrite parameter ommitted or set to false
231 | var ex1 = Assert.Throws(() => file.WriteLines(content, Encoding.Unicode));
232 | var ex2 = Assert.Throws(() => file.WriteLines(content, Encoding.Unicode, false));
233 |
234 | Assert.IsTrue(ex1.Message.Contains(file.FullName));
235 | Assert.IsTrue(ex2.Message.Contains(file.FullName));
236 | }
237 |
238 | [TestCase("line1", "line2", "line3")]
239 | [TestCase("line1", "", "line3")]
240 | public void WriteLinesWithUTF16Encoding_WriteLinesToExistingFileWithOverwriteEnabled_FileIsTruncatedAndLinesAreWritten(params string[] content)
241 | {
242 | //arrange
243 | var fs = new FileSystem();
244 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
245 | var guid = Guid.NewGuid().ToString();
246 | var file = current.File(guid);
247 |
248 | //create file with long content
249 | var data = Encoding.Unicode.GetBytes("line5 line4 line3 line2 line1");
250 | using (var stream = file.OpenWrite())
251 | {
252 | stream.Write(data, 0, data.Length);
253 | stream.Dispose();
254 | }
255 |
256 | //act
257 | Assert.IsTrue(file.Exists);
258 | file.WriteLines(content, Encoding.Unicode, overwrite: true);
259 | var actual = file.EnumerateLines(Encoding.Unicode).ToArray();
260 |
261 | //assert
262 | Assert.AreEqual(content.Length, actual.Length);
263 | for (int i = 0; i < content.Length; i++)
264 | {
265 | Assert.AreEqual(content[i], actual[i]);
266 | }
267 | }
268 |
269 | [TestCase("line1", "line2", "line3")]
270 | [TestCase("line1", "", "line3")]
271 | public void AppendText_FileExistsAndHasText_LinesAreAppended(params string[] append)
272 | {
273 | //arrange
274 | var initial = new[] { "test1", "test2", "test3" };
275 | var fs = new FileSystem();
276 | var current = fs.DirectoryInfo.New(fs.Directory.GetCurrentDirectory());
277 | var guid = Guid.NewGuid().ToString();
278 | var file = current.File(guid);
279 | file.WriteLines(initial);
280 |
281 | //act
282 | file.AppendLines(append);
283 |
284 | //assert
285 | var expected = initial.Concat(append).ToArray();
286 | var actual = file.EnumerateLines().ToArray();
287 |
288 | Assert.AreEqual(expected.Length, actual.Length);
289 | for (int i = 0; i < expected.Length; i++)
290 | {
291 | Assert.AreEqual(expected[i], actual[i]);
292 | }
293 | }
294 | }
295 | }
--------------------------------------------------------------------------------
/tests/System.IO.Abstractions.Extensions.Tests/FileSystemExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Assert = NUnit.Framework.Legacy.ClassicAssert;
3 |
4 | namespace System.IO.Abstractions.Extensions.Tests
5 | {
6 | [TestFixture]
7 | public class FileSystemExtensionsTests
8 | {
9 | private class CustomDisposableDirectory : DisposableDirectory
10 | {
11 | public bool DeleteFileSystemInfoWasCalled { get; private set; }
12 |
13 | public CustomDisposableDirectory(IDirectoryInfo directoryInfo) : base(directoryInfo)
14 | {
15 | }
16 |
17 | protected override void DeleteFileSystemInfo()
18 | {
19 | DeleteFileSystemInfoWasCalled = true;
20 | base.DeleteFileSystemInfo();
21 | }
22 | }
23 |
24 | private class CustomDisposableFile : DisposableFile
25 | {
26 | public bool DeleteFileSystemInfoWasCalled { get; private set; }
27 |
28 | public CustomDisposableFile(IFileInfo fileInfo) : base(fileInfo)
29 | {
30 | }
31 |
32 | protected override void DeleteFileSystemInfo()
33 | {
34 | DeleteFileSystemInfoWasCalled = true;
35 | base.DeleteFileSystemInfo();
36 | }
37 | }
38 |
39 | [Test]
40 | public void CurrentDirectoryTest()
41 | {
42 | var fs = new FileSystem();
43 | var fullName = fs.CurrentDirectory().FullName;
44 |
45 | Assert.IsFalse(String.IsNullOrWhiteSpace(fullName));
46 | NUnit.Framework.Assert.That(fullName, Is.EqualTo(Environment.CurrentDirectory));
47 | }
48 |
49 | [Test]
50 | public void CreateDisposableDirectory_Temp_Path_Test()
51 | {
52 | // Arrange
53 | var fs = new FileSystem();
54 | string path;
55 |
56 | // Act
57 | using (_ = fs.CreateDisposableDirectory(out var dir))
58 | {
59 | path = dir.FullName;
60 |
61 | Assert.IsTrue(dir.Exists, "Directory should exist");
62 | Assert.IsTrue(
63 | path.StartsWith(fs.Path.GetTempPath(), StringComparison.Ordinal),
64 | "Directory should be in temp path");
65 | }
66 |
67 | // Assert directory is deleted
68 | Assert.IsFalse(fs.Directory.Exists(path), "Directory should not exist");
69 | }
70 |
71 | [Test]
72 | public void CreateDisposableDirectory_Already_Exists_Test()
73 | {
74 | // Arrange
75 | var fs = new FileSystem();
76 | var path = fs.Path.Combine(fs.Path.GetTempPath(), fs.Path.GetRandomFileName());
77 | fs.Directory.CreateDirectory(path);
78 |
79 | // Assert
80 | var ex = Assert.Throws(() => fs.CreateDisposableDirectory(path, out _));
81 | Assert.True(ex.Data["path"].ToString() == path, "Exception data should contain colliding path to aid with debugging");
82 |
83 | // Delete colliding directory
84 | fs.Directory.Delete(path);
85 | Assert.IsFalse(fs.Directory.Exists(path), "Directory should not exist");
86 | }
87 |
88 | [Test]
89 | public void CreateDisposableDirectory_Custom_IDisposable_Test()
90 | {
91 | // Arrange
92 | var fs = new FileSystem();
93 | string path = null;
94 |
95 | // Act
96 | CustomDisposableDirectory customDisposable;
97 | using (customDisposable = fs.CreateDisposableDirectory(dir => new CustomDisposableDirectory(dir), out var dirInfo))
98 | {
99 | path = dirInfo.FullName;
100 |
101 | Assert.IsTrue(dirInfo.Exists, "Directory should exist");
102 | Assert.IsFalse(customDisposable.DeleteFileSystemInfoWasCalled, "Delete should not have been called yet");
103 | }
104 |
105 | // Assert directory is deleted
106 | Assert.IsNotNull(path);
107 | Assert.IsFalse(fs.Directory.Exists(path), "Directory should not exist");
108 | Assert.IsTrue(customDisposable.DeleteFileSystemInfoWasCalled, "Custom disposable delete should have been called");
109 | }
110 |
111 | [Test]
112 | public void CreateDisposableFile_Temp_Path_Test()
113 | {
114 | // Arrange
115 | var fs = new FileSystem();
116 | string path;
117 |
118 | // Act
119 | using (_ = fs.CreateDisposableFile(out var file))
120 | {
121 | path = file.FullName;
122 |
123 | Assert.That(file.Exists, Is.True, "File should exist");
124 | Assert.IsTrue(
125 | path.StartsWith(fs.Path.GetTempPath(), StringComparison.Ordinal),
126 | "File should be in temp path");
127 | }
128 |
129 | // Assert file is deleted
130 | Assert.That(fs.File.Exists(path), Is.False, "File should not exist");
131 | }
132 |
133 | [Test]
134 | public void CreateDisposableFile_Already_Exists_Test()
135 | {
136 | // Arrange
137 | var fs = new FileSystem();
138 | var path = fs.Path.Combine(fs.Path.GetTempPath(), fs.Path.GetRandomFileName());
139 | fs.File.Create(path).Dispose();
140 |
141 | // Assert
142 | var ex = Assert.Throws(() => fs.CreateDisposableFile(path, out _));
143 | Assert.True(ex.Data["path"].ToString() == path, "Exception data should contain colliding path to aid with debugging");
144 |
145 | // Delete colliding file
146 | fs.File.Delete(path);
147 | Assert.IsFalse(fs.File.Exists(path), "File should not exist");
148 | }
149 |
150 | [Test]
151 | public void CreateDisposableFile_Custom_IDisposable_Test()
152 | {
153 | // Arrange
154 | var fs = new FileSystem();
155 | string path = null;
156 |
157 | // Act
158 | CustomDisposableFile customDisposable;
159 | using (customDisposable = fs.CreateDisposableFile(dir => new CustomDisposableFile(dir), out var fileInfo))
160 | {
161 | path = fileInfo.FullName;
162 |
163 | Assert.IsTrue(fileInfo.Exists, "File should exist");
164 | Assert.IsFalse(customDisposable.DeleteFileSystemInfoWasCalled, "Delete should not have been called yet");
165 | }
166 |
167 | // Assert file is deleted
168 | Assert.IsNotNull(path);
169 | Assert.IsFalse(fs.File.Exists(path), "File should not exist");
170 | Assert.IsTrue(customDisposable.DeleteFileSystemInfoWasCalled, "Custom disposable delete should have been called");
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/tests/System.IO.Abstractions.Extensions.Tests/System.IO.Abstractions.Extensions.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0;net8.0
5 | $(TargetFrameworks);net472
6 | false
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/version.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3 | "version": "22.0",
4 | "assemblyVersion": {
5 | "precision": "major"
6 | },
7 | "publicReleaseRefSpec": [
8 | "^refs/heads/main$"
9 | ],
10 | "cloudBuild": {
11 | "buildNumber": {
12 | "enabled": true
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------