├── .gitattributes ├── .gitignore ├── Build.ps1 ├── LICENSE ├── README.md ├── appveyor.yml ├── build └── 7-zip │ ├── 7za.dll │ ├── 7za.exe │ ├── 7zxa.dll │ ├── Far │ ├── 7-ZipEng.hlf │ ├── 7-ZipEng.lng │ ├── 7-ZipFar.dll │ ├── 7-ZipFar64.dll │ ├── 7-ZipRus.hlf │ ├── 7-ZipRus.lng │ ├── 7zToFar.ini │ ├── far7z.reg │ └── far7z.txt │ ├── License.txt │ ├── history.txt │ └── readme.txt ├── clef-tool.sln ├── clef-tool.sln.DotSettings ├── data └── example.clef ├── src └── Datalust.ClefTool │ ├── ClefTool.ico │ ├── Cli │ ├── Command.cs │ ├── CommandAttribute.cs │ ├── CommandFeature.cs │ ├── CommandLineHost.cs │ ├── CommandMetadata.cs │ ├── Commands │ │ ├── HelpCommand.cs │ │ ├── PipeCommand.cs │ │ └── VersionCommand.cs │ ├── Features │ │ ├── EnrichFeature.cs │ │ ├── FileInputFeature.cs │ │ ├── FileOutputFeature.cs │ │ ├── FilterFeature.cs │ │ ├── InvalidDataHandlingFeature.cs │ │ ├── JsonFormatFeature.cs │ │ ├── SeqOutputFeature.cs │ │ └── TemplateFormatFeature.cs │ ├── ICommandMetadata.cs │ ├── Options.cs │ └── Printing.cs │ ├── Datalust.ClefTool.csproj │ ├── Pipe │ ├── EventPipe.cs │ └── InvalidDataHandling.cs │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── Syntax │ └── ClefToolNameResolver.cs └── test └── Datalust.ClefTool.Tests ├── ClefToolNameResolverTests.cs ├── Datalust.ClefTool.Tests.csproj ├── Pipe └── EventPipeTests.cs └── Support └── CollectingSink.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | 3 | * text=auto 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | 3 | $framework = 'net6.0' 4 | 5 | function Clean-Output 6 | { 7 | if(Test-Path ./artifacts) { rm ./artifacts -Force -Recurse } 8 | } 9 | 10 | function Restore-Packages 11 | { 12 | & dotnet restore 13 | } 14 | 15 | function Execute-Tests 16 | { 17 | & dotnet test ./test/Datalust.ClefTool.Tests/Datalust.ClefTool.Tests.csproj -c Release /p:Configuration=Release /p:Platform=x64 /p:VersionPrefix=$version 18 | if($LASTEXITCODE -ne 0) { exit 3 } 19 | } 20 | 21 | function Create-ArtifactDir 22 | { 23 | mkdir ./artifacts 24 | } 25 | 26 | function Publish-Archives($version) 27 | { 28 | $rids = @("linux-x64", "linux-musl-x64", "linux-arm64", "osx-x64", "win-x64", "osx-arm64") 29 | foreach ($rid in $rids) { 30 | $tfm = $framework 31 | 32 | & dotnet publish ./src/Datalust.ClefTool/Datalust.ClefTool.csproj -c Release -f $tfm -r $rid --self-contained ` 33 | /p:VersionPrefix=$version /p:PublishSingleFile=true /p:PublishReadyToRun=true 34 | 35 | if($LASTEXITCODE -ne 0) { exit 4 } 36 | 37 | # Make sure the archive contains a reasonable root filename 38 | mv ./src/Datalust.ClefTool/bin/Release/$tfm/$rid/publish/ ./src/Datalust.ClefTool/bin/Release/$tfm/$rid/clef-$version-$rid/ 39 | 40 | if ($rid.StartsWith("win-")) { 41 | & ./build/7-zip/7za.exe a -tzip ./artifacts/clef-$version-$rid.zip ./src/Datalust.ClefTool/bin/Release/$tfm/$rid/clef-$version-$rid/ 42 | if($LASTEXITCODE -ne 0) { exit 5 } 43 | 44 | # Back to the original directory name 45 | mv ./src/Datalust.ClefTool/bin/Release/$tfm/$rid/clef-$version-$rid/ ./src/Datalust.ClefTool/bin/Release/$tfm/$rid/publish/ 46 | } else { 47 | & ./build/7-zip/7za.exe a -ttar clef-$version-$rid.tar ./src/Datalust.ClefTool/bin/Release/$tfm/$rid/clef-$version-$rid/ 48 | if($LASTEXITCODE -ne 0) { exit 5 } 49 | 50 | # Back to the original directory name 51 | mv ./src/Datalust.ClefTool/bin/Release/$tfm/$rid/clef-$version-$rid/ ./src/Datalust.ClefTool/bin/Release/$tfm/$rid/publish/ 52 | 53 | & ./build/7-zip/7za.exe a -tgzip ./artifacts/clef-$version-$rid.tar.gz clef-$version-$rid.tar 54 | if($LASTEXITCODE -ne 0) { exit 6 } 55 | 56 | rm clef-$version-$rid.tar 57 | } 58 | } 59 | } 60 | 61 | function Publish-DotNetTool($version) 62 | { 63 | # Tool packages have to target a single non-platform-specific TFM; doing this here is cleaner than attempting it in the CSPROJ directly 64 | & dotnet pack ./src/Datalust.ClefTool/Datalust.ClefTool.csproj -c Release --output ./artifacts /p:VersionPrefix=$version /p:TargetFrameworks=$framework 65 | if($LASTEXITCODE -ne 0) { exit 7 } 66 | } 67 | 68 | Push-Location $PSScriptRoot 69 | 70 | $version = @{ $true = $env:APPVEYOR_BUILD_VERSION; $false = "99.99.99" }[$env:APPVEYOR_BUILD_VERSION -ne $NULL]; 71 | Write-Output "Building version $version" 72 | 73 | Clean-Output 74 | Create-ArtifactDir 75 | Restore-Packages 76 | Publish-Archives($version) 77 | Publish-DotNetTool($version) 78 | Execute-Tests 79 | 80 | Pop-Location 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **C**ompact **L**og **E**vent **F**ormat Tool [![Build status](https://ci.appveyor.com/api/projects/status/ybr08j4h302yaw09?svg=true)](https://ci.appveyor.com/project/datalust/clef-tool) [![Download](https://img.shields.io/badge/download-releases-blue.svg)](https://github.com/datalust/clef-tool/releases) 2 | 3 | The `clef` command-line tool reads and processes the newline-delimited JSON streams produced by [_Serilog.Formatting.Compact_](https://github.com/serilog/serilog-formatting-compact) and other sources. 4 | 5 | > [!IMPORTANT] 6 | > **clef-tool** has been retired. We're focusing our efforts on the later, more complete implementation of this functionality in `seqcli`, which is also open source, and works as a standalone tool. The [`seqcli print`](https://github.com/datalust/seqcli?tab=readme-ov-file#print) and [`seqcli ingest`](https://github.com/datalust/seqcli?tab=readme-ov-file#print) commands provide the main features of `clef-tool`. 7 | 8 | ### What does CLEF look like? 9 | 10 | [CLEF](https://clef-json.org) is a very simple, compact JSON event format with standardized fields for timestamps, messages, levels and so-on. 11 | 12 | ```json 13 | {"@t":"2022-05-09T01:23:45.67890Z","@mt":"Starting up","MachineName":"web-53a889fe"} 14 | ``` 15 | 16 | ### Getting started 17 | 18 | [Binary releases](https://github.com/datalust/clef-tool/releases) can be downloaded directly from this project. 19 | 20 | Or, if you have `dotnet` installed, `clef` can be installed as a global tool using: 21 | 22 | ``` 23 | dotnet tool install --global Datalust.ClefTool 24 | ``` 25 | 26 | And run with: 27 | 28 | ``` 29 | clef --help 30 | ``` 31 | 32 | ### Reading CLEF files 33 | 34 | The default action, given a CLEF file, will be to pretty-print it in text format to the console. 35 | 36 | ``` 37 | > clef -i log-20220509.clef 38 | [2022-05-09T01:23:45.67890Z INF] Starting up 39 | [2022-05-09T01:23:45.96950Z INF] Checking for updates to version 123.4 40 | ... 41 | ``` 42 | 43 | The tool also accepts events on STDIN: 44 | 45 | ``` 46 | > cat log-20220509.clef | clef 47 | ... 48 | ``` 49 | 50 | ### Filtering 51 | 52 | Expressions using the [_Serilog.Expressions_](https://github.com/serilog/serilog-expressions) syntax can be specified to filter the stream: 53 | 54 | ``` 55 | > clef -i log-20220509.clef --filter="Version > 100" 56 | [2022-05-09T01:23:45.96950Z INF] Checking for updates to version 123.4 57 | ``` 58 | 59 | ### Formats 60 | 61 | Output will be plain text unless another format is specified. 62 | 63 | Write the output in JSON format using `--format-json`: 64 | 65 | ``` 66 | > clef -i log-20220509.clef --format-json 67 | {"@t":"2022-05-09T01:23:45.67890Z","@mt":"Starting up"} 68 | ... 69 | ``` 70 | 71 | Control the output text format using `--format-template`: 72 | 73 | ``` 74 | > clef -i log-20220509.clef --format-template="{@m}{NewLine()}" 75 | Starting up 76 | ... 77 | ``` 78 | 79 | ### Outputs 80 | 81 | Output will be written to STDOUT unless another destination is specified. 82 | 83 | Write output to a file with `-o`: 84 | 85 | ``` 86 | > clef -i log-20220509.clef -o log-20220509.txt 87 | ``` 88 | 89 | Send the output to [Seq](https://getseq.net) by specifying a server URL and optional API key: 90 | 91 | ``` 92 | > clef -i log-20220509.clef --out-seq="https://seq.example.com" --out-seq-apikey="1234567890" 93 | ``` 94 | 95 | ### Enrichment 96 | 97 | Events can be enriched with additional properties by specifying them using the `-p` switch: 98 | 99 | ``` 100 | > clef -i log-20220509.clef -p CustomerId=C123 -p Environment=Support [...] 101 | ``` 102 | 103 | ### Filter and template syntax 104 | 105 | The syntax supported in the `--filter` and `--format-template` arguments is documented in the 106 | [_Serilog.Expressions_ language reference](https://github.com/serilog/serilog-expressions#language-reference). 107 | 108 | The following functions are added: 109 | 110 | | Function | Description | 111 | |:------------|:----------------------------------------------------------------------------------------| 112 | | `NewLine()` | Returns a platform-dependent newline character (supported in `--format-template` only). | 113 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 2.0.{build} 2 | skip_tags: true 3 | image: Visual Studio 2022 4 | build_script: 5 | - ps: ./Build.ps1 6 | test: off 7 | artifacts: 8 | - path: artifacts/clef-*.zip 9 | - path: artifacts/clef-*.tar.gz 10 | - path: artifacts/Datalust.ClefTool.*.nupkg 11 | deploy: 12 | - provider: GitHub 13 | auth_token: 14 | secure: Bo3ypKpKFxinjR9ShkNekNvkob2iklHJU+UlYyfHtcFFIAa58SV2TkEd0xWxz633 15 | tag: v$(appveyor_build_version) 16 | on: 17 | branch: main 18 | - provider: NuGet 19 | api_key: 20 | secure: qtcwO3xYGEpN9X+BQNViwuuIJfGBEExqoctZoFFkPsnCz5/mY87S55M+gCDprrno 21 | skip_symbols: true 22 | artifact: /Datalust.ClefTool\..*\.nupkg/ 23 | on: 24 | branch: main 25 | -------------------------------------------------------------------------------- /build/7-zip/7za.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalust/clef-tool/776fac5bc55ab781b8a29deb97d03fbb8c014f74/build/7-zip/7za.dll -------------------------------------------------------------------------------- /build/7-zip/7za.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalust/clef-tool/776fac5bc55ab781b8a29deb97d03fbb8c014f74/build/7-zip/7za.exe -------------------------------------------------------------------------------- /build/7-zip/7zxa.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalust/clef-tool/776fac5bc55ab781b8a29deb97d03fbb8c014f74/build/7-zip/7zxa.dll -------------------------------------------------------------------------------- /build/7-zip/Far/7-ZipEng.hlf: -------------------------------------------------------------------------------- 1 | .Language=English,English 2 | .PluginContents=7-Zip Plugin 3 | 4 | @Contents 5 | $^#7-Zip Plugin 16.04# 6 | $^#Copyright (c) 1999-2016 Igor Pavlov# 7 | This FAR module performs transparent #archive# processing. 8 | Files in the archive are handled in the same manner as if they 9 | were in a folder. 10 | 11 | ~Extracting from the archive~@Extract@ 12 | 13 | ~Add files to the archive~@Update@ 14 | 15 | ~7-Zip Plugin configuration~@Config@ 16 | 17 | 18 | Web site: #www.7-zip.org# 19 | 20 | @Extract 21 | $ #Extracting from the archive# 22 | 23 | In this dialog you may enter extracting mode. 24 | 25 | Path mode 26 | 27 | #Full pathnames# Extract files with full pathnames. 28 | 29 | #Current pathnames# Extract files with all relative paths. 30 | 31 | #No pathnames# Extract files without folder paths. 32 | 33 | 34 | Overwrite mode 35 | 36 | #Ask before overwrite# Ask before overwriting existing files. 37 | 38 | #Overwrite without prompt# Overwrite existing files without prompt. 39 | 40 | #Skip existing files# Skip extracting of existing files. 41 | 42 | 43 | Files 44 | 45 | #Selected files# Extract only selected files. 46 | 47 | #All files# Extract all files from archive. 48 | 49 | @Update 50 | $ #Add files to the archive# 51 | 52 | This dialog allows you to specify options for process of updating archive. 53 | 54 | 55 | Compression method 56 | 57 | #Store# Files will be copied to archive without compression. 58 | 59 | #Normal# Files will be compressed. 60 | 61 | #Maximum# Files will be compressed with method that gives 62 | maximum compression ratio. 63 | 64 | 65 | Update mode 66 | 67 | #Add and replace files# Add all specified files to the archive. 68 | 69 | #Update and add files# Update older files in the archive and add 70 | files that are new to the archive. 71 | 72 | #Freshen existing files# Update specified files in the archive that 73 | are older than the selected disk files. 74 | 75 | #Synchronize files# Replace specified files only if 76 | added files are newer. Always add those 77 | files, which are not present in the 78 | archive. Delete from archive those files, 79 | which are not present on the disk. 80 | 81 | @Config 82 | $ #7-Zip Plugin configuration# 83 | In this dialog you may change following parameters: 84 | 85 | #Plugin is used by default# Plugin is used by default. 86 | -------------------------------------------------------------------------------- /build/7-zip/Far/7-ZipEng.lng: -------------------------------------------------------------------------------- 1 | .Language=English,English 2 | 3 | "Ok" 4 | "&Cancel" 5 | 6 | "Warning" 7 | "Error" 8 | 9 | "Format" 10 | 11 | "Properties" 12 | 13 | "Yes" 14 | "No" 15 | 16 | "Get password" 17 | "Enter password" 18 | 19 | "Extract" 20 | "&Extract to" 21 | 22 | "Path mode" 23 | "&Full pathnames" 24 | "C&urrent pathnames" 25 | "&No pathnames" 26 | 27 | "Overwrite mode" 28 | "As&k before overwrite" 29 | "&Overwrite without prompt" 30 | "Sk&ip existing files" 31 | "A&uto rename" 32 | "A&uto rename existing files" 33 | 34 | "Extract" 35 | "&Selected files" 36 | "A&ll files" 37 | 38 | "&Password" 39 | 40 | "Extr&act" 41 | "&Cancel" 42 | 43 | "Can not open output file '%s'." 44 | 45 | "Unsupported compression method for '%s'." 46 | "CRC failed in '%s'." 47 | "Data error in '%s'." 48 | "CRC failed in encrypted file '%s'. Wrong password?" 49 | "Data error in encrypted file '%s'. Wrong password?" 50 | 51 | "Confirm File Replace" 52 | "Destination folder already contains processed file." 53 | "Would you like to replace the existing file" 54 | "with this one" 55 | 56 | "bytes" 57 | "modified on" 58 | 59 | 60 | "&Yes" 61 | "Yes to &All" 62 | "&No" 63 | "No to A&ll" 64 | "A&uto rename" 65 | "&Cancel" 66 | 67 | 68 | "Update operations are not supported for this archive." 69 | 70 | 71 | "Delete from archive" 72 | "Delete \"%.40s\" from the archive" 73 | "Delete selected files from the archive" 74 | "Delete %d files from the archive" 75 | "Delete" 76 | "Cancel" 77 | 78 | "Add files to archive" 79 | 80 | "Add to %s a&rchive:" 81 | 82 | "Compression method" 83 | "&Store" 84 | "Fas&test" 85 | "&Fast" 86 | "&Normal" 87 | "&Maximum" 88 | "&Ultra" 89 | 90 | "Update mode" 91 | "A&dd and replace files" 92 | "&Update and add files" 93 | "&Freshen existing files" 94 | "S&ynchronize files" 95 | 96 | "&Add" 97 | "Se&lect archiver" 98 | 99 | "Select archive format" 100 | 101 | "Wait" 102 | "Reading the archive" 103 | "Extracting from the archive" 104 | "Deleting from the archive" 105 | "Updating the archive" 106 | 107 | "Move operation is not supported" 108 | 109 | "7-Zip" 110 | "7-Zip (add to archive)" 111 | 112 | "7-Zip" 113 | 114 | "Plugin is used by default" 115 | 116 | "0" 117 | "1" 118 | "2" 119 | "Path" 120 | "Name" 121 | "Extension" 122 | "Is Folder" 123 | "Size" 124 | "Packed Size" 125 | "Attributes" 126 | "Created" 127 | "Accessed" 128 | "Modified" 129 | "Solid" 130 | "Commented" 131 | "Encrypted" 132 | "Splited Before" 133 | "Splited After" 134 | "Dictionary Size" 135 | "CRC" 136 | "Type" 137 | "Anti" 138 | "Method" 139 | "Host OS" 140 | "File System" 141 | "User" 142 | "Group" 143 | "Block" 144 | "Comment" 145 | "Position" 146 | "Path Prefix" 147 | "Folders" 148 | "Files" 149 | "Version" 150 | "Volume" 151 | "Multivolume" 152 | "Offset" 153 | "Links" 154 | "Blocks" 155 | "Volumes" 156 | "Time Type" 157 | "64-bit" 158 | "Big-endian" 159 | "CPU" 160 | "Physical Size" 161 | "Headers Size" 162 | "Checksum" 163 | "Characteristics" 164 | "Virtual Address" 165 | "ID" 166 | "Short Name" 167 | "Creator Application" 168 | "Sector Size" 169 | "Mode" 170 | "Symbolic Link" 171 | "Error" 172 | "Total Size" 173 | "Free Space" 174 | "Cluster Size" 175 | "Label" 176 | "Local Name" 177 | "Provider" 178 | "NT Security" 179 | "Alternate Stream" 180 | "Aux" 181 | "Deleted" 182 | "Tree" 183 | "SHA-1" 184 | "SHA-256" 185 | "Error Type" 186 | "Errors" 187 | "Errors" 188 | "Warnings" 189 | "Warning" 190 | "Streams" 191 | "Alternate Streams" 192 | "Alternate Streams Size" 193 | "Virtual Size" 194 | "Unpack Size" 195 | "Total Physical Size" 196 | "Volume Index" 197 | "SubType" 198 | "Short Comment" 199 | "Code Page" 200 | "Is not archive type" 201 | "Physical Size can't be detected" 202 | "Zeros Tail Is Allowed" 203 | "Tail Size" 204 | "Embedded Stub Size" 205 | "Link" 206 | "Hard Link" 207 | "iNode" 208 | "Stream ID" 209 | -------------------------------------------------------------------------------- /build/7-zip/Far/7-ZipFar.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalust/clef-tool/776fac5bc55ab781b8a29deb97d03fbb8c014f74/build/7-zip/Far/7-ZipFar.dll -------------------------------------------------------------------------------- /build/7-zip/Far/7-ZipFar64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalust/clef-tool/776fac5bc55ab781b8a29deb97d03fbb8c014f74/build/7-zip/Far/7-ZipFar64.dll -------------------------------------------------------------------------------- /build/7-zip/Far/7-ZipRus.hlf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalust/clef-tool/776fac5bc55ab781b8a29deb97d03fbb8c014f74/build/7-zip/Far/7-ZipRus.hlf -------------------------------------------------------------------------------- /build/7-zip/Far/7-ZipRus.lng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalust/clef-tool/776fac5bc55ab781b8a29deb97d03fbb8c014f74/build/7-zip/Far/7-ZipRus.lng -------------------------------------------------------------------------------- /build/7-zip/Far/7zToFar.ini: -------------------------------------------------------------------------------- 1 | ; 7z supporting for MutiArc in Far 2 | ; Append the following strings to file 3 | ; ..\Program Files\Far\Plugins\MultiArc\Formats\Custom.ini 4 | 5 | [7z] 6 | TypeName=7z 7 | ID=37 7A BC AF 27 1C 8 | IDPos= 9 | IDOnly=1 10 | Extension=7z 11 | List=7z l -- %%AQ 12 | Start="^-----" 13 | End="^-----" 14 | Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" 15 | Extract=7z x {-p%%P} -r0 -y -scsDOS -- %%A @%%LQMN 16 | ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -- %%A @%%LQMN 17 | Test=7z t {-p%%P} -r0 -scsDOS -- %%A @%%LQMN 18 | Delete=7z d {-p%%P} -r0 -ms=off -scsDOS -- %%A @%%LQMN 19 | Add=7z a {-p%%P} -r0 -t7z {%%S} -scsDOS -- %%A @%%LQMN 20 | AddRecurse=7z a {-p%%P} -r0 -t7z {%%S} -scsDOS -- %%A @%%LQMN 21 | AllFilesMask="*" 22 | 23 | [rpm] 24 | TypeName=rpm 25 | ID=ED AB EE DB 26 | IDPos= 27 | IDOnly=1 28 | Extension=rpm 29 | List=7z l -- %%AQ 30 | Start="^-----" 31 | End="^-----" 32 | Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" 33 | Extract=7z x {-p%%P} -r0 -y -scsDOS -- %%A @%%LQMN 34 | ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -- %%A @%%LQMN 35 | Test=7z t {-p%%P} -r0 -scsDOS -- %%A @%%LQMN 36 | AllFilesMask="*" 37 | 38 | [cpio] 39 | TypeName=cpio 40 | ID= 41 | IDPos= 42 | IDOnly=0 43 | Extension=cpio 44 | List=7z l -- %%AQ 45 | Start="^-----" 46 | End="^-----" 47 | Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" 48 | Extract=7z x {-p%%P} -r0 -y -scsDOS -- %%A @%%LQMN 49 | ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -- %%A @%%LQMN 50 | Test=7z t {-p%%P} -r0 -scsDOS -- %%A @%%LQMN 51 | AllFilesMask="*" 52 | 53 | [deb] 54 | TypeName=deb 55 | ID= 56 | IDPos= 57 | IDOnly=0 58 | Extension=deb 59 | List=7z l -- %%AQ 60 | Start="^-----" 61 | End="^-----" 62 | Format0="yyyy tt dd hh mm ss aaaaa zzzzzzzzzzzz pppppppppppp nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn" 63 | Extract=7z x {-p%%P} -r0 -y -scsDOS -- %%A @%%LQMN 64 | ExtractWithoutPath=7z e {-p%%P} -r0 -y -scsDOS -- %%A @%%LQMN 65 | Test=7z t {-p%%P} -r0 -scsDOS -- %%A @%%LQMN 66 | AllFilesMask="*" 67 | 68 | -------------------------------------------------------------------------------- /build/7-zip/Far/far7z.reg: -------------------------------------------------------------------------------- 1 | REGEDIT4 2 | 3 | [HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\ZIP] 4 | "Extract"="7z x {-p%%P} -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 5 | "ExtractWithoutPath"="7z e {-p%%P} -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 6 | "Test"="7z t {-p%%P} -r0 -scsDOS -- %%A @%%LQMN" 7 | "Delete"="7z d {-p%%P} -r0 {-w%%W} -scsDOS -- %%A @%%LQMN" 8 | "Add"="7z a {-p%%P} -r0 -tzip {-w%%W} {%%S} -scsDOS -- %%A @%%LQMN" 9 | "AddRecurse"="7z a {-p%%P} -r0 -tzip {-w%%W} {%%S} -scsDOS -- %%A @%%LQMN" 10 | "AllFilesMask"="*" 11 | 12 | [HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\TAR] 13 | "Extract"="7z x -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 14 | "ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 15 | "Test"="7z t -r0 -scsDOS -- %%A @%%LQMN" 16 | "Delete"="7z d -r0 {-w%%W} -scsDOS -- %%A @%%LQMN" 17 | "Add"="7z a -r0 -y -ttar {-w%%W} {%%S} -scsDOS -- %%A @%%LQMN" 18 | "AddRecurse"="7z a -r0 -y -ttar {-w%%W} {%%S} -scsDOS -- %%A @%%LQMN" 19 | "AllFilesMask"="*" 20 | 21 | [HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\GZIP] 22 | "Extract"="7z x -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 23 | "ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 24 | "Test"="7z t -r0 -scsDOS -- %%A @%%LQMN" 25 | "Delete"="7z d -r0 {-w%%W} -scsDOS -- %%A @%%LQMN" 26 | "Add"="7z a -r0 -tgzip {-w%%W} {%%S} -scsDOS -- %%A @%%LQMN" 27 | "AddRecurse"="7z a -r0 -tgzip {-w%%W} {%%S} -scsDOS -- %%A @%%LQMN" 28 | "AllFilesMask"="*" 29 | 30 | [HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\BZIP] 31 | "Extract"="7z x -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 32 | "ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 33 | "Test"="7z t -r0 -scsDOS -- %%A @%%LQMN" 34 | "Delete"="7z d -r0 {-w%%W} -scsDOS -- %%A @%%LQMN" 35 | "Add"="7z a -r0 -tbzip2 {-w%%W} {%%S} -scsDOS -- %%A @%%LQMN" 36 | "AddRecurse"="7z a -r0 -tbzip2 {-w%%W} {%%S} -scsDOS -- %%A @%%LQMN" 37 | "AllFilesMask"="*" 38 | 39 | [HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\ARJ] 40 | "Extract"="7z x -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 41 | "ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 42 | "Test"="7z t -r0 -scsDOS -- %%A @%%LQMN" 43 | "AllFilesMask"="*" 44 | 45 | [HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\CAB] 46 | "Extract"="7z x -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 47 | "ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 48 | "Test"="7z t -r0 -scsDOS -- %%A @%%LQMN" 49 | "AllFilesMask"="*" 50 | 51 | [HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\LZH] 52 | "Extract"="7z x -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 53 | "ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 54 | "Test"="7z t -r0 -scsDOS -- %%A @%%LQMN" 55 | "AllFilesMask"="*" 56 | 57 | [HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\RAR] 58 | "Extract"="7z x -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 59 | "ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 60 | "Test"="7z t -r0 -scsDOS -- %%A @%%LQMN" 61 | "AllFilesMask"="*" 62 | 63 | [HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\Z(Unix)] 64 | "Extract"="7z x -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 65 | "ExtractWithoutPath"="7z e -r0 -y {-w%%W} -scsDOS -- %%A @%%LQMN" 66 | "Test"="7z t -r0 -scsDOS -- %%A @%%LQMN" 67 | "AllFilesMask"="*" 68 | -------------------------------------------------------------------------------- /build/7-zip/Far/far7z.txt: -------------------------------------------------------------------------------- 1 | 7-Zip Plugin for FAR Manager 2 | ---------------------------- 3 | 4 | FAR Manager is a file manager working in text mode. 5 | You can download "FAR Manager" from site: 6 | http://www.farmanager.com 7 | 8 | Files: 9 | 10 | far7z.txt - This file 11 | far7z.reg - Regisrty file for MultiArc Plugin 12 | 7zToFar.ini - Supporting 7z for MultiArc Plugin 13 | 7-ZipFar.dll - 7-Zip Plugin for FAR Manager 14 | 7-ZipEng.hlf - Help file in English for FAR Manager 15 | 7-ZipRus.hlf - Help file in Russian for FAR Manager 16 | 7-ZipEng.lng - Plugin message strings in English for FAR Manager 17 | 7-ZipRus.lng - Plugin message strings in Russian for FAR Manager 18 | 19 | There are two ways to use 7-Zip with FAR Manager: 20 | 21 | 1) Via 7-Zip FAR Plugin (it's recommended way). 22 | 2) Via standard MultiArc Plugin. 23 | 24 | 25 | 7-Zip FAR Plugin 26 | ~~~~~~~~~~~~~~~~ 27 | 28 | 7-Zip FAR Plugin is first level plugin for FAR Manager, like MultiArc plugin. 29 | It very fast extracts and updates files in archive, since it doesn't use 30 | external programs. It supports all formats supported by 7-Zip: 31 | 7z, ZIP, RAR, CAB, ARJ, GZIP, BZIP2, Z, TAR, CPIO, RPM and DEB. 32 | 33 | To install 7-Zip FAR Plugin: 34 | 1) Create "7-Zip" folder in ...\Program Files\Far\Plugins folder. 35 | 2) Copy all files from "FAR" folder of this package to created folder. 36 | 3) Install 7-Zip, or copy 7z.dll from 7-Zip to Program Files\Far\Plugins\7-Zip\ 37 | 4) Restart FAR. 38 | 39 | You can open archives with one of the following ways: 40 | * Pressing Enter. 41 | * Pressing Ctrl-PgDown. 42 | * Pressing F11 and selecting 7-Zip item. 43 | 44 | 45 | You can create new archives with 7-Zip by pressing F11 and 46 | selecting 7-Zip (add to archive) item. 47 | 48 | If you think that some operations with archives is better to do with MultiArc Plugin, 49 | you can disable 7-Zip plugin via Options / Pligin configuration / 7-Zip. In such mode 50 | opening archives by pressing Enter and Ctrl-PgDown will start MultiArc Plugin. And 51 | if you want to open archive with 7-Zip, press F11 and select 7-Zip item. 52 | 53 | 54 | Using command line 7-Zip via MultiArc Plugin 55 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | If you want to use 7-Zip via MultiArc Plugin, you must 58 | register file far7z.reg. 59 | 60 | If you want to use 7z archives via MultiArc Plugin, you must 61 | append contents of file Far\7zToFar.ini to file 62 | ..\Program Files\Far\Plugins\MultiArc\Formats\Custom.ini. 63 | 64 | 65 | If you want to cancel using 7-Zip by MultiArc, just remove lines that contain 66 | 7-Zip (7z) program name from HKEY_LOCAL_MACHINE\SOFTWARE\Far\Plugins\MultiArc\ZIP 67 | registry key. 68 | -------------------------------------------------------------------------------- /build/7-zip/License.txt: -------------------------------------------------------------------------------- 1 | 7-Zip Extra 2 | ~~~~~~~~~~~ 3 | License for use and distribution 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Copyright (C) 1999-2016 Igor Pavlov. 7 | 8 | 7-Zip Extra files are under the GNU LGPL license. 9 | 10 | 11 | Notes: 12 | You can use 7-Zip Extra on any computer, including a computer in a commercial 13 | organization. You don't need to register or pay for 7-Zip. 14 | 15 | 16 | GNU LGPL information 17 | -------------------- 18 | 19 | This library is free software; you can redistribute it and/or 20 | modify it under the terms of the GNU Lesser General Public 21 | License as published by the Free Software Foundation; either 22 | version 2.1 of the License, or (at your option) any later version. 23 | 24 | This library is distributed in the hope that it will be useful, 25 | but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 27 | Lesser General Public License for more details. 28 | 29 | You can receive a copy of the GNU Lesser General Public License from 30 | http://www.gnu.org/ 31 | 32 | -------------------------------------------------------------------------------- /build/7-zip/history.txt: -------------------------------------------------------------------------------- 1 | 7-Zip Extra history 2 | ------------------- 3 | 4 | 9.35 beta 2014-12-07 5 | ------------------------------ 6 | - SFX modules were moved to LZMA SDK package. 7 | 8 | 9 | 9.34 alpha 2014-06-22 10 | ------------------------------ 11 | - Minimum supported system now is Windows 2000 for EXE and DLL files. 12 | - all EXE and DLL files use msvcrt.dll. 13 | - 7zr.exe now support AES encryption. 14 | 15 | 16 | 9.18 2010-11-02 17 | ------------------------------ 18 | - New small SFX module for installers. 19 | 20 | 21 | 9.17 2010-10-04 22 | ------------------------------ 23 | - New 7-Zip plugin for FAR Manager x64. 24 | 25 | 26 | 9.10 2009-12-30 27 | ------------------------------ 28 | - 7-Zip for installers now supports LZMA2. 29 | 30 | 31 | 9.09 2009-12-12 32 | ------------------------------ 33 | - LZMA2 compression method support. 34 | - Some bugs were fixed. 35 | 36 | 37 | 4.65 2009-02-03 38 | ------------------------------ 39 | - Some bugs were fixed. 40 | 41 | 42 | 4.38 beta 2006-04-13 43 | ------------------------------ 44 | - SFX for installers now supports new properties in config file: 45 | Progress, Directory, ExecuteFile, ExecuteParameters. 46 | 47 | 48 | 4.34 beta 2006-02-27 49 | ------------------------------ 50 | - ISetProperties::SetProperties: 51 | it's possible to specify desirable number of CPU threads: 52 | PROPVARIANT: name=L"mt", vt = VT_UI4, ulVal = NumberOfThreads 53 | If "mt" is not defined, 7za.dll will check number of processors in system to set 54 | number of desirable threads. 55 | Now 7za.dll can use: 56 | 2 threads for LZMA compressing 57 | N threads for BZip2 compressing 58 | 4 threads for BZip2 decompressing 59 | Other codecs use only one thread. 60 | Note: 7za.dll can use additional "small" threads with low CPU load. 61 | - It's possible to call ISetProperties::SetProperties to specify "mt" property for decoder. 62 | 63 | 64 | 4.33 beta 2006-02-05 65 | ------------------------------ 66 | - Compressing speed and Memory requirements were increased. 67 | Default dictionary size was increased: Fastest: 64 KB, Fast: 1 MB, 68 | Normal: 4 MB, Max: 16 MB, Ultra: 64 MB. 69 | - 7z/LZMA now can use only these match finders: HC4, BT2, BT3, BT4 70 | 71 | 72 | 4.27 2005-09-21 73 | ------------------------------ 74 | - Some GUIDs/interfaces were changed. 75 | IStream.h: 76 | ISequentialInStream::Read now works as old ReadPart 77 | ISequentialOutStream::Write now works as old WritePart 78 | -------------------------------------------------------------------------------- /build/7-zip/readme.txt: -------------------------------------------------------------------------------- 1 | 7-Zip Extra 16.04 2 | ----------------- 3 | 4 | 7-Zip Extra is package of extra modules of 7-Zip. 5 | 6 | 7-Zip Copyright (C) 1999-2016 Igor Pavlov. 7 | 8 | 7-Zip is free software. Read License.txt for more information about license. 9 | 10 | Source code of binaries can be found at: 11 | http://www.7-zip.org/ 12 | 13 | This package contains the following files: 14 | 15 | 7za.exe - standalone console version of 7-Zip with reduced formats support. 16 | 7za.dll - library for working with 7z archives 17 | 7zxa.dll - library for extracting from 7z archives 18 | License.txt - license information 19 | readme.txt - this file 20 | 21 | Far\ - plugin for Far Manager 22 | x64\ - binaries for x64 23 | 24 | 25 | All 32-bit binaries can work in: 26 | Windows 2000 / 2003 / 2008 / XP / Vista / 7 / 8 / 10 27 | and in any Windows x64 version with WoW64 support. 28 | All x64 binaries can work in any Windows x64 version. 29 | 30 | All binaries use msvcrt.dll. 31 | 32 | 7za.exe 33 | ------- 34 | 35 | 7za.exe - is a standalone console version of 7-Zip with reduced formats support. 36 | 37 | Extra: 7za.exe : support for only some formats of 7-Zip. 38 | 7-Zip: 7z.exe with 7z.dll : support for all formats of 7-Zip. 39 | 40 | 7za.exe and 7z.exe from 7-Zip have same command line interface. 41 | 7za.exe doesn't use external DLL files. 42 | 43 | You can read Help File (7-zip.chm) from 7-Zip package for description 44 | of all commands and switches for 7za.exe and 7z.exe. 45 | 46 | 7za.exe features: 47 | 48 | - High compression ratio in 7z format 49 | - Supported formats: 50 | - Packing / unpacking: 7z, xz, ZIP, GZIP, BZIP2 and TAR 51 | - Unpacking only: Z, lzma, CAB. 52 | - Highest compression ratio for ZIP and GZIP formats. 53 | - Fast compression and decompression 54 | - Strong AES-256 encryption in 7z and ZIP formats. 55 | 56 | Note: LZMA SDK contains 7zr.exe - more reduced version of 7za.exe. 57 | But you can use 7zr.exe as "public domain" code. 58 | 59 | 60 | 61 | DLL files 62 | --------- 63 | 64 | 7za.dll and 7zxa.dll are reduced versions of 7z.dll from 7-Zip. 65 | 7za.dll and 7zxa.dll support only 7z format. 66 | Note: 7z.dll is main DLL file that works with all archive types in 7-Zip. 67 | 68 | 7za.dll and 7zxa.dll support the following decoding methods: 69 | - LZMA, LZMA2, PPMD, BCJ, BCJ2, COPY, 7zAES, BZip2, Deflate. 70 | 71 | 7za.dll also supports 7z encoding with the following encoding methods: 72 | - LZMA, LZMA2, PPMD, BCJ, BCJ2, COPY, 7zAES. 73 | 74 | 7za.dll and 7zxa.dll work via COM interfaces. 75 | But these DLLs don't use standard COM interfaces for objects creating. 76 | 77 | Look also example code that calls DLL functions (in source code of 7-Zip): 78 | 79 | 7zip\UI\Client7z 80 | 81 | Another example of binary that uses these interface is 7-Zip itself. 82 | The following binaries from 7-Zip use 7z.dll: 83 | - 7z.exe (console version) 84 | - 7zG.exe (GUI version) 85 | - 7zFM.exe (7-Zip File Manager) 86 | 87 | Note: The source code of LZMA SDK also contains the code for similar DLLs 88 | (DLLs without BZip2, Deflate support). And these files from LZMA SDK can be 89 | used as "public domain" code. If you use LZMA SDK files, you don't need to 90 | follow GNU LGPL rules, if you want to change the code. 91 | 92 | 93 | 94 | 95 | License FAQ 96 | ----------- 97 | 98 | Can I use the EXE or DLL files from 7-Zip in a commercial application? 99 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 100 | Yes, but you are required to specify in documentation for your application: 101 | (1) that you used parts of the 7-Zip program, 102 | (2) that 7-Zip is licensed under the GNU LGPL license and 103 | (3) you must give a link to www.7-zip.org, where the source code can be found. 104 | 105 | 106 | Can I use the source code of 7-Zip in a commercial application? 107 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 108 | Since 7-Zip is licensed under the GNU LGPL you must follow the rules of that license. 109 | In brief, it means that any LGPL'ed code must remain licensed under the LGPL. 110 | For instance, you can change the code from 7-Zip or write a wrapper for some 111 | code from 7-Zip and compile it into a DLL; but, the source code of that DLL 112 | (including your modifications / additions / wrapper) must be licensed under 113 | the LGPL or GPL. 114 | Any other code in your application can be licensed as you wish. This scheme allows 115 | users and developers to change LGPL'ed code and recompile that DLL. That is the 116 | idea of free software. Read more here: http://www.gnu.org/. 117 | 118 | 119 | 120 | Note: You can look also LZMA SDK, which is available under a more liberal license. 121 | 122 | 123 | --- 124 | End of document 125 | -------------------------------------------------------------------------------- /clef-tool.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{61556DAE-6B5F-472B-AA8F-6E36E6B1376D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1F622339-3A47-4350-8291-AD6834E025E1}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Datalust.ClefTool", "src\Datalust.ClefTool\Datalust.ClefTool.csproj", "{9898F355-E8FA-4D8E-8850-443010C9E8A5}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Datalust.ClefTool.Tests", "test\Datalust.ClefTool.Tests\Datalust.ClefTool.Tests.csproj", "{8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "data", "data", "{0DA54CF5-F0A9-4A0B-B5F3-B3CB0902EAAE}" 15 | ProjectSection(SolutionItems) = preProject 16 | data\example.clef = data\example.clef 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sln", "sln", "{8644E54F-A00F-4E52-80AD-47647123D265}" 20 | ProjectSection(SolutionItems) = preProject 21 | appveyor.yml = appveyor.yml 22 | Build.ps1 = Build.ps1 23 | LICENSE = LICENSE 24 | README.md = README.md 25 | .gitattributes = .gitattributes 26 | .gitignore = .gitignore 27 | EndProjectSection 28 | EndProject 29 | Global 30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 31 | Debug|Any CPU = Debug|Any CPU 32 | Debug|x64 = Debug|x64 33 | Release|Any CPU = Release|Any CPU 34 | Release|x64 = Release|x64 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {9898F355-E8FA-4D8E-8850-443010C9E8A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {9898F355-E8FA-4D8E-8850-443010C9E8A5}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {9898F355-E8FA-4D8E-8850-443010C9E8A5}.Debug|x64.ActiveCfg = Debug|Any CPU 40 | {9898F355-E8FA-4D8E-8850-443010C9E8A5}.Debug|x64.Build.0 = Debug|Any CPU 41 | {9898F355-E8FA-4D8E-8850-443010C9E8A5}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {9898F355-E8FA-4D8E-8850-443010C9E8A5}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {9898F355-E8FA-4D8E-8850-443010C9E8A5}.Release|x64.ActiveCfg = Release|Any CPU 44 | {9898F355-E8FA-4D8E-8850-443010C9E8A5}.Release|x64.Build.0 = Release|Any CPU 45 | {8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A}.Debug|x64.ActiveCfg = Debug|Any CPU 48 | {8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A}.Debug|x64.Build.0 = Debug|Any CPU 49 | {8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A}.Release|x64.ActiveCfg = Release|Any CPU 52 | {8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A}.Release|x64.Build.0 = Release|Any CPU 53 | EndGlobalSection 54 | GlobalSection(SolutionProperties) = preSolution 55 | HideSolutionNode = FALSE 56 | EndGlobalSection 57 | GlobalSection(NestedProjects) = preSolution 58 | {9898F355-E8FA-4D8E-8850-443010C9E8A5} = {61556DAE-6B5F-472B-AA8F-6E36E6B1376D} 59 | {8603CDF1-1B8D-4762-B5CB-7BD5DA40C16A} = {1F622339-3A47-4350-8291-AD6834E025E1} 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /clef-tool.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /data/example.clef: -------------------------------------------------------------------------------- 1 | {"@t":"2017-04-20T04:24:47.0251719Z","@mt":"Loop {Counter}","Counter":0} 2 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":1} 3 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":2} 4 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":3} 5 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":4} 6 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":5} 7 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":6} 8 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":7} 9 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":8} 10 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":9} 11 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":10} 12 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":11} 13 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":12} 14 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":13} 15 | {"@t":"2017-04-20T04:24:47.0371689Z","@mt":"Loop {Counter}","Counter":14} 16 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/ClefTool.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datalust/clef-tool/776fac5bc55ab781b8a29deb97d03fbb8c014f74/src/Datalust.ClefTool/ClefTool.ico -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Command.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | using System.IO; 18 | using System.Linq; 19 | 20 | namespace Datalust.ClefTool.Cli 21 | { 22 | public abstract class Command 23 | { 24 | private readonly IList _features = new List(); 25 | 26 | protected Command() 27 | { 28 | Options = new OptionSet(); 29 | } 30 | 31 | protected OptionSet Options { get; } 32 | 33 | public bool HasArgs => Options.Any(); 34 | 35 | protected T Enable() 36 | where T : CommandFeature, new() 37 | { 38 | var t = new T(); 39 | return Enable(t); 40 | } 41 | 42 | protected T Enable(T t) 43 | where T : CommandFeature 44 | { 45 | if (t == null) throw new ArgumentNullException(nameof(t)); 46 | t.Enable(Options); 47 | _features.Add(t); 48 | return t; 49 | } 50 | 51 | public void PrintUsage() 52 | { 53 | if (Options.Any()) 54 | { 55 | Console.Error.WriteLine("Arguments:"); 56 | Options.WriteOptionDescriptions(Console.Error); 57 | } 58 | } 59 | 60 | public int Invoke(string[] args) 61 | { 62 | try 63 | { 64 | var unrecognised = Options.Parse(args).ToArray(); 65 | 66 | var errs = _features.SelectMany(f => f.Errors).ToList(); 67 | 68 | if (errs.Any()) 69 | { 70 | ShowUsageErrors(errs); 71 | return -1; 72 | } 73 | 74 | return Run(unrecognised); 75 | } 76 | catch (Exception ex) 77 | { 78 | Console.Error.WriteLine(ex.Message); 79 | return -1; 80 | } 81 | } 82 | 83 | protected virtual int Run(string[] unrecognised) 84 | { 85 | // All commands used to accept --nologo 86 | var notIgnored = unrecognised.Where(o => o.IndexOf("nologo", StringComparison.OrdinalIgnoreCase) == -1); 87 | if (notIgnored.Any()) 88 | { 89 | ShowUsageErrors(new [] { "Unrecognized options: " + string.Join(", ", notIgnored) }); 90 | return -1; 91 | } 92 | 93 | return Run(); 94 | } 95 | 96 | protected virtual int Run() { return 0; } 97 | 98 | protected virtual void ShowUsageErrors(IEnumerable errors) 99 | { 100 | var header = "Error:"; 101 | foreach (var error in errors) 102 | { 103 | Printing.Define(header, error, 7, Console.Out); 104 | header = new string(' ', header.Length); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/CommandAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | 17 | namespace Datalust.ClefTool.Cli 18 | { 19 | [AttributeUsage(AttributeTargets.Class)] 20 | public class CommandAttribute : Attribute, ICommandMetadata 21 | { 22 | public string Name { get; private set; } 23 | public string HelpText { get; private set; } 24 | 25 | public CommandAttribute(string name, string helpText) 26 | { 27 | Name = name; 28 | HelpText = helpText; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/CommandFeature.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Collections.Generic; 16 | 17 | namespace Datalust.ClefTool.Cli 18 | { 19 | public abstract class CommandFeature 20 | { 21 | protected CommandFeature() 22 | { 23 | Errors = new List(); 24 | } 25 | 26 | public abstract void Enable(OptionSet options); 27 | 28 | public IList Errors { get; private set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/CommandLineHost.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Linq; 18 | using Autofac.Features.Metadata; 19 | 20 | namespace Datalust.ClefTool.Cli 21 | { 22 | public class CommandLineHost 23 | { 24 | readonly List, CommandMetadata>> _availableCommands; 25 | 26 | public CommandLineHost(IEnumerable, CommandMetadata>> availableCommands) 27 | { 28 | _availableCommands = availableCommands.ToList(); 29 | } 30 | 31 | public int Run(string[] args) 32 | { 33 | if (args.Length > 0) 34 | { 35 | var norm = args[0].ToLowerInvariant(); 36 | var cmd = _availableCommands.SingleOrDefault(c => c.Metadata.Name == norm); 37 | if (cmd != null) 38 | { 39 | return cmd.Value.Value.Invoke(args.Skip(1).ToArray()); 40 | } 41 | } 42 | 43 | var pipeCommand = _availableCommands.Single(c => c.Metadata.Name == "pipe"); 44 | return pipeCommand.Value.Value.Invoke(args.ToArray()); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/CommandMetadata.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Datalust.ClefTool.Cli 16 | { 17 | public class CommandMetadata : ICommandMetadata 18 | { 19 | public string Name { get; set; } = null!; 20 | public string HelpText { get; set; } = null!; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Commands/HelpCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Linq; 18 | using System.Reflection; 19 | using Autofac.Features.Metadata; 20 | 21 | namespace Datalust.ClefTool.Cli.Commands 22 | { 23 | [Command("--help", "Show information about available commands")] 24 | public class HelpCommand : Command 25 | { 26 | readonly List, CommandMetadata>> _availableCommands; 27 | 28 | public HelpCommand(IEnumerable, CommandMetadata>> availableCommands) 29 | { 30 | _availableCommands = availableCommands.OrderBy(c => c.Metadata.Name).ToList(); 31 | } 32 | 33 | protected override int Run(string[] unrecognised) 34 | { 35 | // This is a little hacky; we always show the help for the (anonymous) "pipe" command. 36 | 37 | var ea = Assembly.GetEntryAssembly()!; 38 | var name = ea.GetName().Name; 39 | 40 | var cmd = _availableCommands.SingleOrDefault(c => c.Metadata.Name == "pipe"); 41 | if (cmd != null) 42 | { 43 | var argHelp = cmd.Value.Value.HasArgs ? " []" : ""; 44 | Console.Error.WriteLine(name + argHelp); 45 | Console.Error.WriteLine(); 46 | Console.Error.WriteLine(cmd.Metadata.HelpText); 47 | Console.Error.WriteLine(); 48 | 49 | cmd.Value.Value.PrintUsage(); 50 | return 0; 51 | } 52 | 53 | return base.Run(unrecognised); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Commands/PipeCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using Datalust.ClefTool.Cli.Features; 5 | using Datalust.ClefTool.Pipe; 6 | using Datalust.ClefTool.Syntax; 7 | using Serilog; 8 | using Serilog.Core; 9 | using Serilog.Debugging; 10 | using Serilog.Events; 11 | using Serilog.Formatting.Compact; 12 | using Serilog.Formatting.Compact.Reader; 13 | using Serilog.Templates; 14 | using Serilog.Templates.Themes; 15 | 16 | // ReSharper disable UnusedType.Global 17 | 18 | namespace Datalust.ClefTool.Cli.Commands 19 | { 20 | [Command("pipe", "Process CLEF-formatted events")] 21 | class PipeCommand : Command 22 | { 23 | readonly EnrichFeature _enrichFeature; 24 | readonly FileInputFeature _fileInputFeature; 25 | readonly FilterFeature _filterFeature; 26 | readonly JsonFormatFeature _jsonFormatFeature; 27 | readonly FileOutputFeature _fileOutputFeature; 28 | readonly TemplateFormatFeature _templateFormatFeature; 29 | readonly SeqOutputFeature _seqOutputFeature; 30 | readonly InvalidDataHandlingFeature _invalidDataHandlingFeature; 31 | 32 | static readonly string DefaultOutputTemplate = "[{@t:o} {@l:u3}] {@m:lj} {rest(true)}" + Environment.NewLine + "{@x}"; 33 | 34 | public PipeCommand() 35 | { 36 | _fileInputFeature = Enable(); 37 | _fileOutputFeature = Enable(); 38 | _enrichFeature = Enable(); 39 | _filterFeature = Enable(); 40 | _jsonFormatFeature = Enable(); 41 | _templateFormatFeature = Enable(); 42 | _seqOutputFeature = Enable(); 43 | _invalidDataHandlingFeature = Enable(); 44 | } 45 | 46 | protected override int Run() 47 | { 48 | try 49 | { 50 | var failed = false; 51 | SelfLog.Enable(m => 52 | { 53 | Console.Error.WriteLine(m); 54 | failed = true; 55 | }); 56 | 57 | var levelSwitch = new LoggingLevelSwitch(LevelAlias.Minimum); 58 | var configuration = new LoggerConfiguration() 59 | .MinimumLevel.ControlledBy(levelSwitch); 60 | 61 | foreach (var property in _enrichFeature.Properties) 62 | { 63 | configuration.Enrich.WithProperty(property.Key, property.Value); 64 | } 65 | 66 | if (_filterFeature.Filter != null) 67 | { 68 | configuration.Filter.ByIncludingOnly(_filterFeature.Filter); 69 | } 70 | 71 | if (_seqOutputFeature.SeqUrl != null) 72 | { 73 | configuration.WriteTo.Seq( 74 | _seqOutputFeature.SeqUrl, 75 | apiKey: _seqOutputFeature.SeqApiKey, 76 | batchPostingLimit: _seqOutputFeature.BatchPostingLimit, 77 | eventBodyLimitBytes: _seqOutputFeature.EventBodyLimitBytes, 78 | controlLevelSwitch: levelSwitch); 79 | } 80 | else if (_jsonFormatFeature.UseJsonFormat) 81 | { 82 | if (_fileOutputFeature.OutputFilename != null) 83 | { 84 | configuration.AuditTo.File(new CompactJsonFormatter(), _fileOutputFeature.OutputFilename); 85 | } 86 | else 87 | { 88 | configuration.WriteTo.Console(new CompactJsonFormatter()); 89 | } 90 | } 91 | else 92 | { 93 | var template = _templateFormatFeature.OutputTemplate ?? DefaultOutputTemplate; 94 | if (_fileOutputFeature.OutputFilename != null) 95 | { 96 | var formatter = new ExpressionTemplate(template, CultureInfo.InvariantCulture, new ClefToolNameResolver()); 97 | configuration.AuditTo.File(formatter, _fileOutputFeature.OutputFilename); 98 | } 99 | else 100 | { 101 | var formatter = new ExpressionTemplate(template, CultureInfo.InvariantCulture, new ClefToolNameResolver(), TemplateTheme.Literate); 102 | configuration.WriteTo.Console(formatter); 103 | } 104 | } 105 | 106 | using var logger = configuration.CreateLogger(); 107 | using var inputFile = _fileInputFeature.InputFilename != null 108 | ? new StreamReader(File.Open(_fileInputFeature.InputFilename, FileMode.Open, FileAccess.Read, 109 | FileShare.ReadWrite)) 110 | : null; 111 | using var reader = new LogEventReader(inputFile ?? Console.In); 112 | EventPipe.PipeEvents(reader, logger, _invalidDataHandlingFeature.InvalidDataHandling); 113 | 114 | return failed ? 1 : 0; 115 | } 116 | catch (Exception ex) 117 | { 118 | Console.Error.WriteLine(ex.Message); 119 | return -1; 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Commands/VersionCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Reflection; 17 | 18 | namespace Datalust.ClefTool.Cli.Commands 19 | { 20 | [Command("--version", "Print the current executable version")] 21 | class VersionCommand : Command 22 | { 23 | protected override int Run() 24 | { 25 | var version = Assembly.GetEntryAssembly()!.GetCustomAttribute()!.InformationalVersion; 26 | Console.WriteLine(version); 27 | return 0; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Features/EnrichFeature.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Collections.Generic; 16 | 17 | namespace Datalust.ClefTool.Cli.Features 18 | { 19 | class EnrichFeature : CommandFeature 20 | { 21 | readonly Dictionary _properties = new(); 22 | 23 | public Dictionary Properties => _properties; 24 | 25 | public override void Enable(OptionSet options) 26 | { 27 | options.Add( 28 | "p={=}|property={=}", 29 | "Enrich events with additional properties, e.g. -p Customer=C123 -p Environment=Production", 30 | (n, v) => 31 | { 32 | var name = n.Trim(); 33 | var valueText = v?.Trim(); 34 | if (string.IsNullOrEmpty(valueText)) 35 | _properties.Add(name, null); 36 | else if (decimal.TryParse(valueText, out var numeric)) 37 | _properties.Add(name, numeric); 38 | else 39 | _properties.Add(name, valueText); 40 | }); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Features/FileInputFeature.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Datalust.ClefTool.Cli.Features 16 | { 17 | class FileInputFeature : CommandFeature 18 | { 19 | public string? InputFilename { get; private set; } 20 | 21 | public override void Enable(OptionSet options) 22 | { 23 | options.Add("i=|input=", 24 | "CLEF file to read; if not specified, STDIN will be used", 25 | v => InputFilename = string.IsNullOrWhiteSpace(v) ? null : v.Trim()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Features/FileOutputFeature.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Datalust.ClefTool.Cli.Features 16 | { 17 | class FileOutputFeature : CommandFeature 18 | { 19 | public string? OutputFilename { get; private set; } 20 | 21 | public override void Enable(OptionSet options) 22 | { 23 | options.Add("o=|out-file=", 24 | "Output file; if no output is specified, STDOUT will be used", 25 | v => OutputFilename = string.IsNullOrWhiteSpace(v) ? null : v.Trim()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Features/FilterFeature.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Datalust.ClefTool.Cli.Features 16 | { 17 | class FilterFeature : CommandFeature 18 | { 19 | public string? Filter { get; set; } 20 | 21 | public override void Enable(OptionSet options) 22 | { 23 | options.Add("filter=", 24 | "Filter expression to select a subset of events", 25 | v => Filter = string.IsNullOrWhiteSpace(v) ? null : v.Trim()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Features/InvalidDataHandlingFeature.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using Datalust.ClefTool.Pipe; 17 | 18 | namespace Datalust.ClefTool.Cli.Features 19 | { 20 | class InvalidDataHandlingFeature : CommandFeature 21 | { 22 | public InvalidDataHandling InvalidDataHandling { get; private set; } 23 | 24 | public override void Enable(OptionSet options) 25 | { 26 | options.Add("invalid-data=", 27 | "Specify how invalid data is handled: fail (default), ignore, or report", 28 | v => InvalidDataHandling = (InvalidDataHandling)Enum.Parse(typeof(InvalidDataHandling), v, ignoreCase: true)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Features/JsonFormatFeature.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Datalust.ClefTool.Cli.Features 16 | { 17 | class JsonFormatFeature : CommandFeature 18 | { 19 | public bool UseJsonFormat { get; set; } 20 | 21 | public override void Enable(OptionSet options) 22 | { 23 | options.Add("format-json", 24 | "Format output as CLEF JSON", 25 | v => UseJsonFormat = true); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Features/SeqOutputFeature.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Datalust.ClefTool.Cli.Features 16 | { 17 | class SeqOutputFeature : CommandFeature 18 | { 19 | public string? SeqUrl { get; private set; } 20 | public string? SeqApiKey { get; private set; } 21 | public int BatchPostingLimit { get; private set; } = 100; 22 | public long? EventBodyLimitBytes { get; private set; } = 256 * 1000; 23 | 24 | public override void Enable(OptionSet options) 25 | { 26 | options.Add("out-seq=", 27 | "Send output to Seq at the specified URL", 28 | v => SeqUrl = string.IsNullOrWhiteSpace(v) ? null : v.Trim()); 29 | 30 | options.Add("out-seq-apikey=", 31 | "Specify the API key to use when writing to Seq, if required", 32 | v => SeqApiKey = string.IsNullOrWhiteSpace(v) ? null : v.Trim()); 33 | 34 | options.Add("out-seq-batchpostinglimit=", 35 | "The maximum number of events to post in a single batch", 36 | v => BatchPostingLimit = int.Parse((v ?? "").Trim())); 37 | 38 | options.Add("out-seq-eventbodylimitbytes=", 39 | "The maximum size, in bytes, that the JSON representation of an event may take before it is dropped rather than being sent to the Seq server", 40 | v => EventBodyLimitBytes = string.IsNullOrWhiteSpace(v) ? (long?)null : long.Parse(v.Trim())); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Features/TemplateFormatFeature.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Datalust.ClefTool.Cli.Features 16 | { 17 | class TemplateFormatFeature : CommandFeature 18 | { 19 | public string? OutputTemplate { get; private set; } 20 | 21 | public override void Enable(OptionSet options) 22 | { 23 | options.Add("format-template=", 24 | "Specify an output template to control plain text formatting", 25 | v => OutputTemplate = string.IsNullOrWhiteSpace(v) ? null : v.Trim()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/ICommandMetadata.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Datalust.ClefTool.Cli 16 | { 17 | interface ICommandMetadata 18 | { 19 | string Name { get; } 20 | string HelpText { get; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Options.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Options.cs 3 | // 4 | // Authors: 5 | // Jonathan Pryor 6 | // Federico Di Gregorio 7 | // Rolf Bjarne Kvinge 8 | // 9 | // Copyright (C) 2008 Novell (http://www.novell.com) 10 | // Copyright (C) 2009 Federico Di Gregorio. 11 | // Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com) 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining 14 | // a copy of this software and associated documentation files (the 15 | // "Software"), to deal in the Software without restriction, including 16 | // without limitation the rights to use, copy, modify, merge, publish, 17 | // distribute, sublicense, and/or sell copies of the Software, and to 18 | // permit persons to whom the Software is furnished to do so, subject to 19 | // the following conditions: 20 | // 21 | // The above copyright notice and this permission notice shall be 22 | // included in all copies or substantial portions of the Software. 23 | // 24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 28 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 29 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 30 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | // 32 | 33 | // Compile With: 34 | // gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll 35 | // gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll 36 | // 37 | // The LINQ version just changes the implementation of 38 | // OptionSet.Parse(IEnumerable), and confers no semantic changes. 39 | 40 | // 41 | // A Getopt::Long-inspired option parsing library for C#. 42 | // 43 | // NDesk.Options.OptionSet is built upon a key/value table, where the 44 | // key is a option format string and the value is a delegate that is 45 | // invoked when the format string is matched. 46 | // 47 | // Option format strings: 48 | // Regex-like BNF Grammar: 49 | // name: .+ 50 | // type: [=:] 51 | // sep: ( [^{}]+ | '{' .+ '}' )? 52 | // aliases: ( name type sep ) ( '|' name type sep )* 53 | // 54 | // Each '|'-delimited name is an alias for the associated action. If the 55 | // format string ends in a '=', it has a required value. If the format 56 | // string ends in a ':', it has an optional value. If neither '=' or ':' 57 | // is present, no value is supported. `=' or `:' need only be defined on one 58 | // alias, but if they are provided on more than one they must be consistent. 59 | // 60 | // Each alias portion may also end with a "key/value separator", which is used 61 | // to split option values if the option accepts > 1 value. If not specified, 62 | // it defaults to '=' and ':'. If specified, it can be any character except 63 | // '{' and '}' OR the *string* between '{' and '}'. If no separator should be 64 | // used (i.e. the separate values should be distinct arguments), then "{}" 65 | // should be used as the separator. 66 | // 67 | // Options are extracted either from the current option by looking for 68 | // the option name followed by an '=' or ':', or is taken from the 69 | // following option IFF: 70 | // - The current option does not contain a '=' or a ':' 71 | // - The current option requires a value (i.e. not a Option type of ':') 72 | // 73 | // The `name' used in the option format string does NOT include any leading 74 | // option indicator, such as '-', '--', or '/'. All three of these are 75 | // permitted/required on any named option. 76 | // 77 | // Option bundling is permitted so long as: 78 | // - '-' is used to start the option group 79 | // - all of the bundled options are a single character 80 | // - at most one of the bundled options accepts a value, and the value 81 | // provided starts from the next character to the end of the string. 82 | // 83 | // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' 84 | // as '-Dname=value'. 85 | // 86 | // Option processing is disabled by specifying "--". All options after "--" 87 | // are returned by OptionSet.Parse() unchanged and unprocessed. 88 | // 89 | // Unprocessed options are returned from OptionSet.Parse(). 90 | // 91 | // Examples: 92 | // int verbose = 0; 93 | // OptionSet p = new OptionSet () 94 | // .Add ("v", v => ++verbose) 95 | // .Add ("name=|value=", v => Console.WriteLine (v)); 96 | // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); 97 | // 98 | // The above would parse the argument string array, and would invoke the 99 | // lambda expression three times, setting `verbose' to 3 when complete. 100 | // It would also print out "A" and "B" to standard output. 101 | // The returned array would contain the string "extra". 102 | // 103 | // C# 3.0 collection initializers are supported and encouraged: 104 | // var p = new OptionSet () { 105 | // { "h|?|help", v => ShowHelp () }, 106 | // }; 107 | // 108 | // System.ComponentModel.TypeConverter is also supported, allowing the use of 109 | // custom data types in the callback type; TypeConverter.ConvertFromString() 110 | // is used to convert the value option to an instance of the specified 111 | // type: 112 | // 113 | // var p = new OptionSet () { 114 | // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, 115 | // }; 116 | // 117 | // Random other tidbits: 118 | // - Boolean options (those w/o '=' or ':' in the option format string) 119 | // are explicitly enabled if they are followed with '+', and explicitly 120 | // disabled if they are followed with '-': 121 | // string a = null; 122 | // var p = new OptionSet () { 123 | // { "a", s => a = s }, 124 | // }; 125 | // p.Parse (new string[]{"-a"}); // sets v != null 126 | // p.Parse (new string[]{"-a+"}); // sets v != null 127 | // p.Parse (new string[]{"-a-"}); // sets v == null 128 | // 129 | 130 | using System; 131 | using System.Collections; 132 | using System.Collections.Generic; 133 | using System.Collections.ObjectModel; 134 | using System.IO; 135 | using System.Reflection; 136 | using System.Text; 137 | using System.Text.RegularExpressions; 138 | 139 | #nullable disable 140 | 141 | #if LINQ 142 | using System.Linq; 143 | #endif 144 | 145 | #if TEST 146 | using NDesk.Options; 147 | #endif 148 | 149 | #if NDESK_OPTIONS 150 | namespace NDesk.Options 151 | #else 152 | namespace Datalust.ClefTool.Cli 153 | #endif 154 | { 155 | public delegate U Converter(T t); 156 | 157 | static class StringCoda { 158 | 159 | public static IEnumerable WrappedLines (string self, params int[] widths) 160 | { 161 | IEnumerable w = widths; 162 | return WrappedLines (self, w); 163 | } 164 | 165 | public static IEnumerable WrappedLines (string self, IEnumerable widths) 166 | { 167 | if (widths == null) 168 | throw new ArgumentNullException (nameof(widths)); 169 | return CreateWrappedLinesIterator (self, widths); 170 | } 171 | 172 | private static IEnumerable CreateWrappedLinesIterator (string self, IEnumerable widths) 173 | { 174 | if (string.IsNullOrEmpty (self)) { 175 | yield return string.Empty; 176 | yield break; 177 | } 178 | using (IEnumerator ewidths = widths.GetEnumerator ()) { 179 | bool? hw = null; 180 | int width = GetNextWidth (ewidths, int.MaxValue, ref hw); 181 | int start = 0, end; 182 | do { 183 | end = GetLineEnd (start, width, self); 184 | char c = self [end-1]; 185 | if (char.IsWhiteSpace (c)) 186 | --end; 187 | bool needContinuation = end != self.Length && !IsEolChar (c); 188 | string continuation = ""; 189 | if (needContinuation) { 190 | --end; 191 | continuation = "-"; 192 | } 193 | string line = self.Substring (start, end - start) + continuation; 194 | yield return line; 195 | start = end; 196 | if (char.IsWhiteSpace (c)) 197 | ++start; 198 | width = GetNextWidth (ewidths, width, ref hw); 199 | } while (start < self.Length); 200 | } 201 | } 202 | 203 | private static int GetNextWidth (IEnumerator ewidths, int curWidth, ref bool? eValid) 204 | { 205 | if (!eValid.HasValue || (eValid.HasValue && eValid.Value)) { 206 | curWidth = (eValid = ewidths.MoveNext ()).Value ? ewidths.Current : curWidth; 207 | // '.' is any character, - is for a continuation 208 | const string minWidth = ".-"; 209 | if (curWidth < minWidth.Length) 210 | throw new ArgumentOutOfRangeException ("widths", 211 | string.Format ("Element must be >= {0}, was {1}.", minWidth.Length, curWidth)); 212 | return curWidth; 213 | } 214 | // no more elements, use the last element. 215 | return curWidth; 216 | } 217 | 218 | private static bool IsEolChar (char c) 219 | { 220 | return !char.IsLetterOrDigit (c); 221 | } 222 | 223 | private static int GetLineEnd (int start, int length, string description) 224 | { 225 | int end = System.Math.Min (start + length, description.Length); 226 | int sep = -1; 227 | for (int i = start; i < end; ++i) { 228 | if (description [i] == '\n') 229 | return i+1; 230 | if (IsEolChar (description [i])) 231 | sep = i+1; 232 | } 233 | if (sep == -1 || end == description.Length) 234 | return end; 235 | return sep; 236 | } 237 | } 238 | 239 | public class OptionValueCollection : IList, IList { 240 | 241 | List values = new List (); 242 | OptionContext c; 243 | 244 | internal OptionValueCollection (OptionContext c) 245 | { 246 | this.c = c; 247 | } 248 | 249 | #region ICollection 250 | void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} 251 | bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} 252 | object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} 253 | #endregion 254 | 255 | #region ICollection 256 | public void Add (string item) {values.Add (item);} 257 | public void Clear () {values.Clear ();} 258 | public bool Contains (string item) {return values.Contains (item);} 259 | public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} 260 | public bool Remove (string item) {return values.Remove (item);} 261 | public int Count {get {return values.Count;}} 262 | public bool IsReadOnly {get {return false;}} 263 | #endregion 264 | 265 | #region IEnumerable 266 | IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} 267 | #endregion 268 | 269 | #region IEnumerable 270 | public IEnumerator GetEnumerator () {return values.GetEnumerator ();} 271 | #endregion 272 | 273 | #region IList 274 | int IList.Add (object value) {return (values as IList).Add (value);} 275 | bool IList.Contains (object value) {return (values as IList).Contains (value);} 276 | int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} 277 | void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} 278 | void IList.Remove (object value) {(values as IList).Remove (value);} 279 | void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} 280 | bool IList.IsFixedSize {get {return false;}} 281 | object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} 282 | #endregion 283 | 284 | #region IList 285 | public int IndexOf (string item) {return values.IndexOf (item);} 286 | public void Insert (int index, string item) {values.Insert (index, item);} 287 | public void RemoveAt (int index) {values.RemoveAt (index);} 288 | 289 | private void AssertValid (int index) 290 | { 291 | if (c.Option == null) 292 | throw new InvalidOperationException ("OptionContext.Option is null."); 293 | if (index >= c.Option.MaxValueCount) 294 | throw new ArgumentOutOfRangeException (nameof(index)); 295 | if (c.Option.OptionValueType == OptionValueType.Required && 296 | index >= values.Count) 297 | throw new OptionException (string.Format ( 298 | c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), 299 | c.OptionName); 300 | } 301 | 302 | public string this [int index] { 303 | get { 304 | AssertValid (index); 305 | return index >= values.Count ? null : values [index]; 306 | } 307 | set { 308 | values [index] = value; 309 | } 310 | } 311 | #endregion 312 | 313 | public List ToList () 314 | { 315 | return new List (values); 316 | } 317 | 318 | public string[] ToArray () 319 | { 320 | return values.ToArray (); 321 | } 322 | 323 | public override string ToString () 324 | { 325 | return string.Join (", ", values.ToArray ()); 326 | } 327 | } 328 | 329 | public class OptionContext { 330 | private Option option; 331 | private string name; 332 | private int index; 333 | private OptionSet set; 334 | private OptionValueCollection c; 335 | 336 | public OptionContext (OptionSet set) 337 | { 338 | this.set = set; 339 | this.c = new OptionValueCollection (this); 340 | } 341 | 342 | public Option Option { 343 | get {return option;} 344 | set {option = value;} 345 | } 346 | 347 | public string OptionName { 348 | get {return name;} 349 | set {name = value;} 350 | } 351 | 352 | public int OptionIndex { 353 | get {return index;} 354 | set {index = value;} 355 | } 356 | 357 | public OptionSet OptionSet { 358 | get {return set;} 359 | } 360 | 361 | public OptionValueCollection OptionValues { 362 | get {return c;} 363 | } 364 | } 365 | 366 | public enum OptionValueType { 367 | None, 368 | Optional, 369 | Required, 370 | } 371 | 372 | public abstract class Option { 373 | string prototype, description; 374 | string[] names; 375 | OptionValueType type; 376 | int count; 377 | string[] separators; 378 | bool hidden; 379 | 380 | protected Option (string prototype, string description) 381 | : this (prototype, description, 1, false) 382 | { 383 | } 384 | 385 | protected Option (string prototype, string description, int maxValueCount) 386 | : this (prototype, description, maxValueCount, false) 387 | { 388 | } 389 | 390 | protected Option (string prototype, string description, int maxValueCount, bool hidden) 391 | { 392 | if (prototype == null) 393 | throw new ArgumentNullException (nameof(prototype)); 394 | if (prototype.Length == 0) 395 | throw new ArgumentException ("Cannot be the empty string.", nameof(prototype)); 396 | if (maxValueCount < 0) 397 | throw new ArgumentOutOfRangeException (nameof(maxValueCount)); 398 | 399 | this.prototype = prototype; 400 | this.description = description; 401 | this.count = maxValueCount; 402 | this.names = (this is OptionSet.Category) 403 | // append GetHashCode() so that "duplicate" categories have distinct 404 | // names, e.g. adding multiple "" categories should be valid. 405 | ? new[]{prototype + this.GetHashCode ()} 406 | : prototype.Split ('|'); 407 | 408 | if (this is OptionSet.Category) 409 | return; 410 | 411 | this.type = ParsePrototype (); 412 | this.hidden = hidden; 413 | 414 | if (this.count == 0 && type != OptionValueType.None) 415 | throw new ArgumentException ( 416 | "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + 417 | "OptionValueType.Optional.", 418 | nameof(maxValueCount)); 419 | if (this.type == OptionValueType.None && maxValueCount > 1) 420 | throw new ArgumentException ( 421 | string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), 422 | nameof(maxValueCount)); 423 | if (Array.IndexOf (names, "<>") >= 0 && 424 | ((names.Length == 1 && this.type != OptionValueType.None) || 425 | (names.Length > 1 && this.MaxValueCount > 1))) 426 | throw new ArgumentException ( 427 | "The default option handler '<>' cannot require values.", 428 | nameof(prototype)); 429 | } 430 | 431 | public string Prototype {get {return prototype;}} 432 | public string Description {get {return description;}} 433 | public OptionValueType OptionValueType {get {return type;}} 434 | public int MaxValueCount {get {return count;}} 435 | public bool Hidden {get {return hidden;}} 436 | 437 | public string[] GetNames () 438 | { 439 | return (string[]) names.Clone (); 440 | } 441 | 442 | public string[] GetValueSeparators () 443 | { 444 | if (separators == null) 445 | return new string [0]; 446 | return (string[]) separators.Clone (); 447 | } 448 | 449 | protected static T Parse (string value, OptionContext c) 450 | { 451 | var tt = typeof (T).GetTypeInfo(); 452 | bool nullable = tt.IsValueType && tt.IsGenericType && 453 | !tt.IsGenericTypeDefinition && 454 | tt.GetGenericTypeDefinition () == typeof (Nullable<>); 455 | Type targetType = nullable ? tt.GetGenericArguments () [0] : typeof (T); 456 | T t = default (T); 457 | try { 458 | if (value != null) 459 | t = (T) Convert.ChangeType(value, targetType); 460 | } 461 | catch (Exception e) { 462 | throw new OptionException ( 463 | string.Format ( 464 | c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), 465 | value, targetType.Name, c.OptionName), 466 | c.OptionName, e); 467 | } 468 | return t; 469 | } 470 | 471 | internal string[] Names {get {return names;}} 472 | internal string[] ValueSeparators {get {return separators;}} 473 | 474 | static readonly char[] NameTerminator = new char[]{'=', ':'}; 475 | 476 | private OptionValueType ParsePrototype () 477 | { 478 | char type = '\0'; 479 | List seps = new List (); 480 | for (int i = 0; i < names.Length; ++i) { 481 | string name = names [i]; 482 | if (name.Length == 0) 483 | throw new ArgumentException ("Empty option names are not supported.", "prototype"); 484 | 485 | int end = name.IndexOfAny (NameTerminator); 486 | if (end == -1) 487 | continue; 488 | names [i] = name.Substring (0, end); 489 | if (type == '\0' || type == name [end]) 490 | type = name [end]; 491 | else 492 | throw new ArgumentException ( 493 | string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), 494 | "prototype"); 495 | AddSeparators (name, end, seps); 496 | } 497 | 498 | if (type == '\0') 499 | return OptionValueType.None; 500 | 501 | if (count <= 1 && seps.Count != 0) 502 | throw new ArgumentException ( 503 | string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), 504 | "prototype"); 505 | if (count > 1) { 506 | if (seps.Count == 0) 507 | this.separators = new string[]{":", "="}; 508 | else if (seps.Count == 1 && seps [0].Length == 0) 509 | this.separators = null; 510 | else 511 | this.separators = seps.ToArray (); 512 | } 513 | 514 | return type == '=' ? OptionValueType.Required : OptionValueType.Optional; 515 | } 516 | 517 | private static void AddSeparators (string name, int end, ICollection seps) 518 | { 519 | int start = -1; 520 | for (int i = end+1; i < name.Length; ++i) { 521 | switch (name [i]) { 522 | case '{': 523 | if (start != -1) 524 | throw new ArgumentException ( 525 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 526 | "prototype"); 527 | start = i+1; 528 | break; 529 | case '}': 530 | if (start == -1) 531 | throw new ArgumentException ( 532 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 533 | "prototype"); 534 | seps.Add (name.Substring (start, i-start)); 535 | start = -1; 536 | break; 537 | default: 538 | if (start == -1) 539 | seps.Add (name [i].ToString ()); 540 | break; 541 | } 542 | } 543 | if (start != -1) 544 | throw new ArgumentException ( 545 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name), 546 | "prototype"); 547 | } 548 | 549 | public void Invoke (OptionContext c) 550 | { 551 | OnParseComplete (c); 552 | c.OptionName = null; 553 | c.Option = null; 554 | c.OptionValues.Clear (); 555 | } 556 | 557 | protected abstract void OnParseComplete (OptionContext c); 558 | 559 | public override string ToString () 560 | { 561 | return Prototype; 562 | } 563 | } 564 | 565 | public abstract class ArgumentSource { 566 | 567 | protected ArgumentSource () 568 | { 569 | } 570 | 571 | public abstract string[] GetNames (); 572 | public abstract string Description { get; } 573 | public abstract bool GetArguments (string value, out IEnumerable replacement); 574 | 575 | public static IEnumerable GetArgumentsFromFile (string file) 576 | { 577 | return GetArguments (File.OpenText (file), true); 578 | } 579 | 580 | public static IEnumerable GetArguments (TextReader reader) 581 | { 582 | return GetArguments (reader, false); 583 | } 584 | 585 | // Cribbed from mcs/driver.cs:LoadArgs(string) 586 | static IEnumerable GetArguments (TextReader reader, bool close) 587 | { 588 | try { 589 | StringBuilder arg = new StringBuilder (); 590 | 591 | string line; 592 | while ((line = reader.ReadLine ()) != null) { 593 | int t = line.Length; 594 | 595 | for (int i = 0; i < t; i++) { 596 | char c = line [i]; 597 | 598 | if (c == '"' || c == '\'') { 599 | char end = c; 600 | 601 | for (i++; i < t; i++){ 602 | c = line [i]; 603 | 604 | if (c == end) 605 | break; 606 | arg.Append (c); 607 | } 608 | } else if (c == ' ') { 609 | if (arg.Length > 0) { 610 | yield return arg.ToString (); 611 | arg.Length = 0; 612 | } 613 | } else 614 | arg.Append (c); 615 | } 616 | if (arg.Length > 0) { 617 | yield return arg.ToString (); 618 | arg.Length = 0; 619 | } 620 | } 621 | } 622 | finally { 623 | if (close) 624 | reader.Dispose(); 625 | } 626 | } 627 | } 628 | 629 | public class ResponseFileSource : ArgumentSource { 630 | 631 | public override string[] GetNames () 632 | { 633 | return new string[]{"@file"}; 634 | } 635 | 636 | public override string Description { 637 | get {return "Read response file for more options.";} 638 | } 639 | 640 | public override bool GetArguments (string value, out IEnumerable replacement) 641 | { 642 | if (string.IsNullOrEmpty (value) || !value.StartsWith ("@")) { 643 | replacement = null; 644 | return false; 645 | } 646 | replacement = ArgumentSource.GetArgumentsFromFile (value.Substring (1)); 647 | return true; 648 | } 649 | } 650 | 651 | public class OptionException : Exception { 652 | private string option; 653 | 654 | public OptionException () 655 | { 656 | } 657 | 658 | public OptionException (string message, string optionName) 659 | : base (message) 660 | { 661 | this.option = optionName; 662 | } 663 | 664 | public OptionException (string message, string optionName, Exception innerException) 665 | : base (message, innerException) 666 | { 667 | this.option = optionName; 668 | } 669 | 670 | public string OptionName { 671 | get {return this.option;} 672 | } 673 | } 674 | 675 | public delegate void OptionAction (TKey key, TValue value); 676 | 677 | public class OptionSet : KeyedCollection 678 | { 679 | public OptionSet () 680 | : this (delegate (string f) {return f;}) 681 | { 682 | } 683 | 684 | public OptionSet (Converter localizer) 685 | { 686 | this.localizer = localizer; 687 | this.roSources = new ReadOnlyCollection(sources); 688 | } 689 | 690 | Converter localizer; 691 | 692 | public Converter MessageLocalizer { 693 | get {return localizer;} 694 | } 695 | 696 | List sources = new List (); 697 | ReadOnlyCollection roSources; 698 | 699 | public ReadOnlyCollection ArgumentSources { 700 | get {return roSources;} 701 | } 702 | 703 | 704 | protected override string GetKeyForItem (Option item) 705 | { 706 | if (item == null) 707 | throw new ArgumentNullException ("option"); 708 | if (item.Names != null && item.Names.Length > 0) 709 | return item.Names [0]; 710 | // This should never happen, as it's invalid for Option to be 711 | // constructed w/o any names. 712 | throw new InvalidOperationException ("Option has no names!"); 713 | } 714 | 715 | [Obsolete ("Use KeyedCollection.this[string]")] 716 | protected Option GetOptionForName (string option) 717 | { 718 | if (option == null) 719 | throw new ArgumentNullException (nameof(option)); 720 | try { 721 | return base [option]; 722 | } 723 | catch (KeyNotFoundException) { 724 | return null; 725 | } 726 | } 727 | 728 | protected override void InsertItem (int index, Option item) 729 | { 730 | base.InsertItem (index, item); 731 | AddImpl (item); 732 | } 733 | 734 | protected override void RemoveItem (int index) 735 | { 736 | Option p = Items [index]; 737 | base.RemoveItem (index); 738 | // KeyedCollection.RemoveItem() handles the 0th item 739 | for (int i = 1; i < p.Names.Length; ++i) { 740 | Dictionary.Remove (p.Names [i]); 741 | } 742 | } 743 | 744 | protected override void SetItem (int index, Option item) 745 | { 746 | base.SetItem (index, item); 747 | AddImpl (item); 748 | } 749 | 750 | private void AddImpl (Option option) 751 | { 752 | if (option == null) 753 | throw new ArgumentNullException (nameof(option)); 754 | List added = new List (option.Names.Length); 755 | try { 756 | // KeyedCollection.InsertItem/SetItem handle the 0th name. 757 | for (int i = 1; i < option.Names.Length; ++i) { 758 | Dictionary.Add (option.Names [i], option); 759 | added.Add (option.Names [i]); 760 | } 761 | } 762 | catch (Exception) { 763 | foreach (string name in added) 764 | Dictionary.Remove (name); 765 | throw; 766 | } 767 | } 768 | 769 | public OptionSet Add (string header) 770 | { 771 | if (header == null) 772 | throw new ArgumentNullException (nameof(header)); 773 | Add (new Category (header)); 774 | return this; 775 | } 776 | 777 | internal sealed class Category : Option { 778 | 779 | // Prototype starts with '=' because this is an invalid prototype 780 | // (see Option.ParsePrototype(), and thus it'll prevent Category 781 | // instances from being accidentally used as normal options. 782 | public Category (string description) 783 | : base ("=:Category:= " + description, description) 784 | { 785 | } 786 | 787 | protected override void OnParseComplete (OptionContext c) 788 | { 789 | throw new NotSupportedException ("Category.OnParseComplete should not be invoked."); 790 | } 791 | } 792 | 793 | 794 | public new OptionSet Add (Option option) 795 | { 796 | base.Add (option); 797 | return this; 798 | } 799 | 800 | sealed class ActionOption : Option { 801 | Action action; 802 | 803 | public ActionOption (string prototype, string description, int count, Action action) 804 | : this (prototype, description, count, action, false) 805 | { 806 | } 807 | 808 | public ActionOption (string prototype, string description, int count, Action action, bool hidden) 809 | : base (prototype, description, count, hidden) 810 | { 811 | this.action = action ?? throw new ArgumentNullException (nameof(action)); 812 | } 813 | 814 | protected override void OnParseComplete (OptionContext c) 815 | { 816 | action (c.OptionValues); 817 | } 818 | } 819 | 820 | public OptionSet Add (string prototype, Action action) 821 | { 822 | return Add (prototype, null, action); 823 | } 824 | 825 | public OptionSet Add (string prototype, string description, Action action) 826 | { 827 | return Add (prototype, description, action, false); 828 | } 829 | 830 | public OptionSet Add (string prototype, string description, Action action, bool hidden) 831 | { 832 | if (action == null) 833 | throw new ArgumentNullException (nameof(action)); 834 | Option p = new ActionOption (prototype, description, 1, 835 | delegate (OptionValueCollection v) 836 | { 837 | var v0 = v[0]; 838 | if (!string.IsNullOrWhiteSpace(v0)) 839 | { 840 | action(v0); 841 | } 842 | }, hidden); 843 | base.Add (p); 844 | return this; 845 | } 846 | 847 | public OptionSet Add (string prototype, OptionAction action) 848 | { 849 | return Add (prototype, null, action); 850 | } 851 | 852 | public OptionSet Add (string prototype, string description, OptionAction action) 853 | { 854 | return Add (prototype, description, action, false); 855 | } 856 | 857 | public OptionSet Add (string prototype, string description, OptionAction action, bool hidden) { 858 | if (action == null) 859 | throw new ArgumentNullException (nameof(action)); 860 | Option p = new ActionOption (prototype, description, 2, 861 | delegate (OptionValueCollection v) {action (v [0], v [1]);}, hidden); 862 | base.Add (p); 863 | return this; 864 | } 865 | 866 | sealed class ActionOption : Option { 867 | Action action; 868 | 869 | public ActionOption (string prototype, string description, Action action) 870 | : base (prototype, description, 1) 871 | { 872 | this.action = action ?? throw new ArgumentNullException (nameof(action)); 873 | } 874 | 875 | protected override void OnParseComplete (OptionContext c) 876 | { 877 | action (Parse (c.OptionValues [0], c)); 878 | } 879 | } 880 | 881 | sealed class ActionOption : Option { 882 | OptionAction action; 883 | 884 | public ActionOption (string prototype, string description, OptionAction action) 885 | : base (prototype, description, 2) 886 | { 887 | this.action = action ?? throw new ArgumentNullException (nameof(action)); 888 | } 889 | 890 | protected override void OnParseComplete (OptionContext c) 891 | { 892 | action ( 893 | Parse (c.OptionValues [0], c), 894 | Parse (c.OptionValues [1], c)); 895 | } 896 | } 897 | 898 | public OptionSet Add (string prototype, Action action) 899 | { 900 | return Add (prototype, null, action); 901 | } 902 | 903 | public OptionSet Add (string prototype, string description, Action action) 904 | { 905 | return Add (new ActionOption (prototype, description, action)); 906 | } 907 | 908 | public OptionSet Add (string prototype, OptionAction action) 909 | { 910 | return Add (prototype, null, action); 911 | } 912 | 913 | public OptionSet Add (string prototype, string description, OptionAction action) 914 | { 915 | return Add (new ActionOption (prototype, description, action)); 916 | } 917 | 918 | public OptionSet Add (ArgumentSource source) 919 | { 920 | if (source == null) 921 | throw new ArgumentNullException (nameof(source)); 922 | sources.Add (source); 923 | return this; 924 | } 925 | 926 | protected virtual OptionContext CreateOptionContext () 927 | { 928 | return new OptionContext (this); 929 | } 930 | 931 | public List Parse (IEnumerable arguments) 932 | { 933 | if (arguments == null) 934 | throw new ArgumentNullException (nameof(arguments)); 935 | OptionContext c = CreateOptionContext (); 936 | c.OptionIndex = -1; 937 | bool process = true; 938 | List unprocessed = new List (); 939 | Option def = Contains ("<>") ? this ["<>"] : null; 940 | ArgumentEnumerator ae = new ArgumentEnumerator (arguments); 941 | foreach (string argument in ae) { 942 | ++c.OptionIndex; 943 | if (argument == "--") { 944 | process = false; 945 | continue; 946 | } 947 | if (!process) { 948 | Unprocessed (unprocessed, def, c, argument); 949 | continue; 950 | } 951 | if (AddSource (ae, argument)) 952 | continue; 953 | if (!Parse (argument, c)) 954 | Unprocessed (unprocessed, def, c, argument); 955 | } 956 | if (c.Option != null) 957 | c.Option.Invoke (c); 958 | return unprocessed; 959 | } 960 | 961 | class ArgumentEnumerator : IEnumerable { 962 | List> sources = new List> (); 963 | 964 | public ArgumentEnumerator (IEnumerable arguments) 965 | { 966 | sources.Add (arguments.GetEnumerator ()); 967 | } 968 | 969 | public void Add (IEnumerable arguments) 970 | { 971 | sources.Add (arguments.GetEnumerator ()); 972 | } 973 | 974 | public IEnumerator GetEnumerator () 975 | { 976 | do { 977 | IEnumerator c = sources [sources.Count-1]; 978 | if (c.MoveNext ()) 979 | yield return c.Current; 980 | else { 981 | c.Dispose (); 982 | sources.RemoveAt (sources.Count-1); 983 | } 984 | } while (sources.Count > 0); 985 | } 986 | 987 | IEnumerator IEnumerable.GetEnumerator () 988 | { 989 | return GetEnumerator (); 990 | } 991 | } 992 | 993 | bool AddSource (ArgumentEnumerator ae, string argument) 994 | { 995 | foreach (ArgumentSource source in sources) { 996 | IEnumerable replacement; 997 | if (!source.GetArguments (argument, out replacement)) 998 | continue; 999 | ae.Add (replacement); 1000 | return true; 1001 | } 1002 | return false; 1003 | } 1004 | 1005 | private static bool Unprocessed (ICollection extra, Option def, OptionContext c, string argument) 1006 | { 1007 | if (def == null) { 1008 | extra.Add (argument); 1009 | return false; 1010 | } 1011 | c.OptionValues.Add (argument); 1012 | c.Option = def; 1013 | c.Option.Invoke (c); 1014 | return false; 1015 | } 1016 | 1017 | private readonly Regex ValueOption = new Regex ( 1018 | @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); 1019 | 1020 | protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value) 1021 | { 1022 | if (argument == null) 1023 | throw new ArgumentNullException (nameof(argument)); 1024 | 1025 | flag = name = sep = value = null; 1026 | Match m = ValueOption.Match (argument); 1027 | if (!m.Success) { 1028 | return false; 1029 | } 1030 | flag = m.Groups ["flag"].Value; 1031 | name = m.Groups ["name"].Value; 1032 | if (m.Groups ["sep"].Success && m.Groups ["value"].Success) { 1033 | sep = m.Groups ["sep"].Value; 1034 | value = m.Groups ["value"].Value; 1035 | } 1036 | return true; 1037 | } 1038 | 1039 | protected virtual bool Parse (string argument, OptionContext c) 1040 | { 1041 | if (c.Option != null) { 1042 | ParseValue (argument, c); 1043 | return true; 1044 | } 1045 | 1046 | string f, n, s, v; 1047 | if (!GetOptionParts (argument, out f, out n, out s, out v)) 1048 | return false; 1049 | 1050 | Option p; 1051 | if (Contains (n)) { 1052 | p = this [n]; 1053 | c.OptionName = f + n; 1054 | c.Option = p; 1055 | switch (p.OptionValueType) { 1056 | case OptionValueType.None: 1057 | c.OptionValues.Add (n); 1058 | c.Option.Invoke (c); 1059 | break; 1060 | case OptionValueType.Optional: 1061 | case OptionValueType.Required: 1062 | ParseValue (v, c); 1063 | break; 1064 | } 1065 | return true; 1066 | } 1067 | // no match; is it a bool option? 1068 | if (ParseBool (argument, n, c)) 1069 | return true; 1070 | // is it a bundled option? 1071 | if (ParseBundledValue (f, string.Concat (n + s + v), c)) 1072 | return true; 1073 | 1074 | return false; 1075 | } 1076 | 1077 | private void ParseValue (string option, OptionContext c) 1078 | { 1079 | if (option != null) 1080 | foreach (string o in c.Option.ValueSeparators != null 1081 | ? option.Split (c.Option.ValueSeparators, c.Option.MaxValueCount - c.OptionValues.Count, StringSplitOptions.None) 1082 | : new string[]{option}) { 1083 | c.OptionValues.Add (o); 1084 | } 1085 | if (c.OptionValues.Count == c.Option.MaxValueCount || 1086 | c.Option.OptionValueType == OptionValueType.Optional) 1087 | c.Option.Invoke (c); 1088 | else if (c.OptionValues.Count > c.Option.MaxValueCount) { 1089 | throw new OptionException (localizer (string.Format ( 1090 | "Error: Found {0} option values when expecting {1}.", 1091 | c.OptionValues.Count, c.Option.MaxValueCount)), 1092 | c.OptionName); 1093 | } 1094 | } 1095 | 1096 | private bool ParseBool (string option, string n, OptionContext c) 1097 | { 1098 | Option p; 1099 | string rn; 1100 | if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') && 1101 | Contains ((rn = n.Substring (0, n.Length-1)))) { 1102 | p = this [rn]; 1103 | string v = n [n.Length-1] == '+' ? option : null; 1104 | c.OptionName = option; 1105 | c.Option = p; 1106 | c.OptionValues.Add (v); 1107 | p.Invoke (c); 1108 | return true; 1109 | } 1110 | return false; 1111 | } 1112 | 1113 | private bool ParseBundledValue (string f, string n, OptionContext c) 1114 | { 1115 | if (f != "-") 1116 | return false; 1117 | for (int i = 0; i < n.Length; ++i) { 1118 | Option p; 1119 | string opt = f + n [i].ToString (); 1120 | string rn = n [i].ToString (); 1121 | if (!Contains (rn)) { 1122 | if (i == 0) 1123 | return false; 1124 | throw new OptionException (string.Format (localizer ( 1125 | "Cannot bundle unregistered option '{0}'."), opt), opt); 1126 | } 1127 | p = this [rn]; 1128 | switch (p.OptionValueType) { 1129 | case OptionValueType.None: 1130 | Invoke (c, opt, n, p); 1131 | break; 1132 | case OptionValueType.Optional: 1133 | case OptionValueType.Required: { 1134 | string v = n.Substring (i+1); 1135 | c.Option = p; 1136 | c.OptionName = opt; 1137 | ParseValue (v.Length != 0 ? v : null, c); 1138 | return true; 1139 | } 1140 | default: 1141 | throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType); 1142 | } 1143 | } 1144 | return true; 1145 | } 1146 | 1147 | private static void Invoke (OptionContext c, string name, string value, Option option) 1148 | { 1149 | c.OptionName = name; 1150 | c.Option = option; 1151 | c.OptionValues.Add (value); 1152 | option.Invoke (c); 1153 | } 1154 | 1155 | private const int OptionWidth = 29; 1156 | private const int Description_FirstWidth = 80 - OptionWidth; 1157 | private const int Description_RemWidth = 80 - OptionWidth - 2; 1158 | 1159 | public void WriteOptionDescriptions (TextWriter o) 1160 | { 1161 | foreach (Option p in this) { 1162 | int written = 0; 1163 | 1164 | if (p.Hidden) 1165 | continue; 1166 | 1167 | Category c = p as Category; 1168 | if (c != null) { 1169 | WriteDescription (o, p.Description, "", 80, 80); 1170 | continue; 1171 | } 1172 | 1173 | if (!WriteOptionPrototype (o, p, ref written)) 1174 | continue; 1175 | 1176 | if (written < OptionWidth) 1177 | o.Write (new string (' ', OptionWidth - written)); 1178 | else { 1179 | o.WriteLine (); 1180 | o.Write (new string (' ', OptionWidth)); 1181 | } 1182 | 1183 | WriteDescription (o, p.Description, new string (' ', OptionWidth+2), 1184 | Description_FirstWidth -1, Description_RemWidth - 2); 1185 | } 1186 | 1187 | foreach (ArgumentSource s in sources) { 1188 | string[] names = s.GetNames (); 1189 | if (names == null || names.Length == 0) 1190 | continue; 1191 | 1192 | int written = 0; 1193 | 1194 | Write (o, ref written, " "); 1195 | Write (o, ref written, names [0]); 1196 | for (int i = 1; i < names.Length; ++i) { 1197 | Write (o, ref written, ", "); 1198 | Write (o, ref written, names [i]); 1199 | } 1200 | 1201 | if (written < OptionWidth) 1202 | o.Write (new string (' ', OptionWidth - written)); 1203 | else { 1204 | o.WriteLine (); 1205 | o.Write (new string (' ', OptionWidth)); 1206 | } 1207 | 1208 | WriteDescription (o, s.Description, new string (' ', OptionWidth+2), 1209 | Description_FirstWidth, Description_RemWidth); 1210 | } 1211 | } 1212 | 1213 | void WriteDescription (TextWriter o, string value, string prefix, int firstWidth, int remWidth) 1214 | { 1215 | bool indent = false; 1216 | foreach (string line in GetLines (localizer (GetDescription (value)), firstWidth, remWidth)) { 1217 | if (indent) 1218 | o.Write (prefix); 1219 | o.WriteLine (line); 1220 | indent = true; 1221 | } 1222 | } 1223 | 1224 | bool WriteOptionPrototype (TextWriter o, Option p, ref int written) 1225 | { 1226 | string[] names = p.Names; 1227 | 1228 | int i = GetNextOptionIndex (names, 0); 1229 | if (i == names.Length) 1230 | return false; 1231 | 1232 | if (names [i].Length == 1) { 1233 | Write (o, ref written, " -"); 1234 | Write (o, ref written, names [0]); 1235 | } 1236 | else { 1237 | Write (o, ref written, " --"); 1238 | Write (o, ref written, names [0]); 1239 | } 1240 | 1241 | for ( i = GetNextOptionIndex (names, i+1); 1242 | i < names.Length; i = GetNextOptionIndex (names, i+1)) { 1243 | Write (o, ref written, ", "); 1244 | Write (o, ref written, names [i].Length == 1 ? "-" : "--"); 1245 | Write (o, ref written, names [i]); 1246 | } 1247 | 1248 | if (p.OptionValueType == OptionValueType.Optional || 1249 | p.OptionValueType == OptionValueType.Required) { 1250 | if (p.OptionValueType == OptionValueType.Optional) { 1251 | Write (o, ref written, localizer ("[")); 1252 | } 1253 | Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description))); 1254 | string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 1255 | ? p.ValueSeparators [0] 1256 | : " "; 1257 | for (int c = 1; c < p.MaxValueCount; ++c) { 1258 | Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description))); 1259 | } 1260 | if (p.OptionValueType == OptionValueType.Optional) { 1261 | Write (o, ref written, localizer ("]")); 1262 | } 1263 | } 1264 | return true; 1265 | } 1266 | 1267 | static int GetNextOptionIndex (string[] names, int i) 1268 | { 1269 | while (i < names.Length && names [i] == "<>") { 1270 | ++i; 1271 | } 1272 | return i; 1273 | } 1274 | 1275 | static void Write (TextWriter o, ref int n, string s) 1276 | { 1277 | n += s.Length; 1278 | o.Write (s); 1279 | } 1280 | 1281 | private static string GetArgumentName (int index, int maxIndex, string description) 1282 | { 1283 | if (description == null) 1284 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 1285 | string[] nameStart; 1286 | if (maxIndex == 1) 1287 | nameStart = new string[]{"{0:", "{"}; 1288 | else 1289 | nameStart = new string[]{"{" + index + ":"}; 1290 | for (int i = 0; i < nameStart.Length; ++i) { 1291 | int start, j = 0; 1292 | do { 1293 | start = description.IndexOf (nameStart [i], j); 1294 | } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false); 1295 | if (start == -1) 1296 | continue; 1297 | int end = description.IndexOf ("}", start); 1298 | if (end == -1) 1299 | continue; 1300 | return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length); 1301 | } 1302 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 1303 | } 1304 | 1305 | private static string GetDescription (string description) 1306 | { 1307 | if (description == null) 1308 | return string.Empty; 1309 | StringBuilder sb = new StringBuilder (description.Length); 1310 | int start = -1; 1311 | for (int i = 0; i < description.Length; ++i) { 1312 | switch (description [i]) { 1313 | case '{': 1314 | if (i == start) { 1315 | sb.Append ('{'); 1316 | start = -1; 1317 | } 1318 | else if (start < 0) 1319 | start = i + 1; 1320 | break; 1321 | case '}': 1322 | if (start < 0) { 1323 | if ((i+1) == description.Length || description [i+1] != '}') 1324 | throw new InvalidOperationException ("Invalid option description: " + description); 1325 | ++i; 1326 | sb.Append ("}"); 1327 | } 1328 | else { 1329 | sb.Append (description.Substring (start, i - start)); 1330 | start = -1; 1331 | } 1332 | break; 1333 | case ':': 1334 | if (start < 0) 1335 | goto default; 1336 | start = i + 1; 1337 | break; 1338 | default: 1339 | if (start < 0) 1340 | sb.Append (description [i]); 1341 | break; 1342 | } 1343 | } 1344 | return sb.ToString (); 1345 | } 1346 | 1347 | private static IEnumerable GetLines (string description, int firstWidth, int remWidth) 1348 | { 1349 | return StringCoda.WrappedLines (description, firstWidth, remWidth); 1350 | } 1351 | } 1352 | } 1353 | 1354 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Cli/Printing.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.IO; 16 | using System.Linq; 17 | 18 | namespace Datalust.ClefTool.Cli 19 | { 20 | static class Printing 21 | { 22 | const int ConsoleWidth = 80; 23 | 24 | public static void Define(string term, string definition, int termColumnWidth, TextWriter output) 25 | { 26 | var header = term.PadRight(termColumnWidth); 27 | var right = ConsoleWidth - header.Length; 28 | 29 | var rest = definition.ToCharArray(); 30 | while (rest.Any()) 31 | { 32 | var content = new string(rest.Take(right).ToArray()); 33 | if (!string.IsNullOrWhiteSpace(content)) 34 | { 35 | output.Write(header); 36 | header = new string(' ', header.Length); 37 | output.WriteLine(content); 38 | } 39 | rest = rest.Skip(right).ToArray(); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Datalust.ClefTool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | win-x64;linux-x64;linux-musl-x64;osx-x64;osx-arm64 7 | True 8 | True 9 | 10 | x64 11 | clef 12 | ClefTool.ico 13 | enable 14 | Datalust.ClefTool 15 | true 16 | clef 17 | Major 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Pipe/EventPipe.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.IO; 17 | using Newtonsoft.Json; 18 | using Serilog.Core; 19 | using Serilog.Formatting.Compact.Reader; 20 | 21 | namespace Datalust.ClefTool.Pipe 22 | { 23 | static class EventPipe 24 | { 25 | public static void PipeEvents(LogEventReader source, Logger destination, InvalidDataHandling invalidDataHandling) 26 | { 27 | do 28 | { 29 | try 30 | { 31 | while (source.TryRead(out var evt)) 32 | { 33 | destination.Write(evt); 34 | } 35 | 36 | return; 37 | } 38 | catch (Exception ex) 39 | { 40 | if (ex is JsonReaderException || ex is InvalidDataException) 41 | { 42 | if (invalidDataHandling == InvalidDataHandling.Ignore) 43 | continue; 44 | 45 | if (invalidDataHandling == InvalidDataHandling.Report) 46 | { 47 | destination.Error(ex, "An event was not in CLEF format."); 48 | continue; 49 | } 50 | } 51 | 52 | throw; 53 | } 54 | } while (true); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Pipe/InvalidDataHandling.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2017 Datalust Pty Ltd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Datalust.ClefTool.Pipe 16 | { 17 | enum InvalidDataHandling 18 | { 19 | Fail, 20 | Ignore, 21 | Report 22 | } 23 | } -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Program.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Datalust.ClefTool.Cli; 3 | using Datalust.ClefTool.Cli.Commands; 4 | 5 | namespace Datalust.ClefTool 6 | { 7 | static class Program 8 | { 9 | public static int Main(string[] args) 10 | { 11 | var builder = new ContainerBuilder(); 12 | builder.RegisterType(); 13 | builder.RegisterTypes(typeof(PipeCommand), typeof(HelpCommand), typeof(VersionCommand)) 14 | .As() 15 | .WithMetadataFrom(); 16 | 17 | using var container = builder.Build(); 18 | var clh = container.Resolve(); 19 | return clh.Run(args); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Datalust.ClefTool.Tests")] 4 | -------------------------------------------------------------------------------- /src/Datalust.ClefTool/Syntax/ClefToolNameResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reflection; 4 | using Serilog.Events; 5 | using Serilog.Expressions; 6 | 7 | namespace Datalust.ClefTool.Syntax; 8 | 9 | public class ClefToolNameResolver : NameResolver 10 | { 11 | public static LogEventPropertyValue? NewLine() 12 | { 13 | return new ScalarValue(Environment.NewLine); 14 | } 15 | 16 | public override bool TryResolveFunctionName(string name, [NotNullWhen(true)] out MethodInfo? implementation) 17 | { 18 | if (nameof(NewLine).Equals(name, StringComparison.OrdinalIgnoreCase)) 19 | { 20 | implementation = typeof(ClefToolNameResolver).GetMethod(nameof(NewLine), BindingFlags.Public | BindingFlags.Static)!; 21 | return true; 22 | } 23 | 24 | return base.TryResolveFunctionName(name, out implementation); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/Datalust.ClefTool.Tests/ClefToolNameResolverTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Datalust.ClefTool.Syntax; 5 | using Serilog.Events; 6 | using Serilog.Templates; 7 | using Xunit; 8 | 9 | namespace Datalust.ClefTool.Tests; 10 | 11 | public class ClefToolNameResolverTests 12 | { 13 | [Fact] 14 | public void NewlineFunctionEvaluatesToNewlineInTemplates() 15 | { 16 | var template = new ExpressionTemplate("a{newline()}b", nameResolver: new ClefToolNameResolver()); 17 | var output = new StringWriter(); 18 | var evt = new LogEvent(DateTimeOffset.Now, LogEventLevel.Debug, null, MessageTemplate.Empty, 19 | Enumerable.Empty()); 20 | template.Format(evt, output); 21 | var result = output.ToString(); 22 | Assert.Equal($"a{Environment.NewLine}b", result); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /test/Datalust.ClefTool.Tests/Datalust.ClefTool.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/Datalust.ClefTool.Tests/Pipe/EventPipeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Datalust.ClefTool.Pipe; 4 | using Datalust.ClefTool.Tests.Support; 5 | using Newtonsoft.Json; 6 | using Serilog; 7 | using Serilog.Events; 8 | using Serilog.Formatting.Compact.Reader; 9 | using Xunit; 10 | 11 | namespace Datalust.ClefTool.Tests.Pipe 12 | { 13 | public class EventPipeTests 14 | { 15 | static readonly string ClefThreeEvents = 16 | "{\"@t\":\"2017-04-20T04:24:47.0251719Z\",\"@mt\":\"Loop {Counter}\",\"Counter\":0}" + Environment.NewLine + 17 | "{\"@t\":\"2017-04-20T04:24:47.0371689Z\",\"@mt\":\"Loop {Counter}\",\"Counter\":1}" + Environment.NewLine + 18 | "{\"@t\":\"2017-04-20T04:24:47.0371689Z\",\"@mt\":\"Loop {Counter}\",\"Counter\":2}" + Environment.NewLine; 19 | 20 | static readonly string ClefTwoValidOneInvalid = 21 | "{\"@t\":\"2017-04-20T04:24:47.0251719Z\",\"@mt\":\"Loop {Counter}\",\"Counter\":0}" + Environment.NewLine + 22 | "Hello, world!" + Environment.NewLine + 23 | "{\"@t\":\"2017-04-20T04:24:47.0371689Z\",\"@mt\":\"Loop {Counter}\",\"Counter\":2}" + Environment.NewLine; 24 | 25 | [Fact] 26 | public void EventsAreCopiedFromSourceToDestination() 27 | { 28 | var output = PipeEvents(ClefThreeEvents, InvalidDataHandling.Fail); 29 | Assert.Equal(3, output.Length); 30 | } 31 | 32 | [Fact] 33 | public void InFailModeInvalidJsonThrows() 34 | { 35 | Assert.Throws(() => PipeEvents(ClefTwoValidOneInvalid, InvalidDataHandling.Fail)); 36 | } 37 | 38 | [Fact] 39 | public void InIgnoreModeInvalidJsonIsDropped() 40 | { 41 | var output = PipeEvents(ClefTwoValidOneInvalid, InvalidDataHandling.Ignore); 42 | Assert.Equal(2, output.Length); 43 | } 44 | 45 | [Fact] 46 | public void InReportModeInvalidJsonIsReported() 47 | { 48 | var output = PipeEvents(ClefTwoValidOneInvalid, InvalidDataHandling.Report); 49 | Assert.Equal(3, output.Length); 50 | } 51 | 52 | static LogEvent[] PipeEvents(string input, InvalidDataHandling invalidDataHandling) 53 | { 54 | var output = new CollectingSink(); 55 | using (var source = new LogEventReader(new StringReader(input))) 56 | using (var destination = new LoggerConfiguration() 57 | .MinimumLevel.Is(LevelAlias.Minimum) 58 | .WriteTo.Sink(output) 59 | .CreateLogger()) 60 | { 61 | EventPipe.PipeEvents(source, destination, invalidDataHandling); 62 | } 63 | 64 | return output.Events; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/Datalust.ClefTool.Tests/Support/CollectingSink.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Serilog.Core; 3 | using Serilog.Events; 4 | 5 | namespace Datalust.ClefTool.Tests.Support 6 | { 7 | public class CollectingSink : ILogEventSink 8 | { 9 | readonly object _sync = new object(); 10 | readonly List _events = new List(); 11 | 12 | public void Emit(LogEvent logEvent) 13 | { 14 | lock (_sync) 15 | _events.Add(logEvent); 16 | } 17 | 18 | public LogEvent[] Events 19 | { 20 | get 21 | { 22 | lock (_sync) 23 | return _events.ToArray(); 24 | } 25 | } 26 | } 27 | } 28 | --------------------------------------------------------------------------------