├── .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 [](https://ci.appveyor.com/project/datalust/clef-tool) [](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 |
--------------------------------------------------------------------------------