diff --git a/.gitignore b/.gitignore index 3545968..e43b0f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ .DS_Store -Manifest.toml diff --git a/Manifest.toml b/Manifest.toml new file mode 100644 index 0000000..bba0dd7 --- /dev/null +++ b/Manifest.toml @@ -0,0 +1,443 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.8.1" +manifest_format = "2.0" +project_hash = "fbe0ee936dd5830e6ed140283f15265ec4d07a17" + +[[deps.ArgParse]] +deps = ["Logging", "TextWrap"] +git-tree-sha1 = "3102bce13da501c9104df33549f511cd25264d7d" +uuid = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" +version = "1.1.4" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.Arrow]] +deps = ["ArrowTypes", "BitIntegers", "CodecLz4", "CodecZstd", "DataAPI", "Dates", "Mmap", "PooledArrays", "SentinelArrays", "Tables", "TimeZones", "UUIDs"] +git-tree-sha1 = "4e7aa2021204bd9456ad3e87372237e84ee2c3c1" +uuid = "69666777-d1a9-59fb-9406-91d4454c9d45" +version = "2.3.0" + +[[deps.ArrowTypes]] +deps = ["UUIDs"] +git-tree-sha1 = "a0633b6d6efabf3f76dacd6eb1b3ec6c42ab0552" +uuid = "31f734f8-188a-4ce0-8406-c8a06bd891cd" +version = "1.2.1" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.BitIntegers]] +deps = ["Random"] +git-tree-sha1 = "5a814467bda636f3dde5c4ef83c30dd0a19928e0" +uuid = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" +version = "0.2.6" + +[[deps.BufferedStreams]] +git-tree-sha1 = "bb065b14d7f941b8617bc323063dbe79f55d16ea" +uuid = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d" +version = "1.1.0" + +[[deps.CEnum]] +git-tree-sha1 = "eb4cb44a499229b3b8426dcfb5dd85333951ff90" +uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" +version = "0.4.2" + +[[deps.CodecLz4]] +deps = ["Lz4_jll", "TranscodingStreams"] +git-tree-sha1 = "59fe0cb37784288d6b9f1baebddbf75457395d40" +uuid = "5ba52731-8f18-5e0d-9241-30f10d1ec561" +version = "0.4.0" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.0" + +[[deps.CodecZstd]] +deps = ["CEnum", "TranscodingStreams", "Zstd_jll"] +git-tree-sha1 = "849470b337d0fa8449c21061de922386f32949d9" +uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2" +version = "0.7.2" + +[[deps.Compat]] +deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] +git-tree-sha1 = "78bee250c6826e1cf805a88b7f1e86025275d208" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "3.46.0" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "0.5.2+0" + +[[deps.ConfParser]] +git-tree-sha1 = "db2712392fbf7dd12eff7c2ea620a34b5d0ad7b9" +uuid = "88353bc9-fd38-507d-a820-d3b43837d6b9" +version = "0.1.2" + +[[deps.DataAPI]] +git-tree-sha1 = "fb5f5316dd3fd4c5e7c30a24d50643b73e37cd40" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.10.0" + +[[deps.DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.DelimitedFiles]] +deps = ["Mmap"] +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.EnumX]] +git-tree-sha1 = "1d2621e1a6246c5cf1116be0055686f305210b80" +uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" +version = "1.0.2" + +[[deps.ExceptionUnwrapping]] +deps = ["Test"] +git-tree-sha1 = "f7de69df4c5f0c09b3bde6bdaf618e16900988e3" +uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" +version = "0.1.4" + +[[deps.ExprTools]] +git-tree-sha1 = "56559bbef6ca5ea0c0818fa5c90320398a6fbf8d" +uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" +version = "0.1.8" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.Future]] +deps = ["Random"] +uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" + +[[deps.HTTP]] +deps = ["Base64", "CodecZlib", "Dates", "IniFile", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] +git-tree-sha1 = "59ba44e0aa49b87a8c7a8920ec76f8afe87ed502" +uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" +version = "1.3.3" + +[[deps.IniFile]] +git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625" +uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f" +version = "0.5.1" + +[[deps.InlineStrings]] +deps = ["Parsers"] +git-tree-sha1 = "d19f9edd8c34760dca2de2b503f969d8700ed288" +uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" +version = "1.1.4" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[deps.JLLWrappers]] +deps = ["Preferences"] +git-tree-sha1 = "abc9885a7ca2052a736a600f7fa66209f96506e1" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.4.1" + +[[deps.JSON3]] +deps = ["Dates", "Mmap", "Parsers", "StructTypes", "UUIDs"] +git-tree-sha1 = "f1572de22c866dc92aea032bc89c2b137cbddd6a" +uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +version = "1.10.0" + +[[deps.LazyArtifacts]] +deps = ["Artifacts", "Pkg"] +uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.3" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "7.84.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.10.2+0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "5d4d2d9904227b8bd66386c1138cf4d5ffa826bf" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "0.4.9" + +[[deps.Lz4_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "5d494bc6e85c4c9b626ee0cab05daa4085486ab1" +uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" +version = "1.9.3+0" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS]] +deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "Random", "Sockets"] +git-tree-sha1 = "ae6676d5f576ccd21b6789c2cbe2ba24fcc8075d" +uuid = "739be429-bea8-5141-9913-cc70e7f3736d" +version = "1.1.5" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.0+0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.Mocking]] +deps = ["Compat", "ExprTools"] +git-tree-sha1 = "29714d0a7a8083bba8427a4fbfb00a540c681ce7" +uuid = "78c3b35d-d492-501b-9361-3d52fe80e533" +version = "0.7.3" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2022.2.1" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.Nullables]] +git-tree-sha1 = "8f87854cc8f3685a60689d8edecaa29d2251979b" +uuid = "4d1e1d77-625e-5b40-9113-a560ec7a8ecd" +version = "1.0.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.20+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.4.1" + +[[deps.Parsers]] +deps = ["Dates"] +git-tree-sha1 = "3d5bf43e3e8b412656404ed9466f1dcbf7c50269" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.4.0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.8.0" + +[[deps.PooledArrays]] +deps = ["DataAPI", "Future"] +git-tree-sha1 = "a6062fe4063cdafe78f4a0a81cfffb89721b30e7" +uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +version = "1.4.2" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "47e5f437cc0e7ef2ce8406ce1e7e24d44915f88d" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.3.0" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.ProtoBuf]] +deps = ["BufferedStreams", "Dates", "EnumX", "TOML", "TranscodingStreams"] +git-tree-sha1 = "32f6c63fb3b5685bf83512b597bae55a5628cc87" +uuid = "3349acd9-ac6a-5e09-bcdb-63829b23a429" +version = "1.0.6" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA", "Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.RecipesBase]] +git-tree-sha1 = "6bf3f380ff52ce0832ddd3a2a7b9538ed1bcca7d" +uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +version = "1.2.1" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Scratch]] +deps = ["Dates"] +git-tree-sha1 = "f94f779c94e58bf9ea243e77a37e16d9de9126bd" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.1.1" + +[[deps.Select]] +deps = ["Nullables"] +git-tree-sha1 = "eedc12abb6c7fecd172b698d15de543e7a833ed1" +repo-rev = "master" +repo-url = "https://github.com/NHDaly/Select.jl" +uuid = "ac7b3b08-bf78-4e82-aa6e-b038cb67d740" +version = "0.1.0" + +[[deps.SentinelArrays]] +deps = ["Dates", "Random"] +git-tree-sha1 = "db8481cf5d6278a121184809e9eb1628943c7704" +uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +version = "1.3.13" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[deps.SimpleBufferStream]] +git-tree-sha1 = "874e8867b33a00e784c8a7e4b60afe9e037b74e1" +uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" +version = "1.1.0" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SparseArrays]] +deps = ["LinearAlgebra", "Random"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StructTypes]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "ca4bccb03acf9faaf4137a9abc1881ed1841aa70" +uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" +version = "1.10.0" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.0" + +[[deps.TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[deps.Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"] +git-tree-sha1 = "5ce79ce186cc678bbb5c5681ca3379d1ddae11a1" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.7.0" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TextWrap]] +git-tree-sha1 = "9250ef9b01b66667380cf3275b3f7488d0e25faf" +uuid = "b718987f-49a8-5099-9789-dcd902bef87d" +version = "1.0.1" + +[[deps.TimeZones]] +deps = ["Dates", "Downloads", "InlineStrings", "LazyArtifacts", "Mocking", "Printf", "RecipesBase", "Scratch", "Unicode"] +git-tree-sha1 = "d634a3641062c040fc8a7e2a3ea17661cc159688" +uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53" +version = "1.9.0" + +[[deps.TranscodingStreams]] +deps = ["Random", "Test"] +git-tree-sha1 = "8a75929dcd3c38611db2f8d08546decb514fcadf" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.9.9" + +[[deps.URIs]] +git-tree-sha1 = "e59ecc5a41b000fa94423a578d29290c7266fc10" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.4.0" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.12+3" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "e45044cd873ded54b6a5bac0eb5c971392cf1927" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.2+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.1.1+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.48.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+0" diff --git a/Project.toml b/Project.toml index 716f99e..7a40869 100644 --- a/Project.toml +++ b/Project.toml @@ -9,11 +9,15 @@ ConfParser = "88353bc9-fd38-507d-a820-d3b43837d6b9" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" EnumX = "4e289a0a-7415-4d19-859d-a7e5c4648b56" ExceptionUnwrapping = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" +FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Mocking = "78c3b35d-d492-501b-9361-3d52fe80e533" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" ProtoBuf = "3349acd9-ac6a-5e09-bcdb-63829b23a429" +REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +Select = "ac7b3b08-bf78-4e82-aa6e-b038cb67d740" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" [compat] diff --git a/src/RAI.jl b/src/RAI.jl index 7abf9ee..1af29ac 100644 --- a/src/RAI.jl +++ b/src/RAI.jl @@ -104,5 +104,6 @@ include("metadata.jl") include("response.jl") include("api.jl") include("results.jl") +include("repl/REPLMode.jl") end diff --git a/src/repl/REPLMode.jl b/src/repl/REPLMode.jl new file mode 100644 index 0000000..ede5fa4 --- /dev/null +++ b/src/repl/REPLMode.jl @@ -0,0 +1,36 @@ +module REPLMode + +using Markdown +using ..RAI +# using RelationalAIProtocol: OPEN_OR_CREATE, CREATE_OVERWRITE +import REPL: REPL, LineEdit, REPLCompletions + +include("show.jl") +include("watch.jl") +include("install.jl") +include("command.jl") +include("repl.jl") + +islocal(ctx::Context) = ctx.host in ("127.0.0.1", "localhost") + +struct Connection + ctx::Context + db::String + engine::String +end + +function connect(db) + global conn = Connection(Context(load_config()), db, "") +end + +function __init__() + db = get(ENV, "RAI_REPL_DB", "repl") + connect(db) + if islocal(conn.ctx) + julia_repl_hook() + else + warn("REPL not initialised on remote connection.") + end +end + +end diff --git a/src/repl/command.jl b/src/repl/command.jl new file mode 100644 index 0000000..66663fc --- /dev/null +++ b/src/repl/command.jl @@ -0,0 +1,76 @@ +using Markdown + +const helpmd = md""" +Welcome to the Rel REPL. These are the commands you can use to manage your +session. + + %help + +List this help message. + + %db + +See all available databases. The currently attached database will be listed in +bold. + + %db foo + +Connect to the database `foo`, creating it if necessary. + + %install foo + +Install the source code at the path `foo`. `foo` can be a `.rel` file or a +directory containing `.rel` files. + + %install + +List all installed Rel code. + + %install - + +Clear all installed Rel code. + + %clear + +Clear installed Rel code and the EDB, resetting the database. +""" + +parsecommand(s) = match(r"%(\w+)\b\s*(.+)?", s) + +function printerr(e) + printstyled("error: ", color = :red, bold = true) + print(e) + println() +end + +function hidepwd(path) + replace(path, pwd() => ".") +end + +function command(m::RegexMatch) + type = m.captures[1] + arg = m.captures[2] + if type in ("help", "h") + println() + display(helpmd) + elseif type in ("install", "i") + if arg == nothing + list_sources() + elseif arg == "-" + reset_sources() + else + install(arg) + end + elseif type == "db" + if arg == nothing + # TODO: list of dbs + println(conn.db) + else + connect(arg) + end + elseif type == "clear" + clear_db() + else + display(md"Unrecognised command. Use `%help` to see all options.") + end +end diff --git a/src/repl/install.jl b/src/repl/install.jl new file mode 100644 index 0000000..6649992 --- /dev/null +++ b/src/repl/install.jl @@ -0,0 +1,87 @@ +function install_source(conn, name, src) + load_model(conn.ctx, conn.db, conn.engine, Dict(name => src)) +end + +function relfiles(dir, files = String[]) + for f in readdir(dir) + path = joinpath(dir, f) + if isdir(path) + relfiles(path, files) + elseif endswith(path, ".rel") && isfile(path) + push!(files, path) + end + end + return files +end + +function install_folder(path) + for file in relfiles(path) + install_source(conn, abspath(file), String(read(file))) + end +end + +const watchers = Dict{String,Union{FileWatcher,RecursiveWatcher}}() + +function is_watched_src(src) + for (path, _) in watchers + startswith(src, path) && return true + end + return false +end + +function updatefile(src) + if isfile(src) + install_source(conn, src, String(read(src))) + else + delete_source(conn, src) + end +end + +function install(src) + src = abspath(src) + haskey(watchers, src) && return + if isfile(src) + install_source(conn, src, String(read(src))) + watchers[src] = FileWatcher(src) do e + updatefile(src) + end + elseif isdir(src) + install_folder(src) + watchers[src] = RecursiveWatcher(src) do e + file, _ = e + file = abspath(joinpath(src, file)) + endswith(file, ".rel") || return + updatefile(file) + end + else + printerr("path doesn't exist: $(src)") + end + return +end + +function reset_sources() + for (src, _) in list_source(conn) + is_watched_src(src) && delete_source(conn, src) + end + foreach(stop!, values(watchers)) + empty!(watchers) + return +end + +function list_sources() + ss = list_source(conn) + for (name, src) in ss + is_watched_src(name) || println(hidepwd(name)) + end + for (src, _) in watchers + println(hidepwd(src)) + end +end + +function clear_db() + # If we replace the global connection, the DB gets reset on every subsequent + # query. So just create a temporary overwriting connection, and make a + # single dummy query to carry out the reset. + tmp = LocalConnection(dbname = conn.dbname, default_open_mode = CREATE_OVERWRITE) + query(tmp, "") +end diff --git a/src/repl/repl.jl b/src/repl/repl.jl new file mode 100644 index 0000000..12a16ea --- /dev/null +++ b/src/repl/repl.jl @@ -0,0 +1,104 @@ +function julia_repl_hook() + if isdefined(Base, :active_repl) + repl_init(Base.active_repl) + else + atreplinit() do repl + if isinteractive() && repl isa REPL.LineEditREPL + isdefined(repl, :interface) || (repl.interface = REPL.setup_interface(repl)) + repl_init(repl) + end + end + end +end + +function repl_init(repl) + main_mode = repl.interface.modes[1] + rel_mode = create_mode(repl, main_mode) + push!(repl.interface.modes, rel_mode) + keymap = Dict{Any,Any}( + '=' => function (s,args...) + if isempty(s) || position(LineEdit.buffer(s)) == 0 + buf = copy(LineEdit.buffer(s)) + LineEdit.transition(s, rel_mode) do + LineEdit.state(s, rel_mode).input_buffer = buf + end + else + LineEdit.edit_insert(s, '=') + end + end + ) + main_mode.keymap_dict = LineEdit.keymap_merge(main_mode.keymap_dict, keymap) + return +end + +function create_mode(repl, main) + rel_mode = LineEdit.Prompt("query> "; + prompt_prefix = repl.options.hascolor ? Base.text_colors[:magenta] : "", + prompt_suffix = "", + on_enter = return_callback, + sticky = true) + + rel_mode.repl = repl + hp = main.hist + hp.mode_mapping[:rel] = rel_mode + rel_mode.hist = hp + + search_prompt, skeymap = LineEdit.setup_search_keymap(hp) + prefix_prompt, prefix_keymap = LineEdit.setup_prefix_keymap(hp, rel_mode) + + rel_mode.on_done = (s, buf, ok) -> begin + ok || return REPL.transition(s, :abort) + input = String(take!(buf)) + REPL.reset(repl) + repl_eval(repl, input) + REPL.prepare_next(repl) + REPL.reset_state(s) + s.current_mode.sticky || REPL.transition(s, main) + end + + mk = REPL.mode_keymap(main) + + b = Dict{Any,Any}[ + skeymap, mk, prefix_keymap, LineEdit.history_keymap, + LineEdit.default_keymap, LineEdit.escape_defaults + ] + rel_mode.keymap_dict = LineEdit.keymap(b) + return rel_mode +end + +# This function determines whether we should evaluate or just add a new line +# when the user presses `enter`. +# Ideally, we'd invoke the parser to see if it needs more input. +# For now we have a fairly dumb bracket-based heuristic. +# (which could of course break in the presence of quotes) +function return_callback(s) + input = String(take!(copy(LineEdit.buffer(s)))) + if count(x -> x in ('(', '['), input) > count(x -> x in (')', ']'), input) + return false + end + return true +end + +function evalrel(input; readonly = false) + try + result = exec(conn.ctx, conn.db, conn.engine, input; readonly) + # Disabled until we decide the representation + # Base.eval(Main, :(ans = $result)) + replshow(stdout, result) + catch + Base.display_error(stdout, Base.catch_stack()) + end +end + +function repl_eval(repl, input) + c = parsecommand(input) + if c != nothing + try # TODO: shouldn't error + command(c) + catch + Base.display_error(stdout, Base.catch_stack()) + end + return + end + evalrel(input) +end diff --git a/src/repl/show.jl b/src/repl/show.jl new file mode 100644 index 0000000..079284f --- /dev/null +++ b/src/repl/show.jl @@ -0,0 +1,40 @@ +function collectresult(result) + keys = result.metadata.relations + keys = [sprint.(RAI.show_rel_type, key.relation_id.arguments) for key in keys] + vals = [isempty(v) ? [()] : collect(zip(v...)) for (k, v) in result.results] + return Dict(zip(keys, vals)) +end + +replshow(io, result::RAI.TransactionResponse) = replshow(io, collectresult(result)) + +function replshow(io, result) + first = true + isempty(result) && println("{}") # false / empty relation + for (key, values) in result + key[1] == ":output" || continue + first || println(io) + first = false + showkey(io, key[2:end]) + showrelation(io, values) + end +end + +function showkey(io, ks) + isempty(ks) && (ks = ["Unit"]) # single empty tuple + for k in ks + printstyled(io, string("/", k), bold = true) + end + println(io) +end + +function showrelation(io, rel) + for row in rel + print(io, " ") + isempty(row) && print(io, "()") + for i = 1:length(row) + i != 1 && printstyled(io, ", ", color=:light_blue) + show(io, row[i]) + end + println(io) + end +end diff --git a/src/repl/watch.jl b/src/repl/watch.jl new file mode 100644 index 0000000..243c232 --- /dev/null +++ b/src/repl/watch.jl @@ -0,0 +1,91 @@ +# Generic file watching utlities + +using Select, FileWatching + +mutable struct CallbackLoop + task::Task + done::Channel{Nothing} + isdone::Bool +end + +function CallbackLoop(func, callback) + done = Channel{Nothing}() + task = @async begin + while true + @select begin + (@async func()) |> e => callback(e) + done => break + end + end + end + return CallbackLoop(task, done, false) +end + +function stop!(cb::CallbackLoop) + cb.isdone && return + put!(cb.done, nothing) + wait(cb.task) + cb.isdone = true + return +end + +struct FileWatcher + cb::CallbackLoop +end + +FileWatcher(callback, path::AbstractString) = + FileWatcher(CallbackLoop(() -> watch_file(path), callback)) + +stop!(fw::FileWatcher) = stop!(fw.cb) + +struct DirWatcher + dir::AbstractString + cb::CallbackLoop +end + +DirWatcher(callback, path::AbstractString) = + DirWatcher(path, CallbackLoop(() -> watch_folder(path), callback)) + +function stop!(dw::DirWatcher) + stop!(dw.cb) + unwatch_folder(dw.dir) + return +end + +struct RecursiveWatcher + root::AbstractString + watcher::DirWatcher + children::Dict{String,RecursiveWatcher} +end + +function RecursiveWatcher(callback, path::AbstractString, root::AbstractString = "") + children = Dict{String,RecursiveWatcher}() + for f in readdir(path) + child = joinpath(path, f) + if isdir(child) + children[f] = RecursiveWatcher(callback, child, joinpath(root, f)) + end + end + watcher = DirWatcher(path) do e + f, event = e + watched = haskey(children, f) + dir = isdir(joinpath(path, f)) + if watched || dir + if watched && !dir + stop!(children[f]) + delete!(children, f) + elseif dir && !watched + children[f] = RecursiveWatcher(callback, joinpath(path, f), joinpath(root, f)) + end + else + callback(joinpath(root, f) => event) + end + end + return RecursiveWatcher(root, watcher, children) +end + +function stop!(rw::RecursiveWatcher) + stop!(rw.watcher) + foreach(stop!, values(rw.children)) + return +end