From 2065e2ca157f8a9f87b4916255c4f67256bd1349 Mon Sep 17 00:00:00 2001 From: Milan Djurdjevic Date: Sun, 2 Feb 2025 22:08:09 +0100 Subject: [PATCH] Refactoring - Pull up the sln file to the root directory. - Add more code documentation. - Refactor entry parsing. - Refactor filter evaluation. - Adjust GitHub action to work with the new sln path. - Centralize package versioning. --- src/.editorconfig => .editorconfig | 0 .github/workflows/main.yml | 30 +- .gitignore | 485 +++++++++++++++++- Directory.Packages.props | 20 + analog.sln | 28 + src/.gitignore | 484 ----------------- src/Analog.Tests/Program.fs | 1 - src/Analog.fsproj | 23 + src/Analog/Analog.fsproj | 22 - src/Analog/Core.fs | 208 -------- src/Filter.fs | 119 +++++ src/Log.fs | 91 ++++ src/{Analog => }/Program.fs | 35 +- src/analog.sln | 51 -- .../Analog.Tests => test}/Analog.Tests.fsproj | 12 +- {src/Analog.Tests => test}/EntryParserTest.fs | 9 +- .../FilterEvaluatorTest.fs | 71 +-- .../Analog.Tests => test}/FilterParserTest.fs | 21 +- test/Program.fs | 3 + 19 files changed, 859 insertions(+), 854 deletions(-) rename src/.editorconfig => .editorconfig (100%) create mode 100644 Directory.Packages.props create mode 100644 analog.sln delete mode 100644 src/.gitignore delete mode 100644 src/Analog.Tests/Program.fs create mode 100644 src/Analog.fsproj delete mode 100644 src/Analog/Analog.fsproj delete mode 100644 src/Analog/Core.fs create mode 100644 src/Filter.fs create mode 100644 src/Log.fs rename src/{Analog => }/Program.fs (65%) delete mode 100644 src/analog.sln rename {src/Analog.Tests => test}/Analog.Tests.fsproj (68%) rename {src/Analog.Tests => test}/EntryParserTest.fs (89%) rename {src/Analog.Tests => test}/FilterEvaluatorTest.fs (55%) rename {src/Analog.Tests => test}/FilterParserTest.fs (77%) create mode 100644 test/Program.fs diff --git a/src/.editorconfig b/.editorconfig similarity index 100% rename from src/.editorconfig rename to .editorconfig diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f28a66d..8af5295 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,15 +17,15 @@ jobs: with: dotnet-version: 8.0.x - name: Restore - run: dotnet restore src + run: dotnet restore - name: Build - run: dotnet build src --no-restore --configuration Release + run: dotnet build --no-restore --configuration Release - name: Test - run: dotnet test src --no-build --configuration Release --verbosity normal + run: dotnet test --no-build --configuration Release --verbosity normal - name: Publish - run: dotnet publish src/Analog --configuration Release -p:PublishSingleFile=true --self-contained + run: dotnet publish src --configuration Release -p:PublishSingleFile=true --self-contained - name: Zip - run: zip /home/runner/work/analog/analog-linux-x64 /home/runner/work/analog/analog/src/Analog/bin/Release/net8.0/linux-x64/publish/* + run: zip /home/runner/work/analog/analog-linux-x64 /home/runner/work/analog/analog/src/bin/Release/net8.0/linux-x64/publish/* - name: Upload uses: actions/upload-artifact@v4 with: @@ -41,16 +41,16 @@ jobs: with: dotnet-version: 8.0.x - name: Restore - run: dotnet restore src + run: dotnet restore - name: Build - run: dotnet build src --no-restore --configuration Release + run: dotnet build --no-restore --configuration Release - name: Test - run: dotnet test src --no-build --configuration Release --verbosity normal + run: dotnet test --no-build --configuration Release --verbosity normal - name: Publish - run: dotnet publish src/Analog --configuration Release -p:PublishSingleFile=true --self-contained + run: dotnet publish src --configuration Release -p:PublishSingleFile=true --self-contained - name: Zip shell: pwsh - run: Compress-Archive -Path D:\a\analog\analog\src\Analog\bin\Release\net8.0\win-x64\publish\* -DestinationPath D:\a\analog\analog-win-x64.zip + run: Compress-Archive -Path D:\a\analog\analog\src\bin\Release\net8.0\win-x64\publish\* -DestinationPath D:\a\analog\analog-win-x64.zip - name: Upload uses: actions/upload-artifact@v4 with: @@ -66,15 +66,15 @@ jobs: with: dotnet-version: 8.0.x - name: Restore - run: dotnet restore src + run: dotnet restore - name: Build - run: dotnet build src --no-restore --configuration Release + run: dotnet build --no-restore --configuration Release - name: Test - run: dotnet test src --no-build --configuration Release --verbosity normal + run: dotnet test --no-build --configuration Release --verbosity normal - name: Publish - run: dotnet publish src/Analog --configuration Release -p:PublishSingleFile=true --self-contained + run: dotnet publish src --configuration Release -p:PublishSingleFile=true --self-contained - name: Zip - run: zip /Users/runner/work/analog/analog-osx-arm64 /Users/runner/work/analog/analog/src/Analog/bin/Release/net8.0/osx-arm64/publish/* + run: zip /Users/runner/work/analog/analog-osx-arm64 /Users/runner/work/analog/analog/src/bin/Release/net8.0/osx-arm64/publish/* - name: Upload uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index c6a47aa..104b544 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,484 @@ -.vscode/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory .vs/ -.idea/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..ec9aec9 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,20 @@ + + + true + false + + + + + + + + + + + + + + + + diff --git a/analog.sln b/analog.sln new file mode 100644 index 0000000..43553cc --- /dev/null +++ b/analog.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Analog", "src\Analog.fsproj", "{ED3EB416-2460-42A1-B521-0051AF617048}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Analog.Tests", "test\Analog.Tests.fsproj", "{416AF7DE-7D0F-4CB0-B886-97399D37C530}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED3EB416-2460-42A1-B521-0051AF617048}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED3EB416-2460-42A1-B521-0051AF617048}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED3EB416-2460-42A1-B521-0051AF617048}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED3EB416-2460-42A1-B521-0051AF617048}.Release|Any CPU.Build.0 = Release|Any CPU + {416AF7DE-7D0F-4CB0-B886-97399D37C530}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {416AF7DE-7D0F-4CB0-B886-97399D37C530}.Debug|Any CPU.Build.0 = Debug|Any CPU + {416AF7DE-7D0F-4CB0-B886-97399D37C530}.Release|Any CPU.ActiveCfg = Release|Any CPU + {416AF7DE-7D0F-4CB0-B886-97399D37C530}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index 104b544..0000000 --- a/src/.gitignore +++ /dev/null @@ -1,484 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from `dotnet new gitignore` - -# dotenv files -.env - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET -project.lock.json -project.fragment.lock.json -artifacts/ - -# Tye -.tye/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml -.idea - -## -## Visual studio for Mac -## - - -# globs -Makefile.in -*.userprefs -*.usertasks -config.make -config.status -aclocal.m4 -install-sh -autom4te.cache/ -*.tar.gz -tarballs/ -test-results/ - -# Mac bundle stuff -*.dmg -*.app - -# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Vim temporary swap files -*.swp diff --git a/src/Analog.Tests/Program.fs b/src/Analog.Tests/Program.fs deleted file mode 100644 index 0695f84..0000000 --- a/src/Analog.Tests/Program.fs +++ /dev/null @@ -1 +0,0 @@ -module Program = let [] main _ = 0 diff --git a/src/Analog.fsproj b/src/Analog.fsproj new file mode 100644 index 0000000..9d944d5 --- /dev/null +++ b/src/Analog.fsproj @@ -0,0 +1,23 @@ + + + + net8.0 + Exe + + + + + + + + + + + + + + + + + + diff --git a/src/Analog/Analog.fsproj b/src/Analog/Analog.fsproj deleted file mode 100644 index 0a0c3d4..0000000 --- a/src/Analog/Analog.fsproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net8.0 - Exe - - - - - - - - - - - - - - - - - diff --git a/src/Analog/Core.fs b/src/Analog/Core.fs deleted file mode 100644 index 46f89ca..0000000 --- a/src/Analog/Core.fs +++ /dev/null @@ -1,208 +0,0 @@ -namespace Analog - -open System - -type Literal = - | String of string - | Number of float - | Boolean of bool - | Timestamp of DateTimeOffset - -type Entry = Map - -type Operator = - | Equal - | NotEqual - | GreaterThan - | GreaterThanOrEqual - | LessThan - | LessThanOrEqual - | And - | Or - -type Filter = - | Const of Literal - | Field of string - | Binary of Filter * Operator * Filter - -module ParserRunner = - open FParsec - - let run parser = - run parser - >> function - | Success(value, _, _) -> Result.Ok value - | Failure(error, _, _) -> Result.Error error - - let tryRun parser = - run parser - >> function - | Result.Ok value -> Option.Some value - | Result.Error _ -> Option.None - -module LiteralParser = - open FParsec - - let number: Parser<_, unit> = - pfloat - >>= fun res -> - if Double.IsInfinity res || Double.IsNaN res then - fail "Number cannot be infinite or NaN" - else - preturn res - |> attempt - |>> Literal.Number - - let boolean: Parser<_, unit> = - choice [ pstringCI "true" >>% true; pstringCI "false" >>% false ] - |>> Literal.Boolean - - let timestamp: Parser<_, unit> = - restOfLine false - >>= fun input -> - try - DateTimeOffset.Parse input |> preturn |>> Literal.Timestamp - with err -> - fail err.Message - |> attempt - - let string: Parser<_, unit> = restOfLine true |>> Literal.String - - let literal: Parser<_, unit> = choice [ timestamp; number; boolean; string ] - -module EntryParser = - open GrokNet - - type private RawEntry = Map - - let create txt = - try - Grok txt |> Result.Ok - with err -> - Result.Error err.Message - - let value = - "\[%{TIMESTAMP_ISO8601:timestamp}\] \[%{LOGLEVEL:loglevel}\] %{GREEDYDATA:message}" - |> Grok - - let private group list (key, value) = - match list with - | [] -> [ Map([ key, value ]) ] - | head :: tail -> - if head |> Map.containsKey key then - [ Map([ key, value ]); head ] @ tail - else - (head |> Map.add key value) :: tail - - let private parseRaw (entry: RawEntry) = - entry - |> Map.map (fun _ -> ParserRunner.tryRun LiteralParser.literal) - |> Map.filter (fun _ value -> value |> Option.isSome) - |> Map.map (fun _ value -> value |> Option.get) - - let parse text (grok: Grok) : Entry list = - grok.Parse text - |> Seq.map (fun i -> i.Key, i.Value.ToString()) - |> Seq.fold group List.empty - |> List.map parseRaw - |> List.rev - -module FilterParser = - open FParsec - - let string: Parser<_, unit> = - skipChar '\'' >>. manyCharsTill anyChar (skipChar '\'') |>> String .>> spaces - - let number: Parser<_, unit> = pfloat |>> Literal.Number .>> spaces - - let boolean: Parser<_, unit> = LiteralParser.boolean .>> spaces - - let timestamp: Parser<_, unit> = LiteralParser.timestamp .>> spaces - - let constant: Parser<_, unit> = - choice [ string; timestamp; number; boolean ] |>> Const - - let field: Parser<_, unit> = many1Chars (letter <|> digit) |>> Field .>> spaces - - let term: Parser<_, unit> = choice [ constant; field ] - - let expression: Parser<_, unit> = - let precedence = OperatorPrecedenceParser() - precedence.TermParser <- choice [ constant; field ] - let add = precedence.AddOperator - let binary operator left right = Binary(left, operator, right) - add (InfixOperator("&", spaces, 1, Associativity.Left, binary And)) - add (InfixOperator("|", spaces, 2, Associativity.Left, binary Or)) - add (InfixOperator(">", spaces, 3, Associativity.None, binary GreaterThan)) - add (InfixOperator(">=", spaces, 4, Associativity.None, binary GreaterThanOrEqual)) - add (InfixOperator("<", spaces, 5, Associativity.None, binary LessThan)) - add (InfixOperator("<=", spaces, 6, Associativity.None, binary LessThanOrEqual)) - add (InfixOperator("=", spaces, 7, Associativity.None, binary Equal)) - add (InfixOperator("<>", spaces, 8, Associativity.None, binary NotEqual)) - precedence.ExpressionParser - -module FilterEvaluator = - - type private Eval = - | Temp of Literal option - | Final of bool - - let private compareLiteral (left: Literal option) (right: Literal option) comparer = - match left, right with - | Some left, Some right -> - match left, right with - | String _, String _ -> comparer left right - | Number _, Number _ -> comparer left right - | Boolean _, Boolean _ -> comparer left right - | Timestamp _, Timestamp _ -> comparer left right - | _ -> false - | _ -> false - - let private combineLiteral (left: Literal option) (right: Literal option) combiner = - match left, right with - | Some left, Some right -> - match left, right with - | Boolean left, Boolean right -> combiner left right - | _ -> false - | _ -> false - - let private wrapFinal = Literal.Boolean >> Option.Some - - let private compareEvaluation (left: Eval) (right: Eval) comparer = - match left, right with - | Temp left, Temp right -> compareLiteral left right comparer - | Temp left, Final right -> compareLiteral left (wrapFinal right) comparer - | Final left, Temp right -> compareLiteral (wrapFinal left) right comparer - | Final left, Final right -> compareLiteral (wrapFinal left) (wrapFinal right) comparer - - let private combineEvaluation (left: Eval) (right: Eval) combiner = - match left, right with - | Temp left, Temp right -> combineLiteral left right combiner - | Temp left, Final right -> combineLiteral left (wrapFinal right) combiner - | Final left, Temp right -> combineLiteral (wrapFinal left) right combiner - | Final left, Final right -> combineLiteral (wrapFinal left) (wrapFinal right) combiner - - let private evalOperator (left: Eval) (operator: Operator) (right: Eval) = - match operator with - | Equal -> compareEvaluation left right (=) - | NotEqual -> compareEvaluation left right (<>) - | GreaterThan -> compareEvaluation left right (>) - | GreaterThanOrEqual -> compareEvaluation left right (>=) - | LessThan -> compareEvaluation left right (<) - | LessThanOrEqual -> compareEvaluation left right (<=) - | And -> combineEvaluation left right (&&) - | Or -> combineEvaluation left right (||) - - let rec private eval (expression: Filter) (entry: Entry) : Eval = - match expression with - | Filter.Const right -> right |> Option.Some |> Eval.Temp - | Filter.Field field -> entry |> Map.tryFind field |> Eval.Temp - | Filter.Binary(left, operator, right) -> - let left = eval left entry - let right = eval right entry - evalOperator left operator right |> Eval.Final - - let evaluate entry expression = - match eval expression entry with - | Temp temp -> temp |> Option.isSome - | Final final -> final \ No newline at end of file diff --git a/src/Filter.fs b/src/Filter.fs new file mode 100644 index 0000000..bf8cf47 --- /dev/null +++ b/src/Filter.fs @@ -0,0 +1,119 @@ +module Analog.Filter + +open FParsec +open Log + +type Operator = + | Equal + | NotEqual + | GreaterThan + | GreaterThanOrEqual + | LessThan + | LessThanOrEqual + | And + | Or + +type Expression = + | Const of Literal + | Field of string + | Binary of Expression * Operator * Expression + +let stringParser: Parser<_, unit> = + skipChar '\'' >>. manyCharsTill anyChar (skipChar '\'') |>> String .>> spaces + +let numberParser: Parser<_, unit> = pfloat |>> Literal.Number .>> spaces + +let boolParser: Parser<_, unit> = boolParser .>> spaces + +let timestampParser: Parser<_, unit> = timestampParser .>> spaces + +let literalParser: Parser<_, unit> = + choice [ stringParser; timestampParser; numberParser; boolParser ] + |>> Expression.Const + +let fieldParser: Parser<_, unit> = + many1Chars (letter <|> digit) |>> Field .>> spaces + +let termParser: Parser<_, unit> = choice [ literalParser; fieldParser ] + +let expressionParser: Parser<_, unit> = + let precedence = OperatorPrecedenceParser() + precedence.TermParser <- termParser + let add = precedence.AddOperator + let binary operator left right = Binary(left, operator, right) + add (InfixOperator("&", spaces, 1, Associativity.Left, binary And)) + add (InfixOperator("|", spaces, 2, Associativity.Left, binary Or)) + add (InfixOperator(">", spaces, 3, Associativity.None, binary GreaterThan)) + add (InfixOperator(">=", spaces, 4, Associativity.None, binary GreaterThanOrEqual)) + add (InfixOperator("<", spaces, 5, Associativity.None, binary LessThan)) + add (InfixOperator("<=", spaces, 6, Associativity.None, binary LessThanOrEqual)) + add (InfixOperator("=", spaces, 7, Associativity.None, binary Equal)) + add (InfixOperator("<>", spaces, 8, Associativity.None, binary NotEqual)) + precedence.ExpressionParser + +type Eval = + | Temporary of Literal option + | Final of bool + +let expressionEvaluator = + let compareLiteral (left: Literal option) (right: Literal option) comparer = + match left, right with + | Some left, Some right -> + match left, right with + | String _, String _ -> comparer left right + | Number _, Number _ -> comparer left right + | Boolean _, Boolean _ -> comparer left right + | Timestamp _, Timestamp _ -> comparer left right + | _ -> false + | _ -> false + + let combineLiteral (left: Literal option) (right: Literal option) combiner = + match left, right with + | Some left, Some right -> + match left, right with + | Boolean left, Boolean right -> combiner left right + | _ -> false + | _ -> false + + let finalizeEval = Literal.Boolean >> Option.Some + + let compareEval (left: Eval) (right: Eval) comparer = + match left, right with + | Temporary left, Temporary right -> compareLiteral left right comparer + | Temporary left, Final right -> compareLiteral left (finalizeEval right) comparer + | Final left, Temporary right -> compareLiteral (finalizeEval left) right comparer + | Final left, Final right -> compareLiteral (finalizeEval left) (finalizeEval right) comparer + + let combineEval (left: Eval) (right: Eval) combiner = + match left, right with + | Temporary left, Temporary right -> combineLiteral left right combiner + | Temporary left, Final right -> combineLiteral left (finalizeEval right) combiner + | Final left, Temporary right -> combineLiteral (finalizeEval left) right combiner + | Final left, Final right -> combineLiteral (finalizeEval left) (finalizeEval right) combiner + + let operatorEval (left: Eval) (operator: Operator) (right: Eval) = + match operator with + | Equal -> compareEval left right (=) + | NotEqual -> compareEval left right (<>) + | GreaterThan -> compareEval left right (>) + | GreaterThanOrEqual -> compareEval left right (>=) + | LessThan -> compareEval left right (<) + | LessThanOrEqual -> compareEval left right (<=) + | And -> combineEval left right (&&) + | Or -> combineEval left right (||) + + let rec loopEval (expression: Expression) (entry: Entry) : Eval = + match expression with + | Expression.Const right -> right |> Option.Some |> Eval.Temporary + | Expression.Field field -> entry |> Map.tryFind field |> Eval.Temporary + | Expression.Binary(left, operator, right) -> + let left = loopEval left entry + let right = loopEval right entry + operatorEval left operator right |> Eval.Final + + let evaluator entry expression = + match loopEval expression entry with + | Temporary temp -> temp |> Option.isSome + | Final final -> final + + evaluator diff --git a/src/Log.fs b/src/Log.fs new file mode 100644 index 0000000..39d4d6c --- /dev/null +++ b/src/Log.fs @@ -0,0 +1,91 @@ +module Analog.Log + +open System +open FParsec +open GrokNet + +type Literal = + | String of string + | Number of float + | Boolean of bool + | Timestamp of DateTimeOffset + +type Entry = Map + +let numberParser: Parser<_, unit> = + pfloat + >>= fun res -> + if Double.IsInfinity res || Double.IsNaN res then + fail "Number cannot be infinite or NaN" + else + preturn res + |> attempt + |>> Literal.Number + +let boolParser: Parser<_, unit> = + choice [ pstringCI "true" >>% true; pstringCI "false" >>% false ] + |>> Literal.Boolean + +let timestampParser: Parser<_, unit> = + restOfLine false + >>= fun input -> + try + DateTimeOffset.Parse input |> preturn |>> Literal.Timestamp + with err -> + fail err.Message + |> attempt + +let stringParser: Parser<_, unit> = restOfLine true |>> Literal.String + +let literalParser: Parser<_, unit> = + choice [ timestampParser; numberParser; boolParser; stringParser ] + +let entryParser = + // Parse raw text using a GROK pattern. + let grok pattern text = + try + // GROK parser can throw an exception if the pattern is not correct. + // The exception should be wrapped in a result object. + Grok(pattern).Parse(text) |> Result.Ok + with err -> + Result.Error err.Message + + // A GROK result comes as a list of key-value pairs, so it is not clear to which log line a key-value belongs. + // Aggregate key-value pairs to form a log entry (a group of key-value pairs). + let aggregate list (key, value) = + match list with + // There are no aggregated entries yet, so create a new one. + | [] -> [ Map([ key, value ]) ] + | head :: tail -> + if head |> Map.containsKey key then + // Create a new entry if the head entry already contains a member with the same key. + [ Map([ key, value ]); head ] @ tail + else + // Otherwise, add a new member to the head entry. + (head |> Map.add key value) :: tail + + // Entry is in a raw form. Both key & value is a string. To be able to do any kind of transformation based + // on the entry value, the value must be parsed into proper data-type. + let parse entry = + entry + |> Map.toSeq + // Filter-out non-parsable members. + |> Seq.choose (fun (key, value) -> + match run literalParser value with + | Success(literal, _, _) -> Some(key, literal) + | Failure _ -> None) + |> Map.ofSeq + + let parser text = + grok text + >> Result.map (fun result -> result |> Seq.map (fun item -> item.Key, item.Value.ToString())) + >> Result.map (fun result -> result |> Seq.fold aggregate List.empty>) + >> Result.map (fun result -> result |> List.map parse) + >> Result.map (fun result -> result |> List.rev) + + // Return parser. + parser + +// Entry parser, using a default GROK pattern. +let entryParserDefault = + entryParser "\[%{TIMESTAMP_ISO8601:timestamp}\] \[%{LOGLEVEL:loglevel}\] %{GREEDYDATA:message}" diff --git a/src/Analog/Program.fs b/src/Program.fs similarity index 65% rename from src/Analog/Program.fs rename to src/Program.fs index 61608f2..a553550 100644 --- a/src/Analog/Program.fs +++ b/src/Program.fs @@ -1,12 +1,15 @@ open System open System.IO open System.Text.Json -open Analog +open Analog.Filter open Argu +open FParsec open Spectre.Console open Spectre.Console.Json -type Argument = +open Analog.Log + +type Command = | [] File of string | [] Pattern of string | [] Filter of string @@ -23,26 +26,28 @@ let import files = let parse pattern text = pattern - |> Option.map EntryParser.create - |> Option.defaultValue (EntryParser.value |> Result.Ok) - |> Result.map (EntryParser.parse text) + |> Option.map (fun pattern -> entryParser pattern text) + |> Option.defaultValue (entryParserDefault text) let filter filter entries = filter - |> Option.map (ParserRunner.run FilterParser.expression) + |> Option.map (run expressionParser) + |> Option.map (fun res -> + match res with + | Success(expression, _, _) -> Result.Ok expression + | Failure(error, _, _) -> Result.Error error) |> Option.map (fun res -> res |> Result.bind (fun filter -> entries |> Result.map (fun entries -> filter, entries))) |> Option.map (fun result -> result - |> Result.map (fun (filter, entries) -> - entries |> List.filter (fun entry -> FilterEvaluator.evaluate entry filter))) + |> Result.map (fun (filter, entries) -> entries |> List.filter (fun entry -> expressionEvaluator entry filter))) |> Option.defaultValue entries -let handle (args: ParseResults) = - import (args.GetResults Argument.File) - |> parse (args.TryGetResult Argument.Pattern) - |> filter (args.TryGetResult Argument.Filter) +let handle (args: ParseResults) = + import (args.GetResults Command.File) + |> parse (args.TryGetResult Command.Pattern) + |> filter (args.TryGetResult Command.Filter) let normalize (entry: Entry) = entry @@ -63,12 +68,12 @@ let print entries = let args = try ArgumentParser - .Create() + .Create() .ParseCommandLine(Environment.GetCommandLineArgs() |> Array.skip 1) |> Result.Ok with err -> Result.Error err.Message match args |> Result.bind handle with -| Ok entries -> print entries -| Error error -> error |> eprintf "%s" +| Result.Ok entries -> print entries +| Result.Error error -> error |> eprintf "%s" diff --git a/src/analog.sln b/src/analog.sln deleted file mode 100644 index e364731..0000000 --- a/src/analog.sln +++ /dev/null @@ -1,51 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Analog", "Analog\Analog.fsproj", "{7A9C59B7-C5F5-449F-A446-DD2703323686}" -EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Analog.Tests", "Analog.Tests\Analog.Tests.fsproj", "{D742D202-8FE7-42B8-B728-ACF3C3B28880}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|arm64 = Debug|arm64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|arm64 = Release|arm64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Debug|arm64.ActiveCfg = Debug|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Debug|arm64.Build.0 = Debug|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Debug|x86.ActiveCfg = Debug|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Debug|x86.Build.0 = Debug|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Release|Any CPU.Build.0 = Release|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Release|arm64.ActiveCfg = Release|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Release|arm64.Build.0 = Release|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Release|x86.ActiveCfg = Release|Any CPU - {7A9C59B7-C5F5-449F-A446-DD2703323686}.Release|x86.Build.0 = Release|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Debug|arm64.ActiveCfg = Debug|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Debug|arm64.Build.0 = Debug|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Debug|x86.ActiveCfg = Debug|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Debug|x86.Build.0 = Debug|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Release|Any CPU.Build.0 = Release|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Release|arm64.ActiveCfg = Release|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Release|arm64.Build.0 = Release|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Release|x86.ActiveCfg = Release|Any CPU - {D742D202-8FE7-42B8-B728-ACF3C3B28880}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {06FB59AB-911B-498D-89BF-3DF9DA28C2E7} - EndGlobalSection -EndGlobal diff --git a/src/Analog.Tests/Analog.Tests.fsproj b/test/Analog.Tests.fsproj similarity index 68% rename from src/Analog.Tests/Analog.Tests.fsproj rename to test/Analog.Tests.fsproj index 7a9ee78..280b366 100644 --- a/src/Analog.Tests/Analog.Tests.fsproj +++ b/test/Analog.Tests.fsproj @@ -16,21 +16,21 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Analog.Tests/EntryParserTest.fs b/test/EntryParserTest.fs similarity index 89% rename from src/Analog.Tests/EntryParserTest.fs rename to test/EntryParserTest.fs index 2c3ce3d..b50c997 100644 --- a/src/Analog.Tests/EntryParserTest.fs +++ b/test/EntryParserTest.fs @@ -1,12 +1,15 @@ module Analog.Tests.EntryParserTest open System -open Analog open FsUnit.Xunit open Xunit +open Analog.Log -let parse txt = - EntryParser.value |> EntryParser.parse txt +let parse = + entryParserDefault + >> function + | Ok entries -> entries + | Error error -> failwith error let stringOf = Literal.String let timestampOf = DateTimeOffset.Parse >> Literal.Timestamp diff --git a/src/Analog.Tests/FilterEvaluatorTest.fs b/test/FilterEvaluatorTest.fs similarity index 55% rename from src/Analog.Tests/FilterEvaluatorTest.fs rename to test/FilterEvaluatorTest.fs index 764acd4..501bb16 100644 --- a/src/Analog.Tests/FilterEvaluatorTest.fs +++ b/test/FilterEvaluatorTest.fs @@ -3,109 +3,110 @@ module Analog.Tests.FilterEvaluatorTest open System open Xunit open FsUnit.Xunit -open Analog +open Analog.Filter +open Analog.Log [] let ``Evaluate Const filter with Boolean literal`` () = let entry = Map.empty - let filter = Filter.Const (Literal.Boolean true) - let result = FilterEvaluator.evaluate entry filter + let filter = Expression.Const (Literal.Boolean true) + let result = expressionEvaluator entry filter result |> should equal true [] let ``Evaluate Field filter with matching field in entry`` () = let entry = Map.ofList [ "key", Literal.Boolean true ] - let filter = Filter.Field "key" - let result = FilterEvaluator.evaluate entry filter + let filter = Expression.Field "key" + let result = expressionEvaluator entry filter result |> should equal true [] let ``Evaluate Field filter with non-matching field in entry`` () = let entry = Map.ofList [ "key", Literal.Boolean true ] - let filter = Filter.Field "missingKey" - let result = FilterEvaluator.evaluate entry filter + let filter = Expression.Field "missingKey" + let result = expressionEvaluator entry filter result |> should equal false [] let ``Evaluate Binary Equal filter with matching String literals`` () = let entry = Map.empty let filter = - Filter.Binary( - Filter.Const (Literal.String "test"), + Expression.Binary( + Expression.Const (Literal.String "test"), Operator.Equal, - Filter.Const (Literal.String "test") + Expression.Const (Literal.String "test") ) - let result = FilterEvaluator.evaluate entry filter + let result = expressionEvaluator entry filter result |> should equal true [] let ``Evaluate Binary NotEqual filter with non-matching Number literals`` () = let entry = Map.empty let filter = - Filter.Binary( - Filter.Const (Literal.Number 1.0), + Expression.Binary( + Expression.Const (Literal.Number 1.0), Operator.NotEqual, - Filter.Const (Literal.Number 2.0) + Expression.Const (Literal.Number 2.0) ) - let result = FilterEvaluator.evaluate entry filter + let result = expressionEvaluator entry filter result |> should equal true [] let ``Evaluate Binary GreaterThan filter with Timestamp literals`` () = let entry = Map.empty let filter = - Filter.Binary( - Filter.Const (Literal.Timestamp (DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero))), + Expression.Binary( + Expression.Const (Literal.Timestamp (DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero))), Operator.GreaterThan, - Filter.Const (Literal.Timestamp (DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero))) + Expression.Const (Literal.Timestamp (DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero))) ) - let result = FilterEvaluator.evaluate entry filter + let result = expressionEvaluator entry filter result |> should equal true [] let ``Evaluate Binary And filter with Boolean literals`` () = let entry = Map.empty let filter = - Filter.Binary( - Filter.Const (Literal.Boolean true), + Expression.Binary( + Expression.Const (Literal.Boolean true), Operator.And, - Filter.Const (Literal.Boolean true) + Expression.Const (Literal.Boolean true) ) - let result = FilterEvaluator.evaluate entry filter + let result = expressionEvaluator entry filter result |> should equal true [] let ``Evaluate Binary Or filter with one Boolean literal true`` () = let entry = Map.empty let filter = - Filter.Binary( - Filter.Const (Literal.Boolean false), + Expression.Binary( + Expression.Const (Literal.Boolean false), Operator.Or, - Filter.Const (Literal.Boolean true) + Expression.Const (Literal.Boolean true) ) - let result = FilterEvaluator.evaluate entry filter + let result = expressionEvaluator entry filter result |> should equal true [] let ``Evaluate Binary Equal filter with mismatched Literal types`` () = let entry = Map.empty let filter = - Filter.Binary( - Filter.Const (Literal.String "test"), + Expression.Binary( + Expression.Const (Literal.String "test"), Operator.Equal, - Filter.Const (Literal.Number 42.0) + Expression.Const (Literal.Number 42.0) ) - let result = FilterEvaluator.evaluate entry filter + let result = expressionEvaluator entry filter result |> should equal false [] let ``Evaluate Binary GreaterThan filter with invalid field in entry`` () = let entry = Map.ofList [ "key", Literal.Number 10.0 ] let filter = - Filter.Binary( - Filter.Field "key", + Expression.Binary( + Expression.Field "key", Operator.GreaterThan, - Filter.Const (Literal.Number 20.0) + Expression.Const (Literal.Number 20.0) ) - let result = FilterEvaluator.evaluate entry filter + let result = expressionEvaluator entry filter result |> should equal false diff --git a/src/Analog.Tests/FilterParserTest.fs b/test/FilterParserTest.fs similarity index 77% rename from src/Analog.Tests/FilterParserTest.fs rename to test/FilterParserTest.fs index aeeb423..bcace86 100644 --- a/src/Analog.Tests/FilterParserTest.fs +++ b/test/FilterParserTest.fs @@ -2,12 +2,16 @@ module Analog.Tests.FilterParserTest open System +open FParsec open FsUnitTyped open Xunit +open Analog.Log +open Analog.Filter -open Analog - -let parse = FilterParser.expression |> ParserRunner.run +let parse text = + match run expressionParser text with + | Success(expression, _, _) -> Result.Ok expression + | Failure(error, _, _) -> Result.Error error [] let ``parse should correctly parse a constant string`` () = @@ -49,12 +53,5 @@ let ``parse should correctly parse a complex binary expression`` () = [] let ``parse should correctly parse a timestamp`` () = - let input = "2024-01-03T12:34:56+00:00" - let result = parse input - result |> shouldEqual (Result.Ok(Const(Timestamp(DateTimeOffset.Parse input)))) - -[] -let ``parse should return an error for invalid input`` () = - match parse "'unterminated string" with - | Ok _ -> failwith "parse should return an error for invalid input" - | Error _ -> () + parse "2024-01-03T12:34:56+00:00" + |> shouldEqual (Result.Ok(Const(Timestamp(DateTimeOffset.Parse "2024-01-03T12:34:56+00:00")))) diff --git a/test/Program.fs b/test/Program.fs new file mode 100644 index 0000000..80c6d84 --- /dev/null +++ b/test/Program.fs @@ -0,0 +1,3 @@ +module Program = + [] + let main _ = 0