From a40a32744a0a9357d4c803446fef36354d0f19af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Thu, 3 Mar 2022 18:14:56 +0100
Subject: [PATCH 01/77] Fill readme
---
README.md | 38 ++++++++++++++++++++++++++++++++++++--
1 file changed, 36 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 30efcf8..1c1ec6e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,36 @@
-# ExtLauncher
+
-## Usage
+
+
+
+
+
+
+
+
+
+
+
+
+
+ExtLauncher is a dotnet tool to search and launch quickly projects in the user's preferred application. ExtLauncher is maintained by folks at [D-EDGE](https://www.d-edge.com/).
+
+# Getting Started
+
+Install extLauncher as a global dotnet tool
+
+``` bash
+dotnet tool install extLauncher -g
+```
+
+or as a dotnet local tool
+
+``` bash
+dotnet new tool-manifest
+dotnet tool install extLauncher
+````
+
+# Usage
```
USAGE:
@@ -16,3 +46,7 @@ COMMANDS:
info Prints the current pattern and all the indexed files
refresh Updates the current index
```
+
+# License
+
+[MIT](./LICENSE)
From 48980f71536d2dae74e5cec55f56e93bc4534c79 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Thu, 3 Mar 2022 18:20:31 +0100
Subject: [PATCH 02/77] Add logos
---
logo-64x64.png | Bin 0 -> 3096 bytes
logo.png | Bin 0 -> 3096 bytes
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 logo-64x64.png
create mode 100644 logo.png
diff --git a/logo-64x64.png b/logo-64x64.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa912bbd690aeb82d00bf71b346b702b9d1afdcd
GIT binary patch
literal 3096
zcmdT``9IX#8~;d+>`EC+Op&fFjU~#Oog3vw80%=Vjb+H1VX}rXU%IJLmKam^P-DtY
zjioFjF}Ze=We_uDZ!CBE?fWm>AD;7^<@GwxInVq3ENQkkECl%^`2YYAw6Zk4#nHIm
zava1NtJ;hVIEpve5*7*o+>GDCMIrBna5g2v%$&mPP~KsYp7*?f$jC@lzaalmglDjq
zD(aq3E>lku0Qg^7nHt$g<*kgw#`J|ly4JSqM@Fvt==yAvo=b#kAbR?`i^eszy**y@&*
zPf$AwUq@5|g^^@8R%Lj?7C4AjZ3qe_G#;^PmT1hP}$wgRZKL6BHj||6!ur`gF%`dA}4y!b+9q^|Hfcbz0
zv0=%H@6Oyn1j_JXt1xVOp{8=kZ#t#LMCa^Vw^I40A9KTRe7Zt<^XmA(mt5bBVwmNo
z0s9e#2Kn_==9+hmSf(8N>)(2QWG@&ier3%xyVx>D?HHxgFi|U?LU3?&&s>M?IzdCF
z-^Dae%{x(@|JaZk<#mhJJEAQN2HGxXN$6@r%)4t*0iDIRN+Q|{eERMI+}i!!E7!3$
zPL21pIfjIa)k%FLAIy%j_9d$9b)#mURtvy9aT@m0@;f>13UNDgz|ctk{m6}
z
zZg+54_PmM+3iAbA-K+`r4!sIYkAP(;#1$!oQ#DLMQH!2GaGl^#0Y%RR?={U_5u5m@
z70E`cMIceGpQv5!C(=WH`f5EY&%ikW?Ub4D|rHhaH<83rgyq1(b-z0|bo5B*yBrJ6?%&O`M
z0^HnsAtNs|TWx;sK3==Fu2eu-EWl)b(bamd#_%0VFSQmi0?qAFyFYaDWz5x+)wrdE
zCw^0fA*8bcR!i72M7G?Fy--~DO@7;5LAa6zaT=K>XHrjm$+aGkK?pQ<15u1-8P;|Fs&)alx)pVa{I@D(_xLpwYmaq7MvCq+?_YuDRckg=5
z!tks~j6llPE4iZE+!ap1HCB;XOn~~I*RAn01r2Ktx1_&vrF#d~#3)}*uS{XaLb{&C
zuh%??%$XBsM-;R#94PKYbp#7Y44!5+A0*SRI@gwG(nClg%hRwF54IZv$_Y2qcY#Ur
zp)Dl#;uLxFcm{;pp4G1(0K;d8o@U|Pu|8~rhtDQ6zxN6M`d;DHSQ8jqPT_6@)6BAe
ztt$f8u07gjMO4id#Gpva(YxURZrNCn&5=S|2da;~l6(=u*3&UZ(1uYJmHE0^NQcUK)
z-cjZY)i1t5Hl+NU6>J-%!wtHh7jjk6sb6rX^Js
z75xg_TE4;!|Es~MjWjS(+!820xotw(wmI!o~GobMP%;ANnf2Q3F-3%pFWT(H`%VPcI>bp;J
z41VRLd7&*Hu0E)&JWig2ZZY-ftMM)tmyP>;<-51x$WJEY3Gno}SvxqHfpVr>PhvPn`xW0OO-lv>}mJGd^FgnqSYT1ea;VgN##W|Z{xc&o$Y@Y
z(AP3N@h%LP=iR+R`k8f79*5o9+p_~Ix#5D6Q+amdOV*>uk}QVE?8@Ti{HZ;(r;-L@
z?3aC5I$8jv-P{+=`7fs;Y=>vqcw6Ku#qFMIb%qD%~6@AR6e>%|EiJ24w+BgWU(PwF?5GlkuDsE$nO7zMV
z=^;Ig8+8}b2O8Z*-vm5)>Tc4{wNiPcbnJ^T;&6b6gNz}s$E8DF43~^$&eyS@{RU*`
zWFfa)IZS{6)o0|OcQXC{U~955S5vDmR8pJ&t;#n4YFpAQ=GmJo2Q8CB`MJUfc4y)<
zbswN%!`E*YP`dr+@4%|BI61)lJjL*9Fd?2@LUrd7X
zdK!{oSq?7qfAFI8_!Dzw5TIU@j?G-rg3XF;S(PHy$(#Y<-}dTURQ_@C*1HiF2i6^il=ABPwF!~UFpX2LiVttFa2(1(wL=9
zQ?pLzFG*t`7pZfS*)%=y8YesAAaC2|-#H^WWZSs8fFo2JCY}&|VRF!6EIzSmTlREk
zKE0sk2YzgiO;`}onHL&_xo{ax9_QOiCf!Kv-Ehs*!w2$Gtcd1OrfzqATx;2jVc&EJ
zR_i)iKfLKUY^9_$`+8Pbs8lZK+^^8m0K&_78N7<#eg<=bixih2L
z8U7bEWTMZDGt_@RLXU~k%0rULrQDo)J3A*fz1kA?tokX@Gs{^)QH{R*KOYNV8nVEP
z@_3IdU_?dL7X_I-c*(RPS=*v+6N`BgO!f#gzenNovKZaR$ygVrLRhq*aW06yLm+V=
zBae6x>h67%x;)g8N(C)1!4flg8%>k
literal 0
HcmV?d00001
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa912bbd690aeb82d00bf71b346b702b9d1afdcd
GIT binary patch
literal 3096
zcmdT``9IX#8~;d+>`EC+Op&fFjU~#Oog3vw80%=Vjb+H1VX}rXU%IJLmKam^P-DtY
zjioFjF}Ze=We_uDZ!CBE?fWm>AD;7^<@GwxInVq3ENQkkECl%^`2YYAw6Zk4#nHIm
zava1NtJ;hVIEpve5*7*o+>GDCMIrBna5g2v%$&mPP~KsYp7*?f$jC@lzaalmglDjq
zD(aq3E>lku0Qg^7nHt$g<*kgw#`J|ly4JSqM@Fvt==yAvo=b#kAbR?`i^eszy**y@&*
zPf$AwUq@5|g^^@8R%Lj?7C4AjZ3qe_G#;^PmT1hP}$wgRZKL6BHj||6!ur`gF%`dA}4y!b+9q^|Hfcbz0
zv0=%H@6Oyn1j_JXt1xVOp{8=kZ#t#LMCa^Vw^I40A9KTRe7Zt<^XmA(mt5bBVwmNo
z0s9e#2Kn_==9+hmSf(8N>)(2QWG@&ier3%xyVx>D?HHxgFi|U?LU3?&&s>M?IzdCF
z-^Dae%{x(@|JaZk<#mhJJEAQN2HGxXN$6@r%)4t*0iDIRN+Q|{eERMI+}i!!E7!3$
zPL21pIfjIa)k%FLAIy%j_9d$9b)#mURtvy9aT@m0@;f>13UNDgz|ctk{m6}
z
zZg+54_PmM+3iAbA-K+`r4!sIYkAP(;#1$!oQ#DLMQH!2GaGl^#0Y%RR?={U_5u5m@
z70E`cMIceGpQv5!C(=WH`f5EY&%ikW?Ub4D|rHhaH<83rgyq1(b-z0|bo5B*yBrJ6?%&O`M
z0^HnsAtNs|TWx;sK3==Fu2eu-EWl)b(bamd#_%0VFSQmi0?qAFyFYaDWz5x+)wrdE
zCw^0fA*8bcR!i72M7G?Fy--~DO@7;5LAa6zaT=K>XHrjm$+aGkK?pQ<15u1-8P;|Fs&)alx)pVa{I@D(_xLpwYmaq7MvCq+?_YuDRckg=5
z!tks~j6llPE4iZE+!ap1HCB;XOn~~I*RAn01r2Ktx1_&vrF#d~#3)}*uS{XaLb{&C
zuh%??%$XBsM-;R#94PKYbp#7Y44!5+A0*SRI@gwG(nClg%hRwF54IZv$_Y2qcY#Ur
zp)Dl#;uLxFcm{;pp4G1(0K;d8o@U|Pu|8~rhtDQ6zxN6M`d;DHSQ8jqPT_6@)6BAe
ztt$f8u07gjMO4id#Gpva(YxURZrNCn&5=S|2da;~l6(=u*3&UZ(1uYJmHE0^NQcUK)
z-cjZY)i1t5Hl+NU6>J-%!wtHh7jjk6sb6rX^Js
z75xg_TE4;!|Es~MjWjS(+!820xotw(wmI!o~GobMP%;ANnf2Q3F-3%pFWT(H`%VPcI>bp;J
z41VRLd7&*Hu0E)&JWig2ZZY-ftMM)tmyP>;<-51x$WJEY3Gno}SvxqHfpVr>PhvPn`xW0OO-lv>}mJGd^FgnqSYT1ea;VgN##W|Z{xc&o$Y@Y
z(AP3N@h%LP=iR+R`k8f79*5o9+p_~Ix#5D6Q+amdOV*>uk}QVE?8@Ti{HZ;(r;-L@
z?3aC5I$8jv-P{+=`7fs;Y=>vqcw6Ku#qFMIb%qD%~6@AR6e>%|EiJ24w+BgWU(PwF?5GlkuDsE$nO7zMV
z=^;Ig8+8}b2O8Z*-vm5)>Tc4{wNiPcbnJ^T;&6b6gNz}s$E8DF43~^$&eyS@{RU*`
zWFfa)IZS{6)o0|OcQXC{U~955S5vDmR8pJ&t;#n4YFpAQ=GmJo2Q8CB`MJUfc4y)<
zbswN%!`E*YP`dr+@4%|BI61)lJjL*9Fd?2@LUrd7X
zdK!{oSq?7qfAFI8_!Dzw5TIU@j?G-rg3XF;S(PHy$(#Y<-}dTURQ_@C*1HiF2i6^il=ABPwF!~UFpX2LiVttFa2(1(wL=9
zQ?pLzFG*t`7pZfS*)%=y8YesAAaC2|-#H^WWZSs8fFo2JCY}&|VRF!6EIzSmTlREk
zKE0sk2YzgiO;`}onHL&_xo{ax9_QOiCf!Kv-Ehs*!w2$Gtcd1OrfzqATx;2jVc&EJ
zR_i)iKfLKUY^9_$`+8Pbs8lZK+^^8m0K&_78N7<#eg<=bixih2L
z8U7bEWTMZDGt_@RLXU~k%0rULrQDo)J3A*fz1kA?tokX@Gs{^)QH{R*KOYNV8nVEP
z@_3IdU_?dL7X_I-c*(RPS=*v+6N`BgO!f#gzenNovKZaR$ygVrLRhq*aW06yLm+V=
zBae6x>h67%x;)g8N(C)1!4flg8%>k
literal 0
HcmV?d00001
From 571a1d1d00ed0d78c4365adc1e557d3620bc774a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Thu, 3 Mar 2022 18:21:42 +0100
Subject: [PATCH 03/77] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 1c1ec6e..82d3f94 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-
+
From 9f9b1fcfe6fc9b52d3ad61e9499429681e853fe1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Thu, 3 Mar 2022 18:22:39 +0100
Subject: [PATCH 04/77] Update README.md
---
README.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 82d3f94..58dc3e1 100644
--- a/README.md
+++ b/README.md
@@ -5,9 +5,11 @@
-
+
+
From ce2da4d8e24cdf266246c1f251b8f5b01a9a3682 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Thu, 3 Mar 2022 18:31:30 +0100
Subject: [PATCH 05/77] Identify as dotnet tool
---
ExtLauncher/ExtLauncher.fsproj | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/ExtLauncher/ExtLauncher.fsproj b/ExtLauncher/ExtLauncher.fsproj
index 9782482..4af5ca5 100644
--- a/ExtLauncher/ExtLauncher.fsproj
+++ b/ExtLauncher/ExtLauncher.fsproj
@@ -1,10 +1,14 @@
-
+
Exe
- net6.0
+ netcoreapp3.1;net5.0;net6.0
preview
true
+
+ true
+ extLauncher
+ ./nupkg
From 761236b2101577a320fefb9639d9d446066decb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Thu, 3 Mar 2022 18:51:18 +0100
Subject: [PATCH 06/77] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 58dc3e1..c5f4e87 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
@@ -51,4 +51,4 @@ COMMANDS:
# License
-[MIT](./LICENSE)
+[MIT](./LICENSE.md)
From abc04dc18f2a9f9a9b4a7afd79c09ad3c30831a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Thu, 3 Mar 2022 18:52:04 +0100
Subject: [PATCH 07/77] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index c5f4e87..c6ebf2b 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
From 5c256fd5332b44eb906ea377d9d64d8045a5bb36 Mon Sep 17 00:00:00 2001
From: Amin Khansari
Date: Sun, 6 Mar 2022 20:42:04 +0100
Subject: [PATCH 08/77] test: Unit Tests
---
ExtLauncher.Tests/AppTests.fs | 26 +++
ExtLauncher.Tests/ConsoleTests.fs | 176 ++++++++++++++++++
ExtLauncher.Tests/DomainTests.fs | 38 ++++
ExtLauncher.Tests/ExtLauncher.Tests.fsproj | 36 ++++
.../ExtLauncher.Tests.v3.ncrunchproject | 5 +
ExtLauncher.Tests/Program.fs | 1 +
ExtLauncher.sln | 13 +-
ExtLauncher/App.fs | 2 +-
ExtLauncher/Console.fs | 110 +++++++----
ExtLauncher/Domain.fs | 18 +-
ExtLauncher/ExtLauncher.fsproj | 2 +-
.../ExtLauncher.net5.0.v3.ncrunchproject | 5 +
.../ExtLauncher.net6.0.v3.ncrunchproject | 5 +
...xtLauncher.netcoreapp3.1.v3.ncrunchproject | 5 +
ExtLauncher/Infra.fs | 3 +-
ExtLauncher/Program.fs | 5 +-
README.md | 2 +-
17 files changed, 401 insertions(+), 51 deletions(-)
create mode 100644 ExtLauncher.Tests/AppTests.fs
create mode 100644 ExtLauncher.Tests/ConsoleTests.fs
create mode 100644 ExtLauncher.Tests/DomainTests.fs
create mode 100644 ExtLauncher.Tests/ExtLauncher.Tests.fsproj
create mode 100644 ExtLauncher.Tests/ExtLauncher.Tests.v3.ncrunchproject
create mode 100644 ExtLauncher.Tests/Program.fs
create mode 100644 ExtLauncher/ExtLauncher.net5.0.v3.ncrunchproject
create mode 100644 ExtLauncher/ExtLauncher.net6.0.v3.ncrunchproject
create mode 100644 ExtLauncher/ExtLauncher.netcoreapp3.1.v3.ncrunchproject
diff --git a/ExtLauncher.Tests/AppTests.fs b/ExtLauncher.Tests/AppTests.fs
new file mode 100644
index 0000000..850fb88
--- /dev/null
+++ b/ExtLauncher.Tests/AppTests.fs
@@ -0,0 +1,26 @@
+module ExtLauncher.AppTests
+
+open Swensen.Unquote
+open Xunit
+
+[]
+let ``should load a folder`` () =
+ let folderPath = "/test"
+ let pattern = "*.ext"
+ let loadFiles _ _ =
+ [| "/test/file2.ext", "file2"
+ "/test/file1.ext", "file1" |]
+ let folder = App.loadFolder loadFiles folderPath pattern
+ folder =! Some
+ { Id = folderPath
+ Pattern = pattern
+ Files =
+ [| File.create "/test/file1.ext" "file1"
+ File.create "/test/file2.ext" "file2" |]
+ OpenWith = Array.empty }
+
+[]
+let ``should not load a folder if no result`` () =
+ let loadFiles _ _ = Array.empty
+ let folder = App.loadFolder loadFiles "" ""
+ folder =! None
diff --git a/ExtLauncher.Tests/ConsoleTests.fs b/ExtLauncher.Tests/ConsoleTests.fs
new file mode 100644
index 0000000..6611e1a
--- /dev/null
+++ b/ExtLauncher.Tests/ConsoleTests.fs
@@ -0,0 +1,176 @@
+module ExtLauncher.ConsoleTests
+
+open System
+open System.Collections.Generic
+open Swensen.Unquote
+open Xunit
+
+let Terminal
+ (strReader: Queue)
+ (keyReader: Queue)
+ (buffer: ResizeArray)
+ =
+ let mutable left = 0
+ let mutable top = 0
+ { new Console.ITerminal with
+ member _.ShowCursor() = ()
+ member _.HideCursor() = ()
+ member _.ToggleCursorVisibility() = ()
+ member _.GetCursorPosition() =
+ left, top
+ member _.SetCursorPosition(l, t) =
+ left <- l
+ top <- t
+ member _.ReadKey () =
+ keyReader.Dequeue()
+ member _.ReadLine() =
+ strReader.Dequeue()
+ member _.Write str =
+ if top < buffer.Count then
+ buffer[top] <- buffer[top].Insert(left, str)
+ else
+ buffer.Add str
+ left <- str.Length
+ member _.WriteLine str =
+ if top < buffer.Count then
+ buffer[top] <- str
+ else
+ buffer.Add str
+ top <- top + 1
+ left <- 0
+ member _.ClearLine() =
+ buffer[top] <- String.Empty
+ left <- 0
+ }
+
+let newTerminal = Terminal (Queue())
+let enterKey = ConsoleKey.Enter, char ConsoleKey.Enter, enum 0
+let downKey = ConsoleKey.DownArrow, char ConsoleKey.DownArrow, enum 0
+let upKey = ConsoleKey.UpArrow, char ConsoleKey.UpArrow, enum 0
+let backspaceKey = ConsoleKey.Backspace, char ConsoleKey.Backspace, enum 0
+let escapeKey = ConsoleKey.Escape, char ConsoleKey.Escape, ConsoleModifiers.Alt
+let aKey k = enum(Char.ToUpper k |> int), k, enum 0
+let [] SearchSentence = "[teal]Search a file to launch:[/] "
+
+let printedLines maxChoices itemsCount chosenNum = [
+ SearchSentence
+ for n in 1..itemsCount do
+ $"""[yellow]{if n = chosenNum then ">" else " "} [/]{n}"""
+ for _ in itemsCount+1..maxChoices do
+ ""
+ ]
+
+[]
+let ``prompt should print choices`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ escapeKey ]
+ let term = newTerminal keyReader lines
+
+ let _ =
+ fun _ -> [| 1; 2; 3 |]
+ |> Console.prompt term 5
+
+ List.ofSeq lines =! printedLines 5 3 1
+
+[]
+let ``prompt should go down`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ downKey; escapeKey ]
+ let term = newTerminal keyReader lines
+
+ let _ =
+ fun _ -> [| 1; 2; 3 |]
+ |> Console.prompt term 5
+
+ List.ofSeq lines =! printedLines 5 3 2
+
+[]
+let ``prompt should go up`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ downKey; upKey; escapeKey ]
+ let term = newTerminal keyReader lines
+
+ let _ =
+ fun _ -> [| 1; 2; 3 |]
+ |> Console.prompt term 5
+
+ List.ofSeq lines =! printedLines 5 3 1
+
+[]
+let ``prompt should stay up`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ upKey; upKey; escapeKey ]
+ let term = newTerminal keyReader lines
+
+ let _ =
+ fun _ -> [| 1; 2; 3 |]
+ |> Console.prompt term 5
+
+ List.ofSeq lines =! printedLines 5 3 1
+
+[]
+let ``prompt should stay down`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ downKey; downKey; downKey; downKey; downKey; escapeKey ]
+ let term = newTerminal keyReader lines
+
+ let _ =
+ fun _ -> [| 1; 2; 3 |]
+ |> Console.prompt term 5
+
+ List.ofSeq lines =! printedLines 5 3 3
+
+[]
+let ``prompt should choose the second choice and clear`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ downKey; enterKey ]
+ let term = newTerminal keyReader lines
+
+ let chosen =
+ fun _ -> [| 1; 2; 3 |]
+ |> Console.prompt term 5
+
+ chosen =! Some 2
+ List.ofSeq lines =! [
+ """Launching "2"..."""
+ ""; ""; ""; ""; ""
+ ]
+
+[]
+let ``prompt should print error if no match`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ escapeKey ]
+ let term = newTerminal keyReader lines
+
+ let _ =
+ fun _ -> Array.empty
+ |> Console.prompt term 1
+
+ List.ofSeq lines =! [
+ SearchSentence
+ "No items match your search."
+ ]
+
+[]
+let ``prompt should print the search chars`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ aKey 't'; aKey 'e'; aKey 's'; aKey 't'; escapeKey ]
+ let term = newTerminal keyReader lines
+
+ let _ =
+ fun _ -> [| 1; 2; 3 |]
+ |> Console.prompt term 1
+
+ Seq.head lines =! $"{SearchSentence}test"
+
+[]
+let ``prompt should print the search chars supporting backspace`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ aKey 't'; aKey 'e'; backspaceKey; aKey 's'; aKey 't'; escapeKey ]
+ let term = newTerminal keyReader lines
+
+ let _ =
+ fun _ -> [| 1; 2; 3 |]
+ |> Console.prompt term 1
+
+ Seq.head lines =! $"{SearchSentence}tst"
diff --git a/ExtLauncher.Tests/DomainTests.fs b/ExtLauncher.Tests/DomainTests.fs
new file mode 100644
index 0000000..4dc4931
--- /dev/null
+++ b/ExtLauncher.Tests/DomainTests.fs
@@ -0,0 +1,38 @@
+module ExtLauncher.DomainTests
+
+open System
+open FsCheck.Xunit
+open Swensen.Unquote
+
+[]
+let ``File with the same Id should be equal`` (file1: File) (file2: File) =
+ let file1 = { file1 with Id = file2.Id }
+ file1 =! file2
+
+[]
+let ``File with different Id should not be equal`` (file1: File) (file2: File) =
+ let file1 = { file1 with Id = Guid.NewGuid().ToString() }
+ let file2 = { file2 with Id = Guid.NewGuid().ToString() }
+ file1 <>! file2
+
+[]
+let ``File with a higher trigger should precede in the sort order`` (file1: File) (file2: File) =
+ let file1 = { file1 with Triggered = file2.Triggered + 1 }
+ compare file1 file2 =! -1
+
+[]
+let ``File with a lower trigger should follow in the sort order`` (file1: File) (file2: File) =
+ let file1 = { file1 with Triggered = file2.Triggered - 1 }
+ compare file1 file2 =! 1
+
+[]
+let ``File with the same trigger should be sorted alphabetically`` (file1: File) (file2: File) =
+ let file1 = { file1 with Triggered = 0; Name = "a" }
+ let file2 = { file2 with Triggered = 0; Name = "b" }
+ compare file1 file2 =! -1
+
+[]
+let ``searchByName should search for the containing string ignoring case`` (file: File) (files: File array) =
+ let file = { file with Name = "Hello World" }
+ let files = Array.insertAt 0 file files
+ File.searchByName files "world" =! [| file |]
diff --git a/ExtLauncher.Tests/ExtLauncher.Tests.fsproj b/ExtLauncher.Tests/ExtLauncher.Tests.fsproj
new file mode 100644
index 0000000..bb76b8d
--- /dev/null
+++ b/ExtLauncher.Tests/ExtLauncher.Tests.fsproj
@@ -0,0 +1,36 @@
+
+
+
+ net6.0
+
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/ExtLauncher.Tests/ExtLauncher.Tests.v3.ncrunchproject b/ExtLauncher.Tests/ExtLauncher.Tests.v3.ncrunchproject
new file mode 100644
index 0000000..cf22dfa
--- /dev/null
+++ b/ExtLauncher.Tests/ExtLauncher.Tests.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtLauncher.Tests/Program.fs b/ExtLauncher.Tests/Program.fs
new file mode 100644
index 0000000..0695f84
--- /dev/null
+++ b/ExtLauncher.Tests/Program.fs
@@ -0,0 +1 @@
+module Program = let [] main _ = 0
diff --git a/ExtLauncher.sln b/ExtLauncher.sln
index eeb6e0b..fe4488c 100644
--- a/ExtLauncher.sln
+++ b/ExtLauncher.sln
@@ -3,7 +3,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ExtLauncher", "ExtLauncher\ExtLauncher.fsproj", "{075AB28F-511D-4C65-97B9-399F442EC8B8}"
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ExtLauncher", "ExtLauncher\ExtLauncher.fsproj", "{075AB28F-511D-4C65-97B9-399F442EC8B8}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ExtLauncher.Tests", "ExtLauncher.Tests\ExtLauncher.Tests.fsproj", "{687867BF-FB37-43BF-B0FF-148313A40D5B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{503F5C7D-95C0-4B13-8BC6-AAF31EA9FDE2}"
+ ProjectSection(SolutionItems) = preProject
+ README.md = README.md
+ EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +22,10 @@ Global
{075AB28F-511D-4C65-97B9-399F442EC8B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{075AB28F-511D-4C65-97B9-399F442EC8B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{075AB28F-511D-4C65-97B9-399F442EC8B8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {687867BF-FB37-43BF-B0FF-148313A40D5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {687867BF-FB37-43BF-B0FF-148313A40D5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {687867BF-FB37-43BF-B0FF-148313A40D5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {687867BF-FB37-43BF-B0FF-148313A40D5B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ExtLauncher/App.fs b/ExtLauncher/App.fs
index e51b10e..0f9e575 100644
--- a/ExtLauncher/App.fs
+++ b/ExtLauncher/App.fs
@@ -24,4 +24,4 @@ let refresh loadFiles save delete folderPath pattern =
None)
let makeSearcher folder =
- File.search folder.Files
+ File.searchByName folder.Files
diff --git a/ExtLauncher/Console.fs b/ExtLauncher/Console.fs
index a4400bb..559d560 100644
--- a/ExtLauncher/Console.fs
+++ b/ExtLauncher/Console.fs
@@ -3,78 +3,114 @@
open System
open Spectre.Console
-let showCursor () = Console.CursorVisible <- true
-let hideCursor () = Console.CursorVisible <- false
-let setCursorPosition left top = Console.SetCursorPosition(left, top)
-let getCursorTop () = Console.CursorTop
-let clearLine () = String(' ', Console.BufferWidth - 1) |> printf "%s"
+type TerminalKey = ConsoleKey * char * ConsoleModifiers
-let clearUp cursorTop count =
- for top = cursorTop to cursorTop + count do
- setCursorPosition 0 top
- clearLine ()
- setCursorPosition 0 cursorTop
+type ITerminal =
+ abstract member ShowCursor: unit -> unit
+ abstract member HideCursor: unit -> unit
+ abstract member ToggleCursorVisibility: unit -> unit
+ abstract member GetCursorPosition: unit -> int32 * int32
+ abstract member SetCursorPosition: int32 * int32 -> unit
+ abstract member ReadKey: unit -> TerminalKey
+ abstract member ReadLine: unit -> string
+ abstract member Write: string -> unit
+ abstract member WriteLine: string -> unit
+ abstract member ClearLine: unit -> unit
+
+let Terminal = { new ITerminal with
+ member _.ShowCursor () =
+ Console.CursorVisible <- true
+ member _.HideCursor () =
+ Console.CursorVisible <- false
+ member _.ToggleCursorVisibility () =
+ Console.CursorVisible <- not Console.CursorVisible
+ member _.GetCursorPosition () =
+ Console.CursorLeft, Console.CursorTop
+ member _.SetCursorPosition (left, top) =
+ Console.SetCursorPosition(
+ (if left < 0 then 0 else left),
+ (if top < 0 then 0 else top))
+ member _.ReadKey () =
+ let k = Console.ReadKey()
+ k.Key, k.KeyChar, k.Modifiers
+ member _.ReadLine () =
+ Console.ReadLine()
+ member _.Write str =
+ AnsiConsole.Markup str
+ member _.WriteLine str =
+ AnsiConsole.MarkupLine str
+ member this.ClearLine () =
+ String(' ', Console.BufferWidth - 1) |> this.Write
+ }
-let readKey () =
- let consoleKey = Console.ReadKey true
- (consoleKey.Key, consoleKey.KeyChar)
+let [] NoMatch = "No items match your search."
-let printNoMatch () =
- printfn "No items match your search."
+let clearUp (term: ITerminal) cursorTop count =
+ for top = cursorTop to cursorTop + count do
+ term.SetCursorPosition (0, top)
+ term.ClearLine ()
+ term.SetCursorPosition (0, cursorTop)
-let checkNoMatch (search: string -> 'T array) =
+let checkNoMatch (term: ITerminal) (search: string -> 'T array) =
if search String.Empty |> Array.isEmpty then
- printNoMatch ()
+ term.WriteLine NoMatch
None
else
Some search
-let prompt<'T> maxChoices (search: string -> 'T array) =
+let prompt<'T> (term: ITerminal) maxChoices (search: string -> 'T array) =
- for _ in 0..maxChoices do printfn "" // allocate buffer area
- let cursorTop = getCursorTop () - maxChoices - 1
+ for _ in 0..maxChoices do term.WriteLine "" // allocate buffer area
+ let cursorTop =
+ let _, top = term.GetCursorPosition()
+ top - maxChoices - 1
let search str =
let choices = search str |> Array.truncate maxChoices
(choices, str, 0)
let print (choices: 'T array, str, pos) =
- hideCursor ()
+ term.HideCursor ()
let pos = max 0 (min (Array.length choices - 1) pos)
- clearUp cursorTop maxChoices
- printfn ""
+ clearUp term cursorTop maxChoices
+ term.WriteLine ""
if Array.isEmpty choices then
- printNoMatch ()
+ term.WriteLine NoMatch
else
choices
|> Array.iteri (fun i choice ->
sprintf "[yellow]%s[/]%s"
(if i = pos then "> " else " ")
(string choice)
- |> AnsiConsole.MarkupLine)
- setCursorPosition 0 cursorTop
- AnsiConsole.Markup $"[teal]Search a file to launch:[/] %s{str}"
- showCursor ()
+ |> term.WriteLine)
+ term.SetCursorPosition (0, cursorTop)
+ term.Write $"[teal]Search a file to launch:[/] %s{str}"
+ term.ShowCursor ()
(choices, str, pos)
let rec read (choices: 'T array, str, pos) =
- match readKey () with
- | ConsoleKey.Escape, _ ->
- clearUp cursorTop maxChoices
+ match term.ReadKey () with
+ | ConsoleKey.Escape, _, ConsoleModifiers.Alt ->
+ None // no clear alternative
+ | ConsoleKey.Escape, _, _ ->
+ clearUp term cursorTop maxChoices
None
- | ConsoleKey.Enter, _ ->
+ | ConsoleKey.Enter, _, _ ->
if Array.isEmpty choices
then read (choices, str, pos)
- else Some choices[pos]
- | ConsoleKey.UpArrow, _ ->
+ else
+ clearUp term cursorTop maxChoices
+ term.WriteLine $"""Launching "{choices[pos]}"..."""
+ Some choices[pos]
+ | ConsoleKey.UpArrow, _, _ ->
print (choices, str, pos - 1) |> read
- | ConsoleKey.DownArrow, _ ->
+ | ConsoleKey.DownArrow, _, _ ->
print (choices, str, pos + 1) |> read
- | ConsoleKey.Backspace, _ ->
+ | ConsoleKey.Backspace, _, _ ->
if str.Length = 0
then read (choices, str, pos)
else search str[..^1] |> print |> read
- | _, key ->
+ | _, key, _ ->
search $"{str}{key}" |> print |> read
search String.Empty |> print |> read
diff --git a/ExtLauncher/Domain.fs b/ExtLauncher/Domain.fs
index 9998a0e..458a5cd 100644
--- a/ExtLauncher/Domain.fs
+++ b/ExtLauncher/Domain.fs
@@ -14,7 +14,7 @@ type File =
override this.Equals other =
match other with
- | :? File as other -> this.Id = other.Name
+ | :? File as other -> this.Id = other.Id
| _ -> ArgumentException() |> raise
interface IComparable with
@@ -35,11 +35,15 @@ module File =
let triggered file =
{ file with Triggered = file.Triggered + 1 }
- let search files str =
- if String.IsNullOrWhiteSpace str then files else
- files
- |> Array.filter (fun file ->
- file.Name.Contains(str, StringComparison.OrdinalIgnoreCase))
+ let searchByName files name =
+ if String.IsNullOrWhiteSpace name then
+ files
+ else
+ files
+ |> Array.filter (fun file ->
+ if isNull file.Name
+ then false
+ else file.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
[]
type Folder =
@@ -47,4 +51,4 @@ type Folder =
Pattern: string
OpenWith: string array
Files: File array }
- override this.ToString() = $"[*.{this.Pattern}] {this.Id}"
+ override this.ToString() = $"{this.Pattern} -> {this.Id}"
diff --git a/ExtLauncher/ExtLauncher.fsproj b/ExtLauncher/ExtLauncher.fsproj
index 4af5ca5..4786538 100644
--- a/ExtLauncher/ExtLauncher.fsproj
+++ b/ExtLauncher/ExtLauncher.fsproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp3.1;net5.0;net6.0
+ netcoreapp3.1;net6.0
preview
true
diff --git a/ExtLauncher/ExtLauncher.net5.0.v3.ncrunchproject b/ExtLauncher/ExtLauncher.net5.0.v3.ncrunchproject
new file mode 100644
index 0000000..cf22dfa
--- /dev/null
+++ b/ExtLauncher/ExtLauncher.net5.0.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtLauncher/ExtLauncher.net6.0.v3.ncrunchproject b/ExtLauncher/ExtLauncher.net6.0.v3.ncrunchproject
new file mode 100644
index 0000000..cf22dfa
--- /dev/null
+++ b/ExtLauncher/ExtLauncher.net6.0.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtLauncher/ExtLauncher.netcoreapp3.1.v3.ncrunchproject b/ExtLauncher/ExtLauncher.netcoreapp3.1.v3.ncrunchproject
new file mode 100644
index 0000000..cf22dfa
--- /dev/null
+++ b/ExtLauncher/ExtLauncher.netcoreapp3.1.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtLauncher/Infra.fs b/ExtLauncher/Infra.fs
index 9069096..063d59a 100644
--- a/ExtLauncher/Infra.fs
+++ b/ExtLauncher/Infra.fs
@@ -5,7 +5,7 @@ open System
module IO =
open System.IO
- let [] AppName = "ext-launcher"
+ let [] AppName = "extLauncher"
let userPath =
let path = Path.Combine(Environment.GetFolderPath Environment.SpecialFolder.ApplicationData, AppName)
@@ -31,6 +31,7 @@ module Db =
let findFolder (path: string) =
use db = newReadOnlyDb ()
+ let foo = db.GetCollection().FindAll() |> List.ofSeq
let doc = db.GetCollection().Include(fun f -> f.Files).FindById path
if box doc <> null then Some doc else None
diff --git a/ExtLauncher/Program.fs b/ExtLauncher/Program.fs
index 62929d4..1b3ac96 100644
--- a/ExtLauncher/Program.fs
+++ b/ExtLauncher/Program.fs
@@ -29,7 +29,7 @@ module private Helpers =
let prompt folder =
folder
|> App.makeSearcher
- |> Console.prompt 10
+ |> Console.prompt Console.Terminal 10
|> Option.iter trigger
let withLoader<'T> (worker: StatusContext -> 'T) =
@@ -45,7 +45,8 @@ module private Helpers =
else
match Db.findFolder path with
| Some f -> Some f
- | None -> System.IO.Path.GetDirectoryName path |> find
+ | None ->
+ find (System.IO.Path.GetDirectoryName path)
find currentPath
type PromptCommand () =
diff --git a/README.md b/README.md
index c6ebf2b..a681861 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ dotnet tool install extLauncher
```
USAGE:
- ext-launcher [OPTIONS]
+ extLauncher [OPTIONS]
OPTIONS:
-h, --help Prints help information
From 5067be6e98220ceb41975fbae1ca7839853ad8c4 Mon Sep 17 00:00:00 2001
From: Amin Khansari
Date: Sun, 6 Mar 2022 23:23:14 +0100
Subject: [PATCH 09/77] feat: Regex pattern
---
ExtLauncher.Tests/AppTests.fs | 5 ++--
ExtLauncher.Tests/ConsoleTests.fs | 25 ++++++++++++++----
ExtLauncher/App.fs | 3 ++-
ExtLauncher/Console.fs | 21 ++++++++-------
ExtLauncher/Domain.fs | 12 ++++++++-
ExtLauncher/ExtLauncher.fsproj | 3 ++-
ExtLauncher/Infra.fs | 31 +++++++++++++++-------
ExtLauncher/Program.fs | 43 +++++++++++++++++++------------
README.md | 2 +-
9 files changed, 99 insertions(+), 46 deletions(-)
diff --git a/ExtLauncher.Tests/AppTests.fs b/ExtLauncher.Tests/AppTests.fs
index 850fb88..81605ed 100644
--- a/ExtLauncher.Tests/AppTests.fs
+++ b/ExtLauncher.Tests/AppTests.fs
@@ -10,10 +10,11 @@ let ``should load a folder`` () =
let loadFiles _ _ =
[| "/test/file2.ext", "file2"
"/test/file1.ext", "file1" |]
- let folder = App.loadFolder loadFiles folderPath pattern
+ let folder = App.loadFolder loadFiles folderPath (WildcardPattern pattern)
folder =! Some
{ Id = folderPath
Pattern = pattern
+ IsRegex = false
Files =
[| File.create "/test/file1.ext" "file1"
File.create "/test/file2.ext" "file2" |]
@@ -22,5 +23,5 @@ let ``should load a folder`` () =
[]
let ``should not load a folder if no result`` () =
let loadFiles _ _ = Array.empty
- let folder = App.loadFolder loadFiles "" ""
+ let folder = App.loadFolder loadFiles "" (WildcardPattern "")
folder =! None
diff --git a/ExtLauncher.Tests/ConsoleTests.fs b/ExtLauncher.Tests/ConsoleTests.fs
index 6611e1a..f7228e9 100644
--- a/ExtLauncher.Tests/ConsoleTests.fs
+++ b/ExtLauncher.Tests/ConsoleTests.fs
@@ -38,18 +38,21 @@ let Terminal
buffer.Add str
top <- top + 1
left <- 0
+ member this.Markup str = this.Write str
+ member this.MarkupLine str = this.WriteLine str
member _.ClearLine() =
buffer[top] <- String.Empty
left <- 0
}
let newTerminal = Terminal (Queue())
-let enterKey = ConsoleKey.Enter, char ConsoleKey.Enter, enum 0
-let downKey = ConsoleKey.DownArrow, char ConsoleKey.DownArrow, enum 0
-let upKey = ConsoleKey.UpArrow, char ConsoleKey.UpArrow, enum 0
-let backspaceKey = ConsoleKey.Backspace, char ConsoleKey.Backspace, enum 0
+let noConsoleModifier = enum 0
+let enterKey = ConsoleKey.Enter, char ConsoleKey.Enter, noConsoleModifier
+let downKey = ConsoleKey.DownArrow, char ConsoleKey.DownArrow, noConsoleModifier
+let upKey = ConsoleKey.UpArrow, char ConsoleKey.UpArrow, noConsoleModifier
+let backspaceKey = ConsoleKey.Backspace, char ConsoleKey.Backspace, noConsoleModifier
let escapeKey = ConsoleKey.Escape, char ConsoleKey.Escape, ConsoleModifiers.Alt
-let aKey k = enum(Char.ToUpper k |> int), k, enum 0
+let aKey k = enum(Char.ToUpper k |> int), k, noConsoleModifier
let [] SearchSentence = "[teal]Search a file to launch:[/] "
let printedLines maxChoices itemsCount chosenNum = [
@@ -174,3 +177,15 @@ let ``prompt should print the search chars supporting backspace`` () =
|> Console.prompt term 1
Seq.head lines =! $"{SearchSentence}tst"
+
+[]
+let ``prompt should clear when exit`` () =
+ let lines = ResizeArray()
+ let keyReader = Queue [ ConsoleKey.Escape, char ConsoleKey.Escape, noConsoleModifier ]
+ let term = newTerminal keyReader lines
+
+ let _ =
+ fun _ -> [| 1; 2; 3 |]
+ |> Console.prompt term 5
+
+ Seq.forall ((=) "") lines =! true
diff --git a/ExtLauncher/App.fs b/ExtLauncher/App.fs
index 0f9e575..e392621 100644
--- a/ExtLauncher/App.fs
+++ b/ExtLauncher/App.fs
@@ -8,7 +8,8 @@ let loadFolder loadFiles folderPath pattern : Folder option =
| [||] -> None
| files ->
{ Id = folderPath
- Pattern = pattern
+ Pattern = Pattern.value pattern
+ IsRegex = Pattern.isRegex pattern
Files = files
OpenWith = Array.empty }
|> Some
diff --git a/ExtLauncher/Console.fs b/ExtLauncher/Console.fs
index 559d560..5ea2d69 100644
--- a/ExtLauncher/Console.fs
+++ b/ExtLauncher/Console.fs
@@ -15,6 +15,8 @@ type ITerminal =
abstract member ReadLine: unit -> string
abstract member Write: string -> unit
abstract member WriteLine: string -> unit
+ abstract member Markup: string -> unit
+ abstract member MarkupLine: string -> unit
abstract member ClearLine: unit -> unit
let Terminal = { new ITerminal with
@@ -35,12 +37,12 @@ let Terminal = { new ITerminal with
k.Key, k.KeyChar, k.Modifiers
member _.ReadLine () =
Console.ReadLine()
- member _.Write str =
- AnsiConsole.Markup str
- member _.WriteLine str =
- AnsiConsole.MarkupLine str
+ member _.Write str = AnsiConsole.Write str
+ member _.WriteLine str = AnsiConsole.WriteLine str
+ member _.Markup str = AnsiConsole.Markup str
+ member _.MarkupLine str = AnsiConsole.MarkupLine str
member this.ClearLine () =
- String(' ', Console.BufferWidth - 1) |> this.Write
+ String(' ', Console.BufferWidth - 1) |> this.Markup
}
let [] NoMatch = "No items match your search."
@@ -53,7 +55,7 @@ let clearUp (term: ITerminal) cursorTop count =
let checkNoMatch (term: ITerminal) (search: string -> 'T array) =
if search String.Empty |> Array.isEmpty then
- term.WriteLine NoMatch
+ term.MarkupLine NoMatch
None
else
Some search
@@ -75,16 +77,16 @@ let prompt<'T> (term: ITerminal) maxChoices (search: string -> 'T array) =
clearUp term cursorTop maxChoices
term.WriteLine ""
if Array.isEmpty choices then
- term.WriteLine NoMatch
+ term.MarkupLine NoMatch
else
choices
|> Array.iteri (fun i choice ->
sprintf "[yellow]%s[/]%s"
(if i = pos then "> " else " ")
(string choice)
- |> term.WriteLine)
+ |> term.MarkupLine)
term.SetCursorPosition (0, cursorTop)
- term.Write $"[teal]Search a file to launch:[/] %s{str}"
+ term.Markup $"[teal]Search a file to launch:[/] %s{str}"
term.ShowCursor ()
(choices, str, pos)
@@ -93,6 +95,7 @@ let prompt<'T> (term: ITerminal) maxChoices (search: string -> 'T array) =
| ConsoleKey.Escape, _, ConsoleModifiers.Alt ->
None // no clear alternative
| ConsoleKey.Escape, _, _ ->
+ term.Write (string '\u200B') // hack to force the clear
clearUp term cursorTop maxChoices
None
| ConsoleKey.Enter, _, _ ->
diff --git a/ExtLauncher/Domain.fs b/ExtLauncher/Domain.fs
index 458a5cd..bd0bdc9 100644
--- a/ExtLauncher/Domain.fs
+++ b/ExtLauncher/Domain.fs
@@ -49,6 +49,16 @@ module File =
type Folder =
{ Id: string
Pattern: string
+ IsRegex: bool
OpenWith: string array
Files: File array }
- override this.ToString() = $"{this.Pattern} -> {this.Id}"
+ override this.ToString() = $"%A{this.Pattern} -> {this.Id}"
+
+type Pattern =
+ | WildcardPattern of string
+ | RegexPattern of string
+
+module Pattern =
+ let value = function WildcardPattern p | RegexPattern p -> p
+ let isRegex = function WildcardPattern _ -> false | RegexPattern _ -> true
+ let from value isRegex = if isRegex then RegexPattern value else WildcardPattern value
diff --git a/ExtLauncher/ExtLauncher.fsproj b/ExtLauncher/ExtLauncher.fsproj
index 4786538..57a7ded 100644
--- a/ExtLauncher/ExtLauncher.fsproj
+++ b/ExtLauncher/ExtLauncher.fsproj
@@ -1,10 +1,11 @@
-
+
Exe
netcoreapp3.1;net6.0
preview
true
+ true
true
extLauncher
diff --git a/ExtLauncher/Infra.fs b/ExtLauncher/Infra.fs
index 063d59a..b8fe0b6 100644
--- a/ExtLauncher/Infra.fs
+++ b/ExtLauncher/Infra.fs
@@ -4,6 +4,7 @@ open System
module IO =
open System.IO
+ open System.Text.RegularExpressions
let [] AppName = "extLauncher"
@@ -15,8 +16,18 @@ module IO =
let userPathCombine path =
Path.Combine(userPath, path)
- let getFiles (folderPath: string) pattern =
- Directory.EnumerateFiles(folderPath, pattern, SearchOption.AllDirectories)
+ let private enumerateFiles folderPath = function
+ | WildcardPattern pattern ->
+ Directory.EnumerateFiles(folderPath, pattern, SearchOption.AllDirectories)
+ | RegexPattern pattern ->
+ let regex = Regex pattern
+ let opt = EnumerationOptions()
+ opt.RecurseSubdirectories <- true
+ Directory.EnumerateFiles(folderPath, "*", opt)
+ |> Seq.filter (Path.GetFileName >> regex.IsMatch)
+
+ let getFiles folderPath pattern =
+ enumerateFiles folderPath pattern
|> Seq.map (fun path -> path, Path.GetFileNameWithoutExtension path)
|> Seq.toArray
@@ -31,22 +42,15 @@ module Db =
let findFolder (path: string) =
use db = newReadOnlyDb ()
- let foo = db.GetCollection().FindAll() |> List.ofSeq
let doc = db.GetCollection().Include(fun f -> f.Files).FindById path
if box doc <> null then Some doc else None
- let upsertFolder (folder: Folder) =
- use db = newSharedDb ()
- db.GetCollection().Upsert folder.Files |> ignore
- db.GetCollection().Upsert folder |> ignore
- folder
-
let updateFile (file: File) =
use db = newSharedDb ()
db.GetCollection().Update file |> ignore
file
- let deleteFolder (path: string) =
+ let deleteFolder path =
match findFolder path with
| None -> ()
| Some folder ->
@@ -54,3 +58,10 @@ module Db =
for file in folder.Files do
db.GetCollection().Delete file.Id |> ignore
db.GetCollection().Delete folder.Id |> ignore
+
+ let upsertFolder (folder: Folder) =
+ deleteFolder folder.Id
+ use db = newSharedDb ()
+ db.GetCollection().InsertBulk folder.Files |> ignore
+ db.GetCollection().Insert folder |> ignore
+ folder
diff --git a/ExtLauncher/Program.fs b/ExtLauncher/Program.fs
index 1b3ac96..3724e84 100644
--- a/ExtLauncher/Program.fs
+++ b/ExtLauncher/Program.fs
@@ -9,13 +9,13 @@ open Spectre.Console.Cli
module private Helpers =
open System.Diagnostics
- let prn value = AnsiConsole.MarkupLine value
+ let markup value = AnsiConsole.MarkupLine value
let notInitialized () =
- prn "Folder not yet indexed."
- prn $" [yellow]{IO.AppName}[/] index [teal][/]"
- prn "For more information:"
- prn $" [yellow]{IO.AppName}[/] --help"
+ printfn "Folder not yet indexed."
+ markup $" [yellow]{IO.AppName}[/] index [teal][/]"
+ printfn "For more information:"
+ markup $" [yellow]{IO.AppName}[/] --help"
1
let run (file: File) =
@@ -59,17 +59,27 @@ type PromptCommand () =
type IndexSettings () =
inherit CommandSettings ()
[")>]
- []
+ []
member val Pattern = "" with get, set
+ []
+ []
+ []
+ member val IsRegex = false with get, set
type IndexCommand () =
inherit Command ()
override _.Execute (_, settings) =
fun _ ->
- App.index IO.getFiles Db.upsertFolder currentPath settings.Pattern
+ Pattern.from settings.Pattern settings.IsRegex
+ |> App.index IO.getFiles Db.upsertFolder currentPath
|> withLoader
- |> Option.iter prompt
- 0
+ |> function
+ | Some folder ->
+ prompt folder
+ 0
+ | None ->
+ Console.WriteLine Console.NoMatch
+ -1
type DeindexCommand () =
inherit Command ()
@@ -78,7 +88,7 @@ type DeindexCommand () =
| None -> notInitialized ()
| Some folder ->
Db.deleteFolder folder.Id
- prn "Deindexed"
+ printfn "Deindexed"
0
type InfoCommand () =
@@ -87,11 +97,11 @@ type InfoCommand () =
match findFolder () with
| None -> notInitialized ()
| Some folder ->
- prn $"[teal]Path:[/]\n {folder.Id}"
- prn $"[teal]Pattern:[/]\n {folder.Pattern}"
- prn $"[teal]Indexed files:[/]"
+ markup $"[teal]Path:[/]\n {folder.Id.EscapeMarkup()}"
+ markup $"[teal]Pattern:[/]\n {folder.Pattern.EscapeMarkup()}"
+ markup $"[teal]Indexed files:[/]"
for file in folder.Files do
- prn $" {file.Name}"
+ printfn $" {file.Name}"
0
type RefreshCommand () =
@@ -105,7 +115,8 @@ type RefreshCommand () =
IO.getFiles
Db.upsertFolder
Db.deleteFolder
- folder.Id folder.Pattern
+ folder.Id
+ (Pattern.from folder.Pattern folder.IsRegex)
|> withLoader
|> Option.iter prompt
0
@@ -121,7 +132,7 @@ module Program =
conf.AddCommand("search")
.WithDescription("(Default) Type to search. Arrows Up/Down to navigate. Enter to launch the file.") |> ignore
conf.AddCommand("index")
- .WithDescription("Indexes all files recursively with a specific pattern.") |> ignore
+ .WithDescription("Indexes all files recursively with a specific pattern which can be a wildcard (default) or a regular expression (--regex).") |> ignore
conf.AddCommand("deindex")
.WithDescription("Clears the current index.") |> ignore
conf.AddCommand("info")
diff --git a/README.md b/README.md
index a681861..7d5ad65 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ OPTIONS:
COMMANDS:
search (Default) Type to search. Arrows Up/Down to navigate. Enter to launch the file
- index Indexes all files recursively with a specific pattern
+ index Indexes all files recursively with a specific pattern which can be a wildcard (default) or a regular expression (--regex)
deindex Clears the current index
info Prints the current pattern and all the indexed files
refresh Updates the current index
From 914ea58e62c1a67f7bdb67cdd3dfac7f594edc89 Mon Sep 17 00:00:00 2001
From: Amin Khansari
Date: Fri, 11 Mar 2022 23:43:54 +0100
Subject: [PATCH 10/77] feat: Handle multiple launchers and choose between file
or directory
---
ExtLauncher.Tests/AppTests.fs | 14 ++-
ExtLauncher.Tests/ConsoleTests.fs | 38 +++---
ExtLauncher.Tests/DomainTests.fs | 2 +-
ExtLauncher/App.fs | 34 +++--
ExtLauncher/Console.fs | 15 ++-
ExtLauncher/Domain.fs | 42 +++++--
ExtLauncher/Program.fs | 203 ++++++++++++++++++++++++------
README.md | 13 +-
8 files changed, 265 insertions(+), 96 deletions(-)
diff --git a/ExtLauncher.Tests/AppTests.fs b/ExtLauncher.Tests/AppTests.fs
index 81605ed..6069d0e 100644
--- a/ExtLauncher.Tests/AppTests.fs
+++ b/ExtLauncher.Tests/AppTests.fs
@@ -10,7 +10,11 @@ let ``should load a folder`` () =
let loadFiles _ _ =
[| "/test/file2.ext", "file2"
"/test/file1.ext", "file1" |]
- let folder = App.loadFolder loadFiles folderPath (WildcardPattern pattern)
+ let folder =
+ App.loadFolder loadFiles
+ { Path = folderPath
+ Pattern = Pattern.from pattern false
+ Launchers = Array.empty }
folder =! Some
{ Id = folderPath
Pattern = pattern
@@ -18,10 +22,14 @@ let ``should load a folder`` () =
Files =
[| File.create "/test/file1.ext" "file1"
File.create "/test/file2.ext" "file2" |]
- OpenWith = Array.empty }
+ Launchers = Array.empty }
[]
let ``should not load a folder if no result`` () =
let loadFiles _ _ = Array.empty
- let folder = App.loadFolder loadFiles "" (WildcardPattern "")
+ let folder =
+ App.loadFolder loadFiles
+ { Path = ""
+ Pattern = Pattern.from "" false
+ Launchers = Array.empty }
folder =! None
diff --git a/ExtLauncher.Tests/ConsoleTests.fs b/ExtLauncher.Tests/ConsoleTests.fs
index f7228e9..478ec69 100644
--- a/ExtLauncher.Tests/ConsoleTests.fs
+++ b/ExtLauncher.Tests/ConsoleTests.fs
@@ -53,10 +53,11 @@ let upKey = ConsoleKey.UpArrow, char ConsoleKey.UpArrow, noConsoleModifier
let backspaceKey = ConsoleKey.Backspace, char ConsoleKey.Backspace, noConsoleModifier
let escapeKey = ConsoleKey.Escape, char ConsoleKey.Escape, ConsoleModifiers.Alt
let aKey k = enum(Char.ToUpper k |> int), k, noConsoleModifier
-let [] SearchSentence = "[teal]Search a file to launch:[/] "
+let [] PromptTitle = "Search and choose"
+let [] PrintedTitle = "[teal]" + PromptTitle + "[/] "
let printedLines maxChoices itemsCount chosenNum = [
- SearchSentence
+ PrintedTitle
for n in 1..itemsCount do
$"""[yellow]{if n = chosenNum then ">" else " "} [/]{n}"""
for _ in itemsCount+1..maxChoices do
@@ -71,7 +72,7 @@ let ``prompt should print choices`` () =
let _ =
fun _ -> [| 1; 2; 3 |]
- |> Console.prompt term 5
+ |> Console.prompt term PromptTitle 5
List.ofSeq lines =! printedLines 5 3 1
@@ -83,7 +84,7 @@ let ``prompt should go down`` () =
let _ =
fun _ -> [| 1; 2; 3 |]
- |> Console.prompt term 5
+ |> Console.prompt term PromptTitle 5
List.ofSeq lines =! printedLines 5 3 2
@@ -95,7 +96,7 @@ let ``prompt should go up`` () =
let _ =
fun _ -> [| 1; 2; 3 |]
- |> Console.prompt term 5
+ |> Console.prompt term PromptTitle 5
List.ofSeq lines =! printedLines 5 3 1
@@ -107,7 +108,7 @@ let ``prompt should stay up`` () =
let _ =
fun _ -> [| 1; 2; 3 |]
- |> Console.prompt term 5
+ |> Console.prompt term PromptTitle 5
List.ofSeq lines =! printedLines 5 3 1
@@ -119,7 +120,7 @@ let ``prompt should stay down`` () =
let _ =
fun _ -> [| 1; 2; 3 |]
- |> Console.prompt term 5
+ |> Console.prompt term PromptTitle 5
List.ofSeq lines =! printedLines 5 3 3
@@ -131,13 +132,10 @@ let ``prompt should choose the second choice and clear`` () =
let chosen =
fun _ -> [| 1; 2; 3 |]
- |> Console.prompt term 5
+ |> Console.prompt term PromptTitle 5
chosen =! Some 2
- List.ofSeq lines =! [
- """Launching "2"..."""
- ""; ""; ""; ""; ""
- ]
+ Seq.forall ((=) "") lines =! true
[]
let ``prompt should print error if no match`` () =
@@ -147,24 +145,24 @@ let ``prompt should print error if no match`` () =
let _ =
fun _ -> Array.empty
- |> Console.prompt term 1
+ |> Console.prompt term PromptTitle 1
List.ofSeq lines =! [
- SearchSentence
+ PrintedTitle
"No items match your search."
]
[]
-let ``prompt should print the search chars`` () =
+let ``prompt should print the search title`` () =
let lines = ResizeArray()
let keyReader = Queue [ aKey 't'; aKey 'e'; aKey 's'; aKey 't'; escapeKey ]
let term = newTerminal keyReader lines
let _ =
fun _ -> [| 1; 2; 3 |]
- |> Console.prompt term 1
+ |> Console.prompt term PromptTitle 1
- Seq.head lines =! $"{SearchSentence}test"
+ Seq.head lines =! $"{PrintedTitle}test"
[]
let ``prompt should print the search chars supporting backspace`` () =
@@ -174,9 +172,9 @@ let ``prompt should print the search chars supporting backspace`` () =
let _ =
fun _ -> [| 1; 2; 3 |]
- |> Console.prompt term 1
+ |> Console.prompt term PromptTitle 1
- Seq.head lines =! $"{SearchSentence}tst"
+ Seq.head lines =! $"{PrintedTitle}tst"
[]
let ``prompt should clear when exit`` () =
@@ -186,6 +184,6 @@ let ``prompt should clear when exit`` () =
let _ =
fun _ -> [| 1; 2; 3 |]
- |> Console.prompt term 5
+ |> Console.prompt term PromptTitle 5
Seq.forall ((=) "") lines =! true
diff --git a/ExtLauncher.Tests/DomainTests.fs b/ExtLauncher.Tests/DomainTests.fs
index 4dc4931..93999bb 100644
--- a/ExtLauncher.Tests/DomainTests.fs
+++ b/ExtLauncher.Tests/DomainTests.fs
@@ -35,4 +35,4 @@ let ``File with the same trigger should be sorted alphabetically`` (file1: File)
let ``searchByName should search for the containing string ignoring case`` (file: File) (files: File array) =
let file = { file with Name = "Hello World" }
let files = Array.insertAt 0 file files
- File.searchByName files "world" =! [| file |]
+ Helpers.searchByName files "world" =! [| file |]
diff --git a/ExtLauncher/App.fs b/ExtLauncher/App.fs
index e392621..8c97472 100644
--- a/ExtLauncher/App.fs
+++ b/ExtLauncher/App.fs
@@ -1,28 +1,36 @@
module ExtLauncher.App
-let loadFolder loadFiles folderPath pattern : Folder option =
- loadFiles folderPath pattern
+type FolderConf =
+ { Path: string
+ Pattern: Pattern
+ Launchers: Launcher array }
+
+let loadFolder loadFiles conf : Folder option =
+ loadFiles conf.Path conf.Pattern
|> Array.map ((<||) File.create)
|> Array.sort
|> function
| [||] -> None
| files ->
- { Id = folderPath
- Pattern = Pattern.value pattern
- IsRegex = Pattern.isRegex pattern
- Files = files
- OpenWith = Array.empty }
+ { Id = conf.Path
+ Pattern = Pattern.value conf.Pattern
+ IsRegex = Pattern.isRegex conf.Pattern
+ Launchers = conf.Launchers
+ Files = files }
|> Some
-let index loadFiles save folderPath pattern : Folder option =
- loadFolder loadFiles folderPath pattern
+let index loadFiles save conf : Folder option =
+ loadFolder loadFiles conf
|> Option.map save
-let refresh loadFiles save delete folderPath pattern =
- index loadFiles save folderPath pattern
+let refresh loadFiles save delete folder =
+ { Path = folder.Id
+ Pattern = Pattern.from folder.Pattern folder.IsRegex
+ Launchers = folder.Launchers }
+ |> index loadFiles save
|> Option.orElseWith (fun () ->
- delete folderPath
+ delete folder.Id
None)
let makeSearcher folder =
- File.searchByName folder.Files
+ Helpers.searchByName folder.Files
diff --git a/ExtLauncher/Console.fs b/ExtLauncher/Console.fs
index 5ea2d69..43ce035 100644
--- a/ExtLauncher/Console.fs
+++ b/ExtLauncher/Console.fs
@@ -42,7 +42,7 @@ let Terminal = { new ITerminal with
member _.Markup str = AnsiConsole.Markup str
member _.MarkupLine str = AnsiConsole.MarkupLine str
member this.ClearLine () =
- String(' ', Console.BufferWidth - 1) |> this.Markup
+ String(' ', Console.BufferWidth - 1) |> this.WriteLine
}
let [] NoMatch = "No items match your search."
@@ -60,7 +60,7 @@ let checkNoMatch (term: ITerminal) (search: string -> 'T array) =
else
Some search
-let prompt<'T> (term: ITerminal) maxChoices (search: string -> 'T array) =
+let prompt<'T> (term: ITerminal) title maxChoices (search: string -> 'T array) =
for _ in 0..maxChoices do term.WriteLine "" // allocate buffer area
let cursorTop =
@@ -86,7 +86,7 @@ let prompt<'T> (term: ITerminal) maxChoices (search: string -> 'T array) =
(string choice)
|> term.MarkupLine)
term.SetCursorPosition (0, cursorTop)
- term.Markup $"[teal]Search a file to launch:[/] %s{str}"
+ term.Markup $"[teal]%s{title}[/] %s{str}"
term.ShowCursor ()
(choices, str, pos)
@@ -103,17 +103,16 @@ let prompt<'T> (term: ITerminal) maxChoices (search: string -> 'T array) =
then read (choices, str, pos)
else
clearUp term cursorTop maxChoices
- term.WriteLine $"""Launching "{choices[pos]}"..."""
Some choices[pos]
| ConsoleKey.UpArrow, _, _ ->
- print (choices, str, pos - 1) |> read
+ read ((choices, str, pos - 1) |> print)
| ConsoleKey.DownArrow, _, _ ->
- print (choices, str, pos + 1) |> read
+ read ((choices, str, pos + 1) |> print)
| ConsoleKey.Backspace, _, _ ->
if str.Length = 0
then read (choices, str, pos)
- else search str[..^1] |> print |> read
+ else read (search str[..^1] |> print)
| _, key, _ ->
- search $"{str}{key}" |> print |> read
+ read (search $"{str}{key}" |> print)
search String.Empty |> print |> read
diff --git a/ExtLauncher/Domain.fs b/ExtLauncher/Domain.fs
index bd0bdc9..598bcb2 100644
--- a/ExtLauncher/Domain.fs
+++ b/ExtLauncher/Domain.fs
@@ -35,24 +35,32 @@ module File =
let triggered file =
{ file with Triggered = file.Triggered + 1 }
- let searchByName files name =
- if String.IsNullOrWhiteSpace name then
- files
- else
- files
- |> Array.filter (fun file ->
- if isNull file.Name
- then false
- else file.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
+type Choose =
+ | File = 0
+ | Directory = 1
+
+type Launcher =
+ { Name: string
+ Path: string
+ Arguments: string
+ Choose: Choose }
+ override this.ToString() = this.Name
+
+module Launcher =
+ let buildArgs launcher tolaunch =
+ if String.IsNullOrEmpty launcher.Arguments
+ then tolaunch
+ else launcher.Arguments.Replace("%s", $"\"{tolaunch}\"")
+// Should be serializable to BSON
[]
type Folder =
{ Id: string
Pattern: string
IsRegex: bool
- OpenWith: string array
+ Launchers: Launcher array
Files: File array }
- override this.ToString() = $"%A{this.Pattern} -> {this.Id}"
+ override this.ToString() = this.Id
type Pattern =
| WildcardPattern of string
@@ -62,3 +70,15 @@ module Pattern =
let value = function WildcardPattern p | RegexPattern p -> p
let isRegex = function WildcardPattern _ -> false | RegexPattern _ -> true
let from value isRegex = if isRegex then RegexPattern value else WildcardPattern value
+
+module Helpers =
+
+ let inline searchByName items str =
+ if String.IsNullOrEmpty str then
+ items
+ else
+ items
+ |> Array.filter (fun item ->
+ match (^T: (member Name: string) item) with
+ | null -> false
+ | name -> name.Contains(str, StringComparison.OrdinalIgnoreCase))
diff --git a/ExtLauncher/Program.fs b/ExtLauncher/Program.fs
index 3724e84..156d62c 100644
--- a/ExtLauncher/Program.fs
+++ b/ExtLauncher/Program.fs
@@ -6,31 +6,52 @@ open Spectre.Console
open Spectre.Console.Cli
[]
-module private Helpers =
+module private Implementations =
open System.Diagnostics
+ type Path = System.IO.Path
let markup value = AnsiConsole.MarkupLine value
let notInitialized () =
- printfn "Folder not yet indexed."
- markup $" [yellow]{IO.AppName}[/] index [teal][/]"
- printfn "For more information:"
- markup $" [yellow]{IO.AppName}[/] --help"
+ markup $"Folder not yet indexed: [yellow]{IO.AppName}[/] index [gray]--help[/]"
1
- let run (file: File) =
- let psi = ProcessStartInfo file.Id
- psi.UseShellExecute <- true
- Process.Start psi |> ignore
+ let run (file: File) launcher =
+ markup $"""Launching [green]{file.Name}[/]..."""
+ let file = file |> File.triggered |> Db.updateFile
+ match launcher with
+ | None ->
+ let psi = ProcessStartInfo file.Id
+ psi.UseShellExecute <- true
+ Process.Start psi |> ignore
+ | Some launcher ->
+ let path =
+ match launcher.Choose with
+ | Choose.File -> file.Id
+ | Choose.Directory -> Path.GetDirectoryName file.Id
+ | _ -> NotImplementedException() |> raise
+ let psi = ProcessStartInfo launcher.Path
+ psi.Arguments <- Launcher.buildArgs launcher path
+ Process.Start psi |> ignore
- let trigger =
- File.triggered >> Db.updateFile >> run
+ let chooseLauncher folder file =
+ match folder.Launchers with
+ | [| |] ->
+ run file None
+ | [| launcher |] ->
+ run file (Some launcher)
+ | launchers ->
+ Helpers.searchByName launchers
+ |> Console.prompt Console.Terminal "With which launcher?" 10
+ |> function
+ | Some launcher -> run file (Some launcher)
+ | None -> ()
let prompt folder =
folder
|> App.makeSearcher
- |> Console.prompt Console.Terminal 10
- |> Option.iter trigger
+ |> Console.prompt Console.Terminal "Search and launch:" 10
+ |> Option.iter (chooseLauncher folder)
let withLoader<'T> (worker: StatusContext -> 'T) =
AnsiConsole.Status().Start("Indexing...", worker)
@@ -40,15 +61,24 @@ module private Helpers =
let findFolder () =
let rec find path =
- if isNull path
- then None
- else
- match Db.findFolder path with
- | Some f -> Some f
- | None ->
- find (System.IO.Path.GetDirectoryName path)
+ if isNull path then None else
+ match Db.findFolder path with
+ | Some f -> Some f
+ | None -> find (Path.GetDirectoryName path)
find currentPath
+ let toCount str num =
+ if num > 1 then $"{num} {str}s" else $"{num} {str}"
+
+ let noNull s = if isNull s then "" else s
+
+ let printLaunchers folder =
+ let launchers = Table().AddColumns([| "Name"; "Choose"; "Path"; "Arguments" |])
+ launchers.Border <- TableBorder.Minimal
+ for l in folder.Launchers do
+ launchers.AddRow([| l.Name; string l.Choose; l.Path; noNull l.Arguments |]) |> ignore
+ AnsiConsole.Write launchers
+
type PromptCommand () =
inherit Command ()
override _.Execute c =
@@ -61,8 +91,7 @@ type IndexSettings () =
[")>]
[]
member val Pattern = "" with get, set
- []
- []
+ []
[]
member val IsRegex = false with get, set
@@ -70,17 +99,84 @@ type IndexCommand () =
inherit Command ()
override _.Execute (_, settings) =
fun _ ->
- Pattern.from settings.Pattern settings.IsRegex
- |> App.index IO.getFiles Db.upsertFolder currentPath
+ App.index IO.getFiles Db.upsertFolder
+ { Path = currentPath
+ Pattern = Pattern.from settings.Pattern settings.IsRegex
+ Launchers = Array.empty }
|> withLoader
|> function
| Some folder ->
- prompt folder
+ printfn $"""{toCount "file" folder.Files.Length} indexed."""
+ markup $"Start to search and launch: [yellow]{IO.AppName}[/]"
+ markup $"Add a specific launcher: [yellow]{IO.AppName}[/] launcher [gray]--help[/]"
0
| None ->
- Console.WriteLine Console.NoMatch
+ printfn $"{Console.NoMatch}"
-1
+type LauncherSettings () =
+ inherit CommandSettings ()
+ [")>]
+ []
+ member val Name = "" with get, set
+
+type SetLauncherSettings () =
+ inherit LauncherSettings ()
+ [")>]
+ []
+ member val Path = "" with get, set
+ []
+ []
+ member val Arguments = "" with get, set
+ []
+ []
+ member val Choose = Choose.File with get, set
+
+type RemoveLauncherSettings () =
+ inherit LauncherSettings ()
+
+type SetLauncherCommand () =
+ inherit Command ()
+ override _.Execute (_, settings) =
+ match findFolder () with
+ | None -> notInitialized ()
+ | Some folder ->
+ markup $"[teal]{settings.Name}[/] launcher updated."
+ { Name = settings.Name
+ Path = settings.Path
+ Arguments = settings.Arguments
+ Choose = settings.Choose }
+ |> fun launcher ->
+ match folder.Launchers |> Array.tryFindIndex (fun l -> l.Name = launcher.Name) with
+ | Some index ->
+ folder.Launchers.[index] <- launcher
+ folder
+ | None ->
+ { folder with Launchers = Array.insertAt 0 launcher folder.Launchers }
+ |> Db.upsertFolder
+ |> printLaunchers
+ 0
+ interface ICommandLimiter
+
+type RemoveLauncherCommand () =
+ inherit Command ()
+ override _.Execute (_, settings) =
+ match findFolder () with
+ | None -> notInitialized ()
+ | Some folder ->
+ match folder.Launchers |> Array.tryFindIndex (fun l -> l.Name = settings.Name) with
+ | Some index ->
+ markup $"[green]{settings.Name}[/] launcher removed."
+ { folder with Launchers = Array.removeAt index folder.Launchers }
+ |> Db.upsertFolder
+ |> printLaunchers
+ 0
+ | None ->
+ markup $"[green]{settings.Name}[/] launcher not found."
+ printLaunchers folder
+ 0
+ interface ICommandLimiter
+
type DeindexCommand () =
inherit Command ()
override _.Execute _ =
@@ -98,10 +194,22 @@ type InfoCommand () =
| None -> notInitialized ()
| Some folder ->
markup $"[teal]Path:[/]\n {folder.Id.EscapeMarkup()}"
- markup $"[teal]Pattern:[/]\n {folder.Pattern.EscapeMarkup()}"
+
+ markup $"\n[teal]Pattern:[/]\n {folder.Pattern.EscapeMarkup()}"
+
+ markup $"\n[teal]Launchers:[/]"
+ if Array.isEmpty folder.Launchers
+ then printfn " -\n"
+ else printLaunchers folder
+
markup $"[teal]Indexed files:[/]"
- for file in folder.Files do
- printfn $" {file.Name}"
+ let files = Table().AddColumns([| "Name"; "Triggered"; "Path" |])
+ files.Border <- TableBorder.Minimal
+ for f in folder.Files do
+ let path = f.Id.Remove(0, folder.Id.Length)
+ files.AddRow([| f.Name; string f.Triggered; path |]) |> ignore
+ AnsiConsole.Write files
+
0
type RefreshCommand () =
@@ -111,12 +219,8 @@ type RefreshCommand () =
| None -> notInitialized ()
| Some folder ->
fun _ ->
- App.refresh
- IO.getFiles
- Db.upsertFolder
- Db.deleteFolder
- folder.Id
- (Pattern.from folder.Pattern folder.IsRegex)
+ folder
+ |> App.refresh IO.getFiles Db.upsertFolder Db.deleteFolder
|> withLoader
|> Option.iter prompt
0
@@ -129,15 +233,38 @@ module Program =
app.Configure (fun conf ->
conf.SetApplicationName(IO.AppName) |> ignore
- conf.AddCommand("search")
- .WithDescription("(Default) Type to search. Arrows Up/Down to navigate. Enter to launch the file.") |> ignore
+ conf.AddCommand("prompt")
+ .WithDescription("[italic](default command)[/] Type to search. Arrows Up/Down to navigate. Enter to launch. Escape to quit.") |> ignore
+
conf.AddCommand("index")
- .WithDescription("Indexes all files recursively with a specific pattern which can be a wildcard (default) or a regular expression (--regex).") |> ignore
+ .WithDescription("Indexes all files recursively with a specific pattern which can be a wildcard [italic](default)[/] or a regular expression.") |> ignore
+
+ conf.AddBranch("launcher", fun launcher ->
+ launcher.SetDescription("Add, update or remove a launcher [italic](optional)[/].")
+ launcher.AddCommand("set")
+ .WithDescription("Add or update a launcher.") |> ignore
+ launcher.AddCommand("remove")
+ .WithDescription("Remove a launcher.") |> ignore
+ ) |> ignore
+
conf.AddCommand("deindex")
.WithDescription("Clears the current index.") |> ignore
+
conf.AddCommand("info")
.WithDescription("Prints the current pattern and all the indexed files.") |> ignore
+
conf.AddCommand("refresh")
.WithDescription("Updates the current index.") |> ignore
+
+ conf.AddExample([| "index"; "*.sln" |])
+ conf.AddExample([| "index"; "\"(.*)[.](fs|cs)proj$\""; "--regex" |])
+ conf.AddExample([| "launcher"; "notepad"; "set"; "notepad.exe" |])
+ conf.AddExample([| "launcher"; "notepad"; "remove" |])
+ conf.AddExample([| "launcher"; "vscode"; "set"; "/usr/bin/code"; "--choose"; "file"; "--args=\"-r %s\"" |])
+ conf.AddExample([| "launcher"; "vscode"; "set"; @"""C:\Users\$env:Username\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd"""; "--choose"; "directory" |])
+
+ #if DEBUG
+ conf.ValidateExamples() |> ignore
+ #endif
)
app.Run args
diff --git a/README.md b/README.md
index 7d5ad65..f05d891 100644
--- a/README.md
+++ b/README.md
@@ -38,12 +38,21 @@ dotnet tool install extLauncher
USAGE:
extLauncher [OPTIONS]
+EXAMPLES:
+ extLauncher index *.sln
+ extLauncher index "(.*)[.](fs|cs)proj$" --regex
+ extLauncher launcher notepad set notepad.exe
+ extLauncher launcher notepad remove
+ extLauncher launcher vscode set /usr/bin/code --choose file --args="-r %s"
+ extLauncher launcher vscode set "C:\Users\$env:Username\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd" --choose directory
+
OPTIONS:
-h, --help Prints help information
COMMANDS:
- search (Default) Type to search. Arrows Up/Down to navigate. Enter to launch the file
- index Indexes all files recursively with a specific pattern which can be a wildcard (default) or a regular expression (--regex)
+ prompt (default command) Type to search. Arrows Up/Down to navigate. Enter to launch. Escape to quit
+ index Indexes all files recursively with a specific pattern which can be a wildcard (default) or a regular expression
+ launcher Add, update or remove a launcher (optional)
deindex Clears the current index
info Prints the current pattern and all the indexed files
refresh Updates the current index
From 6ff4e80f35d3d99db3de6152e4a195666f9459de Mon Sep 17 00:00:00 2001
From: Amin Khansari
Date: Mon, 14 Mar 2022 20:33:06 +0100
Subject: [PATCH 11/77] fix(#11): Refresh should keep triggers
---
ExtLauncher.Tests/AppTests.fs | 48 ++++++++++++++++++++++++++++++++---
ExtLauncher/App.fs | 28 ++++++++++++++------
ExtLauncher/Program.fs | 2 +-
3 files changed, 65 insertions(+), 13 deletions(-)
diff --git a/ExtLauncher.Tests/AppTests.fs b/ExtLauncher.Tests/AppTests.fs
index 6069d0e..db02f4f 100644
--- a/ExtLauncher.Tests/AppTests.fs
+++ b/ExtLauncher.Tests/AppTests.fs
@@ -7,10 +7,10 @@ open Xunit
let ``should load a folder`` () =
let folderPath = "/test"
let pattern = "*.ext"
- let loadFiles _ _ =
- [| "/test/file2.ext", "file2"
- "/test/file1.ext", "file1" |]
let folder =
+ let loadFiles _ _ =
+ [| "/test/file2.ext", "file2"
+ "/test/file1.ext", "file1" |]
App.loadFolder loadFiles
{ Path = folderPath
Pattern = Pattern.from pattern false
@@ -26,10 +26,50 @@ let ``should load a folder`` () =
[]
let ``should not load a folder if no result`` () =
- let loadFiles _ _ = Array.empty
let folder =
+ let loadFiles _ _ = Array.empty
App.loadFolder loadFiles
{ Path = ""
Pattern = Pattern.from "" false
Launchers = Array.empty }
folder =! None
+
+[]
+let ``refresh should synchronize files`` () =
+ let newFolder =
+ let loadFiles _ _ =
+ [| "file1", ""
+ "file3", "" |]
+ let save = id
+ { Id = ""
+ Pattern = ""
+ IsRegex = false
+ Files =
+ [| File.create "file1" ""
+ File.create "file2" "" |]
+ Launchers = Array.empty }
+ |> App.refresh loadFiles save
+ |> Option.get
+
+ newFolder.Files.[0].Id =! "file1"
+ newFolder.Files.[1].Id =! "file3"
+
+[]
+let ``refresh should keep triggers`` () =
+ let newFolder =
+ let loadFiles _ _ =
+ [| "file1", ""
+ "file2", "" |]
+ let save = id
+ { Id = ""
+ Pattern = ""
+ IsRegex = false
+ Files =
+ [| File.create "file1" "" |> File.triggered
+ File.create "file2" "" |]
+ Launchers = Array.empty }
+ |> App.refresh loadFiles save
+ |> Option.get
+
+ newFolder.Files.[0].Triggered =! 1
+ newFolder.Files.[1].Triggered =! 0
diff --git a/ExtLauncher/App.fs b/ExtLauncher/App.fs
index 8c97472..3bbb73d 100644
--- a/ExtLauncher/App.fs
+++ b/ExtLauncher/App.fs
@@ -23,14 +23,26 @@ let index loadFiles save conf : Folder option =
loadFolder loadFiles conf
|> Option.map save
-let refresh loadFiles save delete folder =
- { Path = folder.Id
- Pattern = Pattern.from folder.Pattern folder.IsRegex
- Launchers = folder.Launchers }
- |> index loadFiles save
- |> Option.orElseWith (fun () ->
- delete folder.Id
- None)
+let refresh loadFiles save (folder: Folder) : Folder option =
+
+ let newFiles =
+ Pattern.from folder.Pattern folder.IsRegex
+ |> loadFiles folder.Id
+ |> Array.map ((<||) File.create)
+
+ let currentFiles =
+ folder.Files
+ |> Array.map (fun f -> f.Id, f)
+ |> Map
+
+ newFiles
+ |> Array.map (fun newFile ->
+ match currentFiles.TryFind newFile.Id with
+ | Some current -> { newFile with Triggered = current.Triggered }
+ | None -> newFile)
+ |> fun files -> { folder with Files = files }
+ |> save
+ |> Some
let makeSearcher folder =
Helpers.searchByName folder.Files
diff --git a/ExtLauncher/Program.fs b/ExtLauncher/Program.fs
index 156d62c..b93f3d4 100644
--- a/ExtLauncher/Program.fs
+++ b/ExtLauncher/Program.fs
@@ -220,7 +220,7 @@ type RefreshCommand () =
| Some folder ->
fun _ ->
folder
- |> App.refresh IO.getFiles Db.upsertFolder Db.deleteFolder
+ |> App.refresh IO.getFiles Db.upsertFolder
|> withLoader
|> Option.iter prompt
0
From 0cb84260d55506dca29b3eaa03bc22e313e6c879 Mon Sep 17 00:00:00 2001
From: Amin Khansari
Date: Mon, 14 Mar 2022 21:14:15 +0100
Subject: [PATCH 12/77] fix: Sort before display
---
ExtLauncher/App.fs | 5 +++--
ExtLauncher/Program.fs | 7 ++++---
README.md | 7 ++++---
3 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/ExtLauncher/App.fs b/ExtLauncher/App.fs
index 3bbb73d..f18fa19 100644
--- a/ExtLauncher/App.fs
+++ b/ExtLauncher/App.fs
@@ -44,5 +44,6 @@ let refresh loadFiles save (folder: Folder) : Folder option =
|> save
|> Some
-let makeSearcher folder =
- Helpers.searchByName folder.Files
+let makeSearcher folder str =
+ Helpers.searchByName folder.Files str
+ |> Array.sort
diff --git a/ExtLauncher/Program.fs b/ExtLauncher/Program.fs
index b93f3d4..1b239bd 100644
--- a/ExtLauncher/Program.fs
+++ b/ExtLauncher/Program.fs
@@ -258,10 +258,11 @@ module Program =
conf.AddExample([| "index"; "*.sln" |])
conf.AddExample([| "index"; "\"(.*)[.](fs|cs)proj$\""; "--regex" |])
- conf.AddExample([| "launcher"; "notepad"; "set"; "notepad.exe" |])
- conf.AddExample([| "launcher"; "notepad"; "remove" |])
+ conf.AddExample([| "launcher"; "mylauncher"; "set"; "execpath" |])
+ conf.AddExample([| "launcher"; "mylauncher"; "remove" |])
conf.AddExample([| "launcher"; "vscode"; "set"; "/usr/bin/code"; "--choose"; "file"; "--args=\"-r %s\"" |])
- conf.AddExample([| "launcher"; "vscode"; "set"; @"""C:\Users\$env:Username\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd"""; "--choose"; "directory" |])
+ conf.AddExample([| "launcher"; "vscode"; "set"; @"C:\Users\$env:Username\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd"; "--choose"; "directory" |])
+ conf.AddExample([| "launcher"; "explorer"; "set"; "explorer.exe"; "--choose"; "directory" |])
#if DEBUG
conf.ValidateExamples() |> ignore
diff --git a/README.md b/README.md
index f05d891..36c6a81 100644
--- a/README.md
+++ b/README.md
@@ -41,10 +41,11 @@ USAGE:
EXAMPLES:
extLauncher index *.sln
extLauncher index "(.*)[.](fs|cs)proj$" --regex
- extLauncher launcher notepad set notepad.exe
- extLauncher launcher notepad remove
+ extLauncher launcher mylauncher set execpath
+ extLauncher launcher mylauncher remove
extLauncher launcher vscode set /usr/bin/code --choose file --args="-r %s"
- extLauncher launcher vscode set "C:\Users\$env:Username\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd" --choose directory
+ extLauncher launcher vscode set C:\Users\$env:Username\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd --choose directory
+ extLauncher launcher explorer set explorer.exe --choose directory
OPTIONS:
-h, --help Prints help information
From 746fb5395447b1c6de5c7f055c38ba62de158268 Mon Sep 17 00:00:00 2001
From: Amin Khansari
Date: Mon, 14 Mar 2022 21:18:58 +0100
Subject: [PATCH 13/77] chors: remove useless files
---
ExtLauncher.Tests/ExtLauncher.Tests.v3.ncrunchproject | 5 -----
ExtLauncher/ExtLauncher.net5.0.v3.ncrunchproject | 5 -----
ExtLauncher/ExtLauncher.net6.0.v3.ncrunchproject | 5 -----
ExtLauncher/ExtLauncher.netcoreapp3.1.v3.ncrunchproject | 5 -----
4 files changed, 20 deletions(-)
delete mode 100644 ExtLauncher.Tests/ExtLauncher.Tests.v3.ncrunchproject
delete mode 100644 ExtLauncher/ExtLauncher.net5.0.v3.ncrunchproject
delete mode 100644 ExtLauncher/ExtLauncher.net6.0.v3.ncrunchproject
delete mode 100644 ExtLauncher/ExtLauncher.netcoreapp3.1.v3.ncrunchproject
diff --git a/ExtLauncher.Tests/ExtLauncher.Tests.v3.ncrunchproject b/ExtLauncher.Tests/ExtLauncher.Tests.v3.ncrunchproject
deleted file mode 100644
index cf22dfa..0000000
--- a/ExtLauncher.Tests/ExtLauncher.Tests.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/ExtLauncher/ExtLauncher.net5.0.v3.ncrunchproject b/ExtLauncher/ExtLauncher.net5.0.v3.ncrunchproject
deleted file mode 100644
index cf22dfa..0000000
--- a/ExtLauncher/ExtLauncher.net5.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/ExtLauncher/ExtLauncher.net6.0.v3.ncrunchproject b/ExtLauncher/ExtLauncher.net6.0.v3.ncrunchproject
deleted file mode 100644
index cf22dfa..0000000
--- a/ExtLauncher/ExtLauncher.net6.0.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/ExtLauncher/ExtLauncher.netcoreapp3.1.v3.ncrunchproject b/ExtLauncher/ExtLauncher.netcoreapp3.1.v3.ncrunchproject
deleted file mode 100644
index cf22dfa..0000000
--- a/ExtLauncher/ExtLauncher.netcoreapp3.1.v3.ncrunchproject
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
From 1084ea17a9f017a5c0f1acec66defcea090bf468 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Mon, 21 Mar 2022 11:47:05 +0100
Subject: [PATCH 14/77] docs: add caches and data
Mac is missing because I am not a mac user
---
README.md | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/README.md b/README.md
index 36c6a81..c6e76a7 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,13 @@ COMMANDS:
refresh Updates the current index
```
+# Caches and data generated by extLauncher
+
+To improve its performance, this tool maintains a database. You should be able to find it in the most obvious place for your operating system:
+
+- Windows: `%appdata%\Roaming\extLauncher\extLauncher.db`
+- Linux: `~/.config/ext-launcher/extLauncher.db`
+
# License
[MIT](./LICENSE.md)
From 4c26b876a5d96c3858b34cf3bf41a14ffcec3275 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Mon, 21 Mar 2022 11:49:06 +0100
Subject: [PATCH 15/77] docs: clarify
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index c6e76a7..eed97e1 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ COMMANDS:
# Caches and data generated by extLauncher
-To improve its performance, this tool maintains a database. You should be able to find it in the most obvious place for your operating system:
+This tool maintains a database to improve its performance. You should be able to find it in the most obvious place for your operating system:
- Windows: `%appdata%\Roaming\extLauncher\extLauncher.db`
- Linux: `~/.config/ext-launcher/extLauncher.db`
From 09a121911321cc05c84a2d24668a0a4dfd1d7466 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Mon, 21 Mar 2022 12:04:47 +0100
Subject: [PATCH 16/77] fix(pack): remove PublishSingleFile
---
ExtLauncher/ExtLauncher.fsproj | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/ExtLauncher/ExtLauncher.fsproj b/ExtLauncher/ExtLauncher.fsproj
index 57a7ded..0f89d2a 100644
--- a/ExtLauncher/ExtLauncher.fsproj
+++ b/ExtLauncher/ExtLauncher.fsproj
@@ -1,10 +1,9 @@
-
+
Exe
netcoreapp3.1;net6.0
preview
- true
true
true
From f928de3c0ae68a91697f13bf620ff34cef60d1c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Mon, 21 Mar 2022 12:05:57 +0100
Subject: [PATCH 17/77] chore: ignore nuget leftovers
---
.gitignore | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/.gitignore b/.gitignore
index 1d7e46f..1b5563e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,16 @@ _NCrunch_*
paket.local
paket-files
+
+# From https://gist.github.com/geoder101/aa2ab1ab417fbc52bb8b
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
From 25bcb31b6f5b0eb81a09218ea82d1c89821a7fab Mon Sep 17 00:00:00 2001
From: aloisdg
Date: Mon, 21 Mar 2022 12:28:51 +0100
Subject: [PATCH 18/77] feat(dotnet): install the tool locally
---
.config/dotnet-tools.json | 12 ++++++++++++
README.md | 6 ++++++
2 files changed, 18 insertions(+)
create mode 100644 .config/dotnet-tools.json
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..8e7bbfd
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "extlauncher": {
+ "version": "1.0.0",
+ "commands": [
+ "extLauncher"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 36c6a81..a1cd48b 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,12 @@ COMMANDS:
refresh Updates the current index
```
+# Build locally
+
+- Clone the repository
+- Open the repository
+- Invoke the tool by running the `dotnet tool run` command: `dotnet tool run extlauncher` (with your arguments)
+
# License
[MIT](./LICENSE.md)
From 1bd3559604a734b391eb890cb0b3af63673f16db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Thu, 31 Mar 2022 20:48:14 +0200
Subject: [PATCH 19/77] chore: replace profile with organization
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 66e3672..8787055 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,16 @@
-
+
+
-->
-
+
From bc67a49eccb1ca8be4c9b02e59bc07d6357d0f89 Mon Sep 17 00:00:00 2001
From: aloisdg
Date: Thu, 31 Mar 2022 21:00:15 +0200
Subject: [PATCH 20/77] ci(github): init ci/cd
---
.github/workflows/build.yml | 93 +++++++++++++++++++++++++++++++++++++
1 file changed, 93 insertions(+)
create mode 100644 .github/workflows/build.yml
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..42b4a37
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,93 @@
+name: .NET Core
+on:
+ push:
+ pull_request:
+ release:
+ types:
+ - published
+env:
+ # Stop wasting time caching packages
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ # Disable sending usage data to Microsoft
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ # Project name to pack and publish
+ PROJECT_NAME: ExtLauncher
+ # GitHub Packages Feed settings
+ GITHUB_FEED: https://nuget.pkg.github.com/d-edge/
+ GITHUB_USER: akhansari
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # Official NuGet Feed settings
+ NUGET_FEED: https://api.nuget.org/v3/index.json
+ NUGET_KEY: ${{ secrets.NUGET_KEY }}
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, windows-latest, macos-latest ]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 6.0.100
+ - name: Tool restore
+ run: dotnet tool restore
+ - name: Restore
+ run: dotnet restore
+ - name: Build
+ run: dotnet build -c Release --no-restore
+ - name: Test
+ run: dotnet test -c Release
+ - name: Pack
+ if: matrix.os == 'ubuntu-latest'
+ run: dotnet pack -v normal -c Release --no-restore -p:PackageVersion=$GITHUB_RUN_ID.0.0 $PROJECT_NAME/$PROJECT_NAME.fsproj -o bin/nuget
+ - name: Upload Artifact
+ if: matrix.os == 'ubuntu-latest'
+ uses: actions/upload-artifact@v2
+ with:
+ name: nupkg
+ path: ./bin/nuget/*.nupkg
+ prerelease:
+ needs: build
+ if: github.ref == 'refs/heads/develop'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download Artifact
+ uses: actions/download-artifact@v1
+ with:
+ name: nupkg
+ - name: Push to GitHub Feed
+ run: |
+ for f in ./nupkg/*.nupkg
+ do
+ curl -vX PUT -u "$GITHUB_USER:$GITHUB_TOKEN" -F package=@$f $GITHUB_FEED
+ done
+ deploy:
+ needs: build
+ if: github.event_name == 'release'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 6.0.100
+ - name: Create Release NuGet package
+ run: |
+ arrTag=(${GITHUB_REF//\// })
+ VERSION="${arrTag[2]}"
+ echo Version: $VERSION
+ VERSION="${VERSION//v}"
+ echo Clean Version: $VERSION
+ dotnet tool restore
+ dotnet pack -v normal -c Release -p:PackageVersion=$VERSION -o nupkg $PROJECT_NAME/$PROJECT_NAME.fsproj
+ - name: Push to GitHub Feed
+ run: |
+ for f in ./nupkg/*.nupkg
+ do
+ curl -vX PUT -u "$GITHUB_USER:$GITHUB_TOKEN" -F package=@$f $GITHUB_FEED
+ done
+ - name: Push to NuGet Feed
+ run: dotnet nuget push ./nupkg/*.nupkg --source $NUGET_FEED --skip-duplicate --api-key $NUGET_KEY
\ No newline at end of file
From 17eb493a69d3f9ba8810daa348baa6a6b679d7b4 Mon Sep 17 00:00:00 2001
From: aloisdg
Date: Thu, 31 Mar 2022 21:05:39 +0200
Subject: [PATCH 21/77] chore: align name
---
.config/dotnet-tools.json | 2 +-
README.md | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 8e7bbfd..477d99f 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -2,7 +2,7 @@
"version": 1,
"isRoot": true,
"tools": {
- "extlauncher": {
+ "extLauncher": {
"version": "1.0.0",
"commands": [
"extLauncher"
diff --git a/README.md b/README.md
index 8787055..d80de05 100644
--- a/README.md
+++ b/README.md
@@ -63,14 +63,14 @@ COMMANDS:
- Clone the repository
- Open the repository
-- Invoke the tool by running the `dotnet tool run` command: `dotnet tool run extlauncher` (with your arguments)
+- Invoke the tool by running the `dotnet tool run` command: `dotnet tool run extLauncher` (with your arguments)
# Caches and data generated by extLauncher
This tool maintains a database to improve its performance. You should be able to find it in the most obvious place for your operating system:
- Windows: `%appdata%\Roaming\extLauncher\extLauncher.db`
-- Linux: `~/.config/ext-launcher/extLauncher.db`
+- Linux: `~/.config/extLauncher/extLauncher.db`
# License
From b4eb18da204d2fa4ccf40f16ace787b23cecd5a2 Mon Sep 17 00:00:00 2001
From: aloisdg
Date: Thu, 31 Mar 2022 21:31:29 +0200
Subject: [PATCH 22/77] fix: add missing nuget property
---
ExtLauncher/ExtLauncher.fsproj | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/ExtLauncher/ExtLauncher.fsproj b/ExtLauncher/ExtLauncher.fsproj
index 0f89d2a..3aaa6d4 100644
--- a/ExtLauncher/ExtLauncher.fsproj
+++ b/ExtLauncher/ExtLauncher.fsproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -9,6 +9,22 @@
true
extLauncher
./nupkg
+
+ Copyright 2022 D-EDGE
+ D-EDGE and contributors
+
+
+ extLauncher
+ DEdge;launcher;extLauncher
+ https://github.com/d-edge/extLauncher/releases/
+ https://github.com/d-edge/extLauncher
+ MIT
+ logo-64x64.png
+ true
+ git
+ https://github.com/d-edge/extLauncher
+
+ README.md
From 67a5eb666c97345d49b9d797bfa58ddadcce4f3b Mon Sep 17 00:00:00 2001
From: aloisdg
Date: Fri, 1 Apr 2022 00:48:35 +0200
Subject: [PATCH 23/77] fix: add missing files
---
ExtLauncher/ExtLauncher.fsproj | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/ExtLauncher/ExtLauncher.fsproj b/ExtLauncher/ExtLauncher.fsproj
index 3aaa6d4..25acc48 100644
--- a/ExtLauncher/ExtLauncher.fsproj
+++ b/ExtLauncher/ExtLauncher.fsproj
@@ -9,11 +9,11 @@
true
extLauncher
./nupkg
-
- Copyright 2022 D-EDGE
- D-EDGE and contributors
-
-
+
+ Copyright (c) 2022 D-EDGE
+ Amin Khansari
+
+
extLauncher
DEdge;launcher;extLauncher
https://github.com/d-edge/extLauncher/releases/
@@ -23,7 +23,7 @@
true
git
https://github.com/d-edge/extLauncher
-
+
README.md
@@ -33,6 +33,13 @@
+
+
+ true
+ $(PackageIconUrl)
+
+
+
From dec11eb0a5f01dd18a0f1d488d232f9a27047fd0 Mon Sep 17 00:00:00 2001
From: aloisdg
Date: Fri, 1 Apr 2022 01:27:56 +0200
Subject: [PATCH 24/77] fix: change case
---
.github/workflows/build.yml | 2 +-
README.md | 2 +-
{ExtLauncher.Tests => extLauncher.Tests}/AppTests.fs | 2 +-
{ExtLauncher.Tests => extLauncher.Tests}/ConsoleTests.fs | 2 +-
{ExtLauncher.Tests => extLauncher.Tests}/DomainTests.fs | 2 +-
{ExtLauncher.Tests => extLauncher.Tests}/Program.fs | 0
.../extLauncher.Tests.fsproj | 2 +-
ExtLauncher.sln => extLauncher.sln | 4 ++--
{ExtLauncher => extLauncher}/App.fs | 2 +-
{ExtLauncher => extLauncher}/Console.fs | 2 +-
{ExtLauncher => extLauncher}/Domain.fs | 2 +-
{ExtLauncher => extLauncher}/Infra.fs | 2 +-
{ExtLauncher => extLauncher}/Program.fs | 2 +-
.../ExtLauncher.fsproj => extLauncher/extLauncher.fsproj | 3 ++-
14 files changed, 15 insertions(+), 14 deletions(-)
rename {ExtLauncher.Tests => extLauncher.Tests}/AppTests.fs (98%)
rename {ExtLauncher.Tests => extLauncher.Tests}/ConsoleTests.fs (99%)
rename {ExtLauncher.Tests => extLauncher.Tests}/DomainTests.fs (97%)
rename {ExtLauncher.Tests => extLauncher.Tests}/Program.fs (100%)
rename ExtLauncher.Tests/ExtLauncher.Tests.fsproj => extLauncher.Tests/extLauncher.Tests.fsproj (94%)
rename ExtLauncher.sln => extLauncher.sln (86%)
rename {ExtLauncher => extLauncher}/App.fs (97%)
rename {ExtLauncher => extLauncher}/Console.fs (99%)
rename {ExtLauncher => extLauncher}/Domain.fs (98%)
rename {ExtLauncher => extLauncher}/Infra.fs (98%)
rename {ExtLauncher => extLauncher}/Program.fs (99%)
rename ExtLauncher/ExtLauncher.fsproj => extLauncher/extLauncher.fsproj (96%)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 42b4a37..dea6d85 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,7 +11,7 @@ env:
# Disable sending usage data to Microsoft
DOTNET_CLI_TELEMETRY_OPTOUT: true
# Project name to pack and publish
- PROJECT_NAME: ExtLauncher
+ PROJECT_NAME: extLauncher
# GitHub Packages Feed settings
GITHUB_FEED: https://nuget.pkg.github.com/d-edge/
GITHUB_USER: akhansari
diff --git a/README.md b/README.md
index d80de05..4d82c92 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
-ExtLauncher is a dotnet tool to search and launch quickly projects in the user's preferred application. ExtLauncher is maintained by folks at [D-EDGE](https://www.d-edge.com/).
+extLauncher is a dotnet tool to search and launch quickly projects in the user's preferred application. extLauncher is maintained by folks at [D-EDGE](https://www.d-edge.com/).
# Getting Started
diff --git a/ExtLauncher.Tests/AppTests.fs b/extLauncher.Tests/AppTests.fs
similarity index 98%
rename from ExtLauncher.Tests/AppTests.fs
rename to extLauncher.Tests/AppTests.fs
index db02f4f..ebbe5d4 100644
--- a/ExtLauncher.Tests/AppTests.fs
+++ b/extLauncher.Tests/AppTests.fs
@@ -1,4 +1,4 @@
-module ExtLauncher.AppTests
+module extLauncher.AppTests
open Swensen.Unquote
open Xunit
diff --git a/ExtLauncher.Tests/ConsoleTests.fs b/extLauncher.Tests/ConsoleTests.fs
similarity index 99%
rename from ExtLauncher.Tests/ConsoleTests.fs
rename to extLauncher.Tests/ConsoleTests.fs
index 478ec69..b76f899 100644
--- a/ExtLauncher.Tests/ConsoleTests.fs
+++ b/extLauncher.Tests/ConsoleTests.fs
@@ -1,4 +1,4 @@
-module ExtLauncher.ConsoleTests
+module extLauncher.ConsoleTests
open System
open System.Collections.Generic
diff --git a/ExtLauncher.Tests/DomainTests.fs b/extLauncher.Tests/DomainTests.fs
similarity index 97%
rename from ExtLauncher.Tests/DomainTests.fs
rename to extLauncher.Tests/DomainTests.fs
index 93999bb..52d9f38 100644
--- a/ExtLauncher.Tests/DomainTests.fs
+++ b/extLauncher.Tests/DomainTests.fs
@@ -1,4 +1,4 @@
-module ExtLauncher.DomainTests
+module extLauncher.DomainTests
open System
open FsCheck.Xunit
diff --git a/ExtLauncher.Tests/Program.fs b/extLauncher.Tests/Program.fs
similarity index 100%
rename from ExtLauncher.Tests/Program.fs
rename to extLauncher.Tests/Program.fs
diff --git a/ExtLauncher.Tests/ExtLauncher.Tests.fsproj b/extLauncher.Tests/extLauncher.Tests.fsproj
similarity index 94%
rename from ExtLauncher.Tests/ExtLauncher.Tests.fsproj
rename to extLauncher.Tests/extLauncher.Tests.fsproj
index bb76b8d..1bdca11 100644
--- a/ExtLauncher.Tests/ExtLauncher.Tests.fsproj
+++ b/extLauncher.Tests/extLauncher.Tests.fsproj
@@ -30,7 +30,7 @@
-
+
diff --git a/ExtLauncher.sln b/extLauncher.sln
similarity index 86%
rename from ExtLauncher.sln
rename to extLauncher.sln
index fe4488c..19c1a81 100644
--- a/ExtLauncher.sln
+++ b/extLauncher.sln
@@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ExtLauncher", "ExtLauncher\ExtLauncher.fsproj", "{075AB28F-511D-4C65-97B9-399F442EC8B8}"
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "extLauncher", "extLauncher\extLauncher.fsproj", "{075AB28F-511D-4C65-97B9-399F442EC8B8}"
EndProject
-Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ExtLauncher.Tests", "ExtLauncher.Tests\ExtLauncher.Tests.fsproj", "{687867BF-FB37-43BF-B0FF-148313A40D5B}"
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "extLauncher.Tests", "extLauncher.Tests\extLauncher.Tests.fsproj", "{687867BF-FB37-43BF-B0FF-148313A40D5B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{503F5C7D-95C0-4B13-8BC6-AAF31EA9FDE2}"
ProjectSection(SolutionItems) = preProject
diff --git a/ExtLauncher/App.fs b/extLauncher/App.fs
similarity index 97%
rename from ExtLauncher/App.fs
rename to extLauncher/App.fs
index f18fa19..ab63f66 100644
--- a/ExtLauncher/App.fs
+++ b/extLauncher/App.fs
@@ -1,4 +1,4 @@
-module ExtLauncher.App
+module extLauncher.App
type FolderConf =
{ Path: string
diff --git a/ExtLauncher/Console.fs b/extLauncher/Console.fs
similarity index 99%
rename from ExtLauncher/Console.fs
rename to extLauncher/Console.fs
index 43ce035..481ddc4 100644
--- a/ExtLauncher/Console.fs
+++ b/extLauncher/Console.fs
@@ -1,4 +1,4 @@
-module ExtLauncher.Console
+module extLauncher.Console
open System
open Spectre.Console
diff --git a/ExtLauncher/Domain.fs b/extLauncher/Domain.fs
similarity index 98%
rename from ExtLauncher/Domain.fs
rename to extLauncher/Domain.fs
index 598bcb2..65fbcde 100644
--- a/ExtLauncher/Domain.fs
+++ b/extLauncher/Domain.fs
@@ -1,4 +1,4 @@
-namespace ExtLauncher
+namespace extLauncher
open System
diff --git a/ExtLauncher/Infra.fs b/extLauncher/Infra.fs
similarity index 98%
rename from ExtLauncher/Infra.fs
rename to extLauncher/Infra.fs
index b8fe0b6..a2c5aba 100644
--- a/ExtLauncher/Infra.fs
+++ b/extLauncher/Infra.fs
@@ -1,4 +1,4 @@
-namespace ExtLauncher
+namespace extLauncher
open System
diff --git a/ExtLauncher/Program.fs b/extLauncher/Program.fs
similarity index 99%
rename from ExtLauncher/Program.fs
rename to extLauncher/Program.fs
index 1b239bd..79aa0f9 100644
--- a/ExtLauncher/Program.fs
+++ b/extLauncher/Program.fs
@@ -1,4 +1,4 @@
-namespace ExtLauncher
+namespace extLauncher
open System
open System.ComponentModel
diff --git a/ExtLauncher/ExtLauncher.fsproj b/extLauncher/extLauncher.fsproj
similarity index 96%
rename from ExtLauncher/ExtLauncher.fsproj
rename to extLauncher/extLauncher.fsproj
index 25acc48..d4e7d94 100644
--- a/ExtLauncher/ExtLauncher.fsproj
+++ b/extLauncher/extLauncher.fsproj
@@ -27,6 +27,7 @@
README.md
+
@@ -39,7 +40,7 @@
$(PackageIconUrl)
-
+
From 0794f343900615d568b6f6b8fbaee7b4824a4443 Mon Sep 17 00:00:00 2001
From: aloisdg
Date: Fri, 1 Apr 2022 02:10:34 +0200
Subject: [PATCH 25/77] ci: add clean step
---
.github/workflows/build.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index dea6d85..59d10bd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -36,6 +36,8 @@ jobs:
run: dotnet tool restore
- name: Restore
run: dotnet restore
+ - name: Clean
+ run: dotnet clean ./extLauncher.sln -c Release && dotnet nuget locals all --clear
- name: Build
run: dotnet build -c Release --no-restore
- name: Test
From 607322d0df6036935639d656d9ad52f712087234 Mon Sep 17 00:00:00 2001
From: aloisdg
Date: Fri, 1 Apr 2022 02:22:34 +0200
Subject: [PATCH 26/77] ci: remove clean step
---
.github/workflows/build.yml | 41 ++++++++++++++++++++++---------------
1 file changed, 24 insertions(+), 17 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 59d10bd..5c58b94 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -32,28 +32,40 @@ jobs:
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
- - name: Tool restore
- run: dotnet tool restore
- name: Restore
run: dotnet restore
- - name: Clean
- run: dotnet clean ./extLauncher.sln -c Release && dotnet nuget locals all --clear
- name: Build
run: dotnet build -c Release --no-restore
- name: Test
run: dotnet test -c Release
+ - name: Strip HTML from README
+ uses: d-edge/strip-markdown-html@v0.1
+ with:
+ input-path: README.md
+ output-path: $PROJECT_NAME/README.md
- name: Pack
if: matrix.os == 'ubuntu-latest'
- run: dotnet pack -v normal -c Release --no-restore -p:PackageVersion=$GITHUB_RUN_ID.0.0 $PROJECT_NAME/$PROJECT_NAME.fsproj -o bin/nuget
+ run: |
+ if [ "$GITHUB_REF_TYPE" = "tag" ]; then
+ arrTag=(${GITHUB_REF//\// })
+ VERSION="${arrTag[2]}"
+ echo Version: $VERSION
+ VERSION="${VERSION//v}"
+ echo Clean Version: $VERSION
+ else
+ VERSION=$GITHUB_RUN_ID
+ echo Non-release version: $VERSION
+ fi
+ dotnet pack -v normal -c Release --no-restore --include-symbols --include-source -p:PackageVersion=$VERSION src/$PROJECT_NAME/$PROJECT_NAME.*proj
- name: Upload Artifact
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v2
with:
name: nupkg
- path: ./bin/nuget/*.nupkg
+ path: ./${{ env.PROJECT_NAME }}/bin/Release/*.nupkg
prerelease:
needs: build
- if: github.ref == 'refs/heads/develop'
+ if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Download Artifact
@@ -76,15 +88,10 @@ jobs:
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
- - name: Create Release NuGet package
- run: |
- arrTag=(${GITHUB_REF//\// })
- VERSION="${arrTag[2]}"
- echo Version: $VERSION
- VERSION="${VERSION//v}"
- echo Clean Version: $VERSION
- dotnet tool restore
- dotnet pack -v normal -c Release -p:PackageVersion=$VERSION -o nupkg $PROJECT_NAME/$PROJECT_NAME.fsproj
+ - name: Download Artifact
+ uses: actions/download-artifact@v1
+ with:
+ name: nupkg
- name: Push to GitHub Feed
run: |
for f in ./nupkg/*.nupkg
@@ -92,4 +99,4 @@ jobs:
curl -vX PUT -u "$GITHUB_USER:$GITHUB_TOKEN" -F package=@$f $GITHUB_FEED
done
- name: Push to NuGet Feed
- run: dotnet nuget push ./nupkg/*.nupkg --source $NUGET_FEED --skip-duplicate --api-key $NUGET_KEY
\ No newline at end of file
+ run: dotnet nuget push ./nupkg/*.nupkg --source $NUGET_FEED --skip-duplicate --api-key $NUGET_KEY
From 5588cc58829f7e381ecd55678bc5138985d85337 Mon Sep 17 00:00:00 2001
From: aloisdg
Date: Fri, 1 Apr 2022 02:24:37 +0200
Subject: [PATCH 27/77] ci: remove src folder
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5c58b94..e52a6be 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -56,7 +56,7 @@ jobs:
VERSION=$GITHUB_RUN_ID
echo Non-release version: $VERSION
fi
- dotnet pack -v normal -c Release --no-restore --include-symbols --include-source -p:PackageVersion=$VERSION src/$PROJECT_NAME/$PROJECT_NAME.*proj
+ dotnet pack -v normal -c Release --no-restore --include-symbols --include-source -p:PackageVersion=$VERSION $PROJECT_NAME/$PROJECT_NAME.*proj
- name: Upload Artifact
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v2
From a5d63482501636fd49d62508e2c6e5dcfc9ae9d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Mon, 4 Apr 2022 11:59:45 +0200
Subject: [PATCH 28/77] chore: add D-EDGE
---
LICENSE.md => LICENSE | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename LICENSE.md => LICENSE (96%)
diff --git a/LICENSE.md b/LICENSE
similarity index 96%
rename from LICENSE.md
rename to LICENSE
index e8d9225..01050c7 100644
--- a/LICENSE.md
+++ b/LICENSE
@@ -1,7 +1,7 @@
The MIT License (MIT)
-Copyright (c) 2022 Amin Khansari
+Copyright (c) 2022 Amin Khansari, D-EDGE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
From c09a15119415f02ad767a4705c6fc2a15cf912d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Mon, 4 Apr 2022 12:00:55 +0200
Subject: [PATCH 29/77] chore: update license link
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 4d82c92..90c756b 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
@@ -74,4 +74,4 @@ This tool maintains a database to improve its performance. You should be able to
# License
-[MIT](./LICENSE.md)
+[MIT](https://github.com/d-edge/extLauncher/blob/main/LICENSE)
From d256b941262d0200b17f5cc1b2d06b0738ce3cf7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Mon, 4 Apr 2022 12:10:02 +0200
Subject: [PATCH 30/77] ci: change artifact path
I am not sure why the path doesn't match anymore
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e52a6be..2099988 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -62,7 +62,7 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: nupkg
- path: ./${{ env.PROJECT_NAME }}/bin/Release/*.nupkg
+ path: ./${{ env.PROJECT_NAME }}/nupkg/*.nupkg
prerelease:
needs: build
if: github.ref == 'refs/heads/main'
From df9534bfbd6abbcc20468c127976f0fda97d9171 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alo=C3=AFs=20de=20Gouvello?=
Date: Mon, 4 Apr 2022 14:42:49 +0200
Subject: [PATCH 31/77] doc: show ci status
---
README.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 90c756b..d2f8014 100644
--- a/README.md
+++ b/README.md
@@ -5,13 +5,13 @@
-
+
+
+
+
-
-
From 0db9717b68325cdff6cdc745fce7810653c68de6 Mon Sep 17 00:00:00 2001
From: Amin Khansari
Date: Mon, 4 Apr 2022 15:35:52 +0200
Subject: [PATCH 32/77] chors: add terminal gif
---
README.md | 8 ++++++--
logo-64x64.png => assets/logo-64x64.png | Bin
logo.png => assets/logo.png | Bin
assets/terminal.gif | Bin 0 -> 1182017 bytes
extLauncher/Program.fs | 2 +-
5 files changed, 7 insertions(+), 3 deletions(-)
rename logo-64x64.png => assets/logo-64x64.png (100%)
rename logo.png => assets/logo.png (100%)
create mode 100644 assets/terminal.gif
diff --git a/README.md b/README.md
index d2f8014..d1684c2 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-
+
@@ -13,6 +13,10 @@
-->
+
+
+
+
extLauncher is a dotnet tool to search and launch quickly projects in the user's preferred application. extLauncher is maintained by folks at [D-EDGE](https://www.d-edge.com/).
@@ -44,7 +48,7 @@ EXAMPLES:
extLauncher launcher mylauncher set execpath
extLauncher launcher mylauncher remove
extLauncher launcher vscode set /usr/bin/code --choose file --args="-r %s"
- extLauncher launcher vscode set C:\Users\$env:Username\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd --choose directory
+ extLauncher launcher vscode set "$env:LOCALAPPDATA\Programs\Microsoft VS Code\bin\code.cmd" --choose directory
extLauncher launcher explorer set explorer.exe --choose directory
OPTIONS:
diff --git a/logo-64x64.png b/assets/logo-64x64.png
similarity index 100%
rename from logo-64x64.png
rename to assets/logo-64x64.png
diff --git a/logo.png b/assets/logo.png
similarity index 100%
rename from logo.png
rename to assets/logo.png
diff --git a/assets/terminal.gif b/assets/terminal.gif
new file mode 100644
index 0000000000000000000000000000000000000000..c1e4f4a300ba2823ac48a71288bed01f1131ae04
GIT binary patch
literal 1182017
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