Week 03 of 2024
Development log of Tad Notes
22 items
- Implement a POC notes importer from IMAP
- Write some comments
- Setup proper logging
- Format the code
- Implement the IMAPConfig.from_env function
- Correct a mistake in error message
- Cram everything into the main function
- Make imap_disconnect function generic over Session
- Control root certificate loading with env variable
- Extract imap_connect function
- Program will get all data of all messages
- Will extract a subject, date and text content
- Replace "extern crate" with "use" statements
- Use parsemail crate to decode the content
- Introduce a Note struct with parsed timestamp
- Use Pandoc to convert HTML to markdown
- Fix nix build unable to find openssl
- Usa a template to output a denote formatted note
- Use yaml filters to format values in the template
- Write imported notes to files
- Prevent overwriting or duplicating existing notes
- Create a readme file
Implement a POC notes importer from IMAP
On by
For now it just prints the body of a first email in a given mailbox.
It works with Proton Mail Bridge.
Credentials must be provided in environment variables. One way is to set them using .envrc.private file that will be loaded by direnv. Their values can be obtained like this:
$ protonmail-bridge --cli
>>> info
Root certificate for the server can be obtained like this
>>> cert export
It must be placed in private/cert.pem (probably not a very descriptive path, but it's a POC).
index e426355..2a0972e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
=.direnv/
+.envrc.private
=/target/
=/result
=/public/index e047bd7..5e5a7a5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,687 @@
=# It is not intended for manual editing.
=version = 3
=
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "bufstream"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
=[[package]]
=name = "denote-tools"
=version = "0.1.0"
+dependencies = [
+ "imap",
+ "native-tls",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "imap"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c617c55def8c42129e0dd503f11d7ee39d73f5c7e01eff55768b3879ff1d107d"
+dependencies = [
+ "base64",
+ "bufstream",
+ "chrono",
+ "imap-proto",
+ "lazy_static",
+ "native-tls",
+ "nom",
+ "regex",
+]
+
+[[package]]
+name = "imap-proto"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16a6def1d5ac8975d70b3fd101d57953fe3278ef2ee5d7816cba54b1d1dfc22f"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lexical-core"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
+dependencies = [
+ "arrayvec",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "native-tls"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "nom"
+version = "5.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b"
+dependencies = [
+ "lexical-core",
+ "memchr",
+ "version_check",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "openssl"
+version = "0.10.62"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671"
+dependencies = [
+ "bitflags 2.4.1",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "rustix"
+version = "0.38.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "schannel"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"index a4f51ad..c842a80 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,3 +6,5 @@ edition = "2021"
=# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
=
=[dependencies]
+imap = "2.4.1"
+native-tls = "0.2.11"index 04a5076..cac952a 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ install: build
=
=develop: ## Rebuild and run a development version of the program
=develop:
- cargo run --features bevy/dynamic
+ cargo run
=.PHONY: develop
=
=clean: ## Remove all build artifactsindex aac49e1..51fe718 100644
--- a/flake.nix
+++ b/flake.nix
@@ -20,6 +20,7 @@
= nativeBuildInputs = with pkgs; [
= (rust-bin.fromRustupToolchainFile ./rust-toolchain.toml)
= gcc
+ openssl
= pkg-config
= gnumake
= findutilsnew file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/private/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignoreindex e7a11a9..d4f7009 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,101 @@
+extern crate imap;
+extern crate native_tls;
+
+use std::fs;
+use std::env;
+
=fn main() {
- println!("Hello, world!");
+ // TODO: Make a struct and helpers for all of this.
+ let config = IMAPConfig {
+ host: env::var("imap_host")
+ .expect("There should be an environment variable named 'imap_host'"),
+ port: env::var("imap_port")
+ .expect("There should be an environment variable named 'imap_port'")
+ .parse::<u16>()
+ .expect("The 'imap_port' environment variable must be a positive number"),
+ username: env::var("imap_username")
+ .expect("There should be an environment variable named 'imap_username'"),
+ password: env::var("imap_password")
+ .expect("There should be an environment variable named 'imap_password'"),
+ mailbox: env::var("imap_mailbox")
+ .expect("There should be an environment variable named 'imap_password'"),
+ };
+
+ let subject: String = get_latest_subject(&config).unwrap().unwrap();
+ println!("{}", subject);
+}
+
+pub struct IMAPConfig {
+ host: String,
+ port: u16,
+ username: String,
+ password: String,
+ mailbox: String,
+}
+
+fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>> {
+ // Setup TLS with a certificate from Proton Bridge
+ // TODO: Automate certificate export from Proton Mail Bridge, or at least document it.
+ let pem = &fs::read("private/cert.pem")?;
+ let cert = native_tls::Certificate::from_pem(pem)?;
+ let tls = native_tls::TlsConnector::builder()
+ .add_root_certificate(cert)
+ .build()?;
+
+ println!("Certificate loaded.");
+
+ let client = imap::connect_starttls((config.host.clone(), config.port), &config.host, &tls)?;
+ println!("Client connected");
+
+ let mut session = client.login(&config.username, &config.password).map_err(|e| e.0)?;
+ println!("Client authenticated");
+
+ let mailboxes = session
+ .list(Some(""), Some("*"))
+ .expect("Failed to list mailboxes");
+ println!("There are {} mailboxes", mailboxes.len());
+ for name in &mailboxes {
+ println!("- {}", name.name());
+ }
+
+ session.select(&config.mailbox)?;
+ println!("Inbox selected: {}", &config.mailbox);
+
+ let messages = session.fetch("1", "RFC822")?;
+ let message = if let Some(first) = messages.iter().next() {
+ first
+ } else {
+ return Ok(None);
+ };
+ println!("Messages fetched");
+
+ let body = message
+ .body()
+ .expect("Failed to extract a body from the message");
+ let body = std::str::from_utf8(body)
+ .expect("Failed to extract a body")
+ .to_string();
+
+ // println!("The body is: {}", body);
+
+ session.debug = true;
+
+ // TODO: Extract into a helper
+ match session.logout() {
+ Ok(_) => {}
+ Err(err) => match &err {
+ imap::Error::Parse(imap::error::ParseError::Invalid(bytes)) => {
+ if bytes.clone() == "* BYE\r\n".as_bytes() {
+ println!("The server said BYE and the client freaked out, but it's fine");
+ } else {
+ return Err(err);
+ }
+ }
+ _ => return Err(err),
+ },
+ }
+
+ println!("Logged out.");
+
+ Ok(Some(body))
=}Write some comments
On by
index d4f7009..58a588b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -36,6 +36,7 @@ pub struct IMAPConfig {
=fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>> {
= // Setup TLS with a certificate from Proton Bridge
= // TODO: Automate certificate export from Proton Mail Bridge, or at least document it.
+ // SEE: https://github.com/ProtonMail/proton-bridge/issues/315
= let pem = &fs::read("private/cert.pem")?;
= let cert = native_tls::Certificate::from_pem(pem)?;
= let tls = native_tls::TlsConnector::builder()
@@ -81,6 +82,7 @@ fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>
= session.debug = true;
=
= // TODO: Extract into a helper
+ // TODO: Report. See https://github.com/jonhoo/rust-imap/issues/210
= match session.logout() {
= Ok(_) => {}
= Err(err) => match &err {Setup proper logging
On by
index 5e5a7a5..dd774ce 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -117,10 +117,25 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
=name = "denote-tools"
=version = "0.1.0"
=dependencies = [
+ "env_logger",
= "imap",
+ "log",
= "native-tls",
=]
=
+[[package]]
+name = "env_logger"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
=[[package]]
=name = "errno"
=version = "0.3.8"
@@ -152,6 +167,18 @@ version = "0.1.1"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
=
+[[package]]
+name = "hermit-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
=[[package]]
=name = "iana-time-zone"
=version = "0.1.59"
@@ -200,6 +227,17 @@ dependencies = [
= "nom",
=]
=
+[[package]]
+name = "is-terminal"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
+dependencies = [
+ "hermit-abi",
+ "rustix",
+ "windows-sys",
+]
+
=[[package]]
=name = "js-sys"
=version = "0.3.67"
@@ -483,6 +521,15 @@ dependencies = [
= "windows-sys",
=]
=
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
=[[package]]
=name = "unicode-ident"
=version = "1.0.12"
@@ -555,6 +602,37 @@ version = "0.2.90"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
=
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
=[[package]]
=name = "windows-core"
=version = "0.52.0"index c842a80..dab776d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,3 +8,5 @@ edition = "2021"
=[dependencies]
=imap = "2.4.1"
=native-tls = "0.2.11"
+env_logger = "0.10.1"
+log = "0.4.20"index cac952a..5aee1ac 100644
--- a/Makefile
+++ b/Makefile
@@ -28,8 +28,9 @@ install: build
=### DEVELOPMENT
=
=develop: ## Rebuild and run a development version of the program
+develop: log-level ?= info
=develop:
- cargo run
+ RUST_LOG=$(log-level) cargo run
=.PHONY: develop
=
=clean: ## Remove all build artifactsindex 58a588b..10e9648 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,8 +3,15 @@ extern crate native_tls;
=
=use std::fs;
=use std::env;
+use env_logger;
+use log::{debug, error, info};
=
=fn main() {
+ env_logger::init();
+
+
+ info!("Starting import");
+
= // TODO: Make a struct and helpers for all of this.
= let config = IMAPConfig {
= host: env::var("imap_host")
@@ -43,24 +50,24 @@ fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>
= .add_root_certificate(cert)
= .build()?;
=
- println!("Certificate loaded.");
+ debug!("Certificate loaded.");
=
= let client = imap::connect_starttls((config.host.clone(), config.port), &config.host, &tls)?;
- println!("Client connected");
+ debug!("Client connected");
=
= let mut session = client.login(&config.username, &config.password).map_err(|e| e.0)?;
- println!("Client authenticated");
+ debug!("Client authenticated");
=
= let mailboxes = session
= .list(Some(""), Some("*"))
= .expect("Failed to list mailboxes");
- println!("There are {} mailboxes", mailboxes.len());
+ debug!("There are {} mailboxes", mailboxes.len());
= for name in &mailboxes {
- println!("- {}", name.name());
+ debug!("- {}", name.name());
= }
=
= session.select(&config.mailbox)?;
- println!("Inbox selected: {}", &config.mailbox);
+ debug!("Inbox selected: {}", &config.mailbox);
=
= let messages = session.fetch("1", "RFC822")?;
= let message = if let Some(first) = messages.iter().next() {
@@ -68,7 +75,7 @@ fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>
= } else {
= return Ok(None);
= };
- println!("Messages fetched");
+ debug!("{} messages fetched", messages.len());
=
= let body = message
= .body()
@@ -79,7 +86,10 @@ fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>
=
= // println!("The body is: {}", body);
=
- session.debug = true;
+ if log::log_enabled!(log::Level::Debug) {
+ debug!("Debug logging for IMAP session enabled");
+ session.debug = true;
+ }
=
= // TODO: Extract into a helper
= // TODO: Report. See https://github.com/jonhoo/rust-imap/issues/210
@@ -88,7 +98,7 @@ fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>
= Err(err) => match &err {
= imap::Error::Parse(imap::error::ParseError::Invalid(bytes)) => {
= if bytes.clone() == "* BYE\r\n".as_bytes() {
- println!("The server said BYE and the client freaked out, but it's fine");
+ debug!("The server said BYE and the client freaked out, but it's fine");
= } else {
= return Err(err);
= }
@@ -97,7 +107,7 @@ fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>
= },
= }
=
- println!("Logged out.");
+ debug!("Logged out.");
=
= Ok(Some(body))
=}Format the code
On by
index 10e9648..ac629e4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,15 +1,14 @@
=extern crate imap;
=extern crate native_tls;
=
-use std::fs;
-use std::env;
=use env_logger;
=use log::{debug, error, info};
+use std::env;
+use std::fs;
=
=fn main() {
= env_logger::init();
=
-
= info!("Starting import");
=
= // TODO: Make a struct and helpers for all of this.
@@ -55,7 +54,9 @@ fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>
= let client = imap::connect_starttls((config.host.clone(), config.port), &config.host, &tls)?;
= debug!("Client connected");
=
- let mut session = client.login(&config.username, &config.password).map_err(|e| e.0)?;
+ let mut session = client
+ .login(&config.username, &config.password)
+ .map_err(|e| e.0)?;
= debug!("Client authenticated");
=
= let mailboxes = sessionImplement the IMAPConfig.from_env function
On by
index ac629e4..a5001ec 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,7 @@ extern crate imap;
=extern crate native_tls;
=
=use env_logger;
-use log::{debug, error, info};
+use log::{debug, info};
=use std::env;
=use std::fs;
=
@@ -12,20 +12,7 @@ fn main() {
= info!("Starting import");
=
= // TODO: Make a struct and helpers for all of this.
- let config = IMAPConfig {
- host: env::var("imap_host")
- .expect("There should be an environment variable named 'imap_host'"),
- port: env::var("imap_port")
- .expect("There should be an environment variable named 'imap_port'")
- .parse::<u16>()
- .expect("The 'imap_port' environment variable must be a positive number"),
- username: env::var("imap_username")
- .expect("There should be an environment variable named 'imap_username'"),
- password: env::var("imap_password")
- .expect("There should be an environment variable named 'imap_password'"),
- mailbox: env::var("imap_mailbox")
- .expect("There should be an environment variable named 'imap_password'"),
- };
+ let config = IMAPConfig::from_env();
=
= let subject: String = get_latest_subject(&config).unwrap().unwrap();
= println!("{}", subject);
@@ -39,6 +26,25 @@ pub struct IMAPConfig {
= mailbox: String,
=}
=
+impl IMAPConfig {
+ fn from_env() -> Self {
+ Self {
+ host: env::var("imap_host")
+ .expect("There should be an environment variable named 'imap_host'"),
+ port: env::var("imap_port")
+ .expect("There should be an environment variable named 'imap_port'")
+ .parse::<u16>()
+ .expect("The 'imap_port' environment variable must be a positive number"),
+ username: env::var("imap_username")
+ .expect("There should be an environment variable named 'imap_username'"),
+ password: env::var("imap_password")
+ .expect("There should be an environment variable named 'imap_password'"),
+ mailbox: env::var("imap_mailbox")
+ .expect("There should be an environment variable named 'imap_password'"),
+ }
+ }
+}
+
=fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>> {
= // Setup TLS with a certificate from Proton Bridge
= // TODO: Automate certificate export from Proton Mail Bridge, or at least document it.Correct a mistake in error message
On by
index a5001ec..c98de90 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -40,7 +40,7 @@ impl IMAPConfig {
= password: env::var("imap_password")
= .expect("There should be an environment variable named 'imap_password'"),
= mailbox: env::var("imap_mailbox")
- .expect("There should be an environment variable named 'imap_password'"),
+ .expect("There should be an environment variable named 'imap_mailbox'"),
= }
= }
=}Cram everything into the main function
On by
It's a first step to refactor and divide responsibility better than before.
Also separate the logout handling of the BYE response to own funtion.
index c98de90..335e0e5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -14,55 +14,23 @@ fn main() {
= // TODO: Make a struct and helpers for all of this.
= let config = IMAPConfig::from_env();
=
- let subject: String = get_latest_subject(&config).unwrap().unwrap();
- println!("{}", subject);
-}
-
-pub struct IMAPConfig {
- host: String,
- port: u16,
- username: String,
- password: String,
- mailbox: String,
-}
-
-impl IMAPConfig {
- fn from_env() -> Self {
- Self {
- host: env::var("imap_host")
- .expect("There should be an environment variable named 'imap_host'"),
- port: env::var("imap_port")
- .expect("There should be an environment variable named 'imap_port'")
- .parse::<u16>()
- .expect("The 'imap_port' environment variable must be a positive number"),
- username: env::var("imap_username")
- .expect("There should be an environment variable named 'imap_username'"),
- password: env::var("imap_password")
- .expect("There should be an environment variable named 'imap_password'"),
- mailbox: env::var("imap_mailbox")
- .expect("There should be an environment variable named 'imap_mailbox'"),
- }
- }
-}
-
-fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>> {
= // Setup TLS with a certificate from Proton Bridge
= // TODO: Automate certificate export from Proton Mail Bridge, or at least document it.
= // SEE: https://github.com/ProtonMail/proton-bridge/issues/315
- let pem = &fs::read("private/cert.pem")?;
- let cert = native_tls::Certificate::from_pem(pem)?;
+ let pem = &fs::read("private/cert.pem").unwrap();
+ let cert = native_tls::Certificate::from_pem(pem).unwrap();
= let tls = native_tls::TlsConnector::builder()
= .add_root_certificate(cert)
- .build()?;
+ .build()
+ .unwrap();
=
= debug!("Certificate loaded.");
=
- let client = imap::connect_starttls((config.host.clone(), config.port), &config.host, &tls)?;
+ let client =
+ imap::connect_starttls((config.host.clone(), config.port), &config.host, &tls).unwrap();
= debug!("Client connected");
=
- let mut session = client
- .login(&config.username, &config.password)
- .map_err(|e| e.0)?;
+ let mut session = client.login(&config.username, &config.password).unwrap();
= debug!("Client authenticated");
=
= let mailboxes = session
@@ -73,48 +41,78 @@ fn get_latest_subject(config: &IMAPConfig) -> imap::error::Result<Option<String>
= debug!("- {}", name.name());
= }
=
- session.select(&config.mailbox)?;
+ session.select(&config.mailbox).unwrap();
= debug!("Inbox selected: {}", &config.mailbox);
=
- let messages = session.fetch("1", "RFC822")?;
- let message = if let Some(first) = messages.iter().next() {
- first
- } else {
- return Ok(None);
- };
+ let messages = session.fetch("1", "RFC822").unwrap();
= debug!("{} messages fetched", messages.len());
=
- let body = message
- .body()
- .expect("Failed to extract a body from the message");
- let body = std::str::from_utf8(body)
- .expect("Failed to extract a body")
- .to_string();
+ for message in &messages {
+ let body = message
+ .body()
+ .expect("Failed to extract a body from the message");
+ let body = std::str::from_utf8(body)
+ .expect("Failed to extract a body")
+ .to_string();
=
- // println!("The body is: {}", body);
+ println!("{}", body);
+ }
=
= if log::log_enabled!(log::Level::Debug) {
= debug!("Debug logging for IMAP session enabled");
= session.debug = true;
= }
=
- // TODO: Extract into a helper
+ imap_disconnect(session).unwrap();
+
+ info!("Done.");
+}
+
+// TODO: Make the session argument generic
+fn imap_disconnect(
+ mut session: imap::Session<native_tls::TlsStream<std::net::TcpStream>>,
+) -> Result<(), imap::Error> {
= // TODO: Report. See https://github.com/jonhoo/rust-imap/issues/210
- match session.logout() {
- Ok(_) => {}
+ let result = session.logout();
+ match &result {
+ Ok(_) => result,
= Err(err) => match &err {
= imap::Error::Parse(imap::error::ParseError::Invalid(bytes)) => {
= if bytes.clone() == "* BYE\r\n".as_bytes() {
= debug!("The server said BYE and the client freaked out, but it's fine");
+ Ok(())
= } else {
- return Err(err);
+ result
= }
= }
- _ => return Err(err),
+ _ => result,
= },
= }
+}
=
- debug!("Logged out.");
+pub struct IMAPConfig {
+ host: String,
+ port: u16,
+ username: String,
+ password: String,
+ mailbox: String,
+}
=
- Ok(Some(body))
+impl IMAPConfig {
+ fn from_env() -> Self {
+ Self {
+ host: env::var("imap_host")
+ .expect("There should be an environment variable named 'imap_host'"),
+ port: env::var("imap_port")
+ .expect("There should be an environment variable named 'imap_port'")
+ .parse::<u16>()
+ .expect("The 'imap_port' environment variable must be a positive number"),
+ username: env::var("imap_username")
+ .expect("There should be an environment variable named 'imap_username'"),
+ password: env::var("imap_password")
+ .expect("There should be an environment variable named 'imap_password'"),
+ mailbox: env::var("imap_mailbox")
+ .expect("There should be an environment variable named 'imap_mailbox'"),
+ }
+ }
=}Make imap_disconnect function generic over Session
On by
index 335e0e5..bd0023e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -68,10 +68,10 @@ fn main() {
= info!("Done.");
=}
=
-// TODO: Make the session argument generic
-fn imap_disconnect(
- mut session: imap::Session<native_tls::TlsStream<std::net::TcpStream>>,
-) -> Result<(), imap::Error> {
+fn imap_disconnect<T>(mut session: imap::Session<T>) -> Result<(), imap::Error>
+where
+ T: std::io::Write + std::io::Read,
+{
= // TODO: Report. See https://github.com/jonhoo/rust-imap/issues/210
= let result = session.logout();
= match &result {Control root certificate loading with env variable
On by
index bd0023e..6d6a543 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,20 +11,23 @@ fn main() {
=
= info!("Starting import");
=
- // TODO: Make a struct and helpers for all of this.
= let config = IMAPConfig::from_env();
=
+ // NOTE: Avoid leaking secrets in logs
+ #[cfg(debug_assertions)]
+ debug!("Configuration loaded {config:?}");
+
= // Setup TLS with a certificate from Proton Bridge
= // TODO: Automate certificate export from Proton Mail Bridge, or at least document it.
= // SEE: https://github.com/ProtonMail/proton-bridge/issues/315
- let pem = &fs::read("private/cert.pem").unwrap();
- let cert = native_tls::Certificate::from_pem(pem).unwrap();
- let tls = native_tls::TlsConnector::builder()
- .add_root_certificate(cert)
- .build()
- .unwrap();
-
- debug!("Certificate loaded.");
+ let mut tls = native_tls::TlsConnector::builder();
+ if let Some(cert_path) = config.cart_path {
+ debug!("Loading root certificate from {}", &cert_path);
+ let pem = &fs::read(cert_path).unwrap();
+ let cert = native_tls::Certificate::from_pem(pem).unwrap();
+ tls.add_root_certificate(cert);
+ }
+ let tls = tls.build().unwrap();
=
= let client =
= imap::connect_starttls((config.host.clone(), config.port), &config.host, &tls).unwrap();
@@ -90,12 +93,14 @@ where
= }
=}
=
+#[derive(Debug)]
=pub struct IMAPConfig {
= host: String,
= port: u16,
= username: String,
= password: String,
= mailbox: String,
+ cart_path: Option<String>,
=}
=
=impl IMAPConfig {
@@ -113,6 +118,16 @@ impl IMAPConfig {
= .expect("There should be an environment variable named 'imap_password'"),
= mailbox: env::var("imap_mailbox")
= .expect("There should be an environment variable named 'imap_mailbox'"),
+ cart_path: env::var("imap_cert_path")
+ .map(Option::Some)
+ .or_else(|error| {
+ if error == env::VarError::NotPresent {
+ Ok(None)
+ } else {
+ Err(error)
+ }
+ })
+ .expect("The value of 'imap_cert_path' environment variable is invalid"),
= }
= }
=}Extract imap_connect function
On by
index 6d6a543..e8de37a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,8 +3,10 @@ extern crate native_tls;
=
=use env_logger;
=use log::{debug, info};
+use native_tls::TlsStream;
=use std::env;
=use std::fs;
+use std::net::TcpStream;
=
=fn main() {
= env_logger::init();
@@ -17,24 +19,8 @@ fn main() {
= #[cfg(debug_assertions)]
= debug!("Configuration loaded {config:?}");
=
- // Setup TLS with a certificate from Proton Bridge
- // TODO: Automate certificate export from Proton Mail Bridge, or at least document it.
- // SEE: https://github.com/ProtonMail/proton-bridge/issues/315
- let mut tls = native_tls::TlsConnector::builder();
- if let Some(cert_path) = config.cart_path {
- debug!("Loading root certificate from {}", &cert_path);
- let pem = &fs::read(cert_path).unwrap();
- let cert = native_tls::Certificate::from_pem(pem).unwrap();
- tls.add_root_certificate(cert);
- }
- let tls = tls.build().unwrap();
-
- let client =
- imap::connect_starttls((config.host.clone(), config.port), &config.host, &tls).unwrap();
- debug!("Client connected");
-
- let mut session = client.login(&config.username, &config.password).unwrap();
- debug!("Client authenticated");
+ let mut session = imap_connect(&config);
+ debug!("Client connected and authenticated");
=
= let mailboxes = session
= .list(Some(""), Some("*"))
@@ -71,6 +57,27 @@ fn main() {
= info!("Done.");
=}
=
+// TODO: Return a result. How to combine different types of errors (io, tls, imap)?
+fn imap_connect(config: &IMAPConfig) -> imap::Session<TlsStream<TcpStream>> {
+ // Setup TLS with a certificate from Proton Bridge
+ // TODO: Automate certificate export from Proton Mail Bridge, or at least document it.
+ // SEE: https://github.com/ProtonMail/proton-bridge/issues/315
+ let mut tls = native_tls::TlsConnector::builder();
+ if let Some(cert_path) = &config.cart_path {
+ debug!("Loading root certificate from {}", &cert_path);
+ let pem = &fs::read(cert_path).unwrap();
+ let cert = native_tls::Certificate::from_pem(pem).unwrap();
+ tls.add_root_certificate(cert);
+ }
+ let tls = tls.build().unwrap();
+
+ let client =
+ imap::connect_starttls((config.host.clone(), config.port), &config.host, &tls).unwrap();
+ debug!("Client connected");
+
+ client.login(&config.username, &config.password).unwrap()
+}
+
=fn imap_disconnect<T>(mut session: imap::Session<T>) -> Result<(), imap::Error>
=where
= T: std::io::Write + std::io::Read,Program will get all data of all messages
On by
Apparently the syntax to get all messages is 1:* - not 1: as in examples.
index e8de37a..bc62087 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -33,7 +33,7 @@ fn main() {
= session.select(&config.mailbox).unwrap();
= debug!("Inbox selected: {}", &config.mailbox);
=
- let messages = session.fetch("1", "RFC822").unwrap();
+ let messages = session.fetch("1:*", "ALL").unwrap();
= debug!("{} messages fetched", messages.len());
=
= for message in &messages {Will extract a subject, date and text content
On by
The key is to query for
(BODY[TEXT] ENVELOPE)
and wrap the query in parentheses.
Now the problem is that HTML text of emails I'm seeing are encoded using something called Quoted-Printable system.
index bc62087..8ed8808 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -33,18 +33,27 @@ fn main() {
= session.select(&config.mailbox).unwrap();
= debug!("Inbox selected: {}", &config.mailbox);
=
- let messages = session.fetch("1:*", "ALL").unwrap();
+ let messages = session.fetch("1:*", "(BODY[TEXT] ENVELOPE)").unwrap();
= debug!("{} messages fetched", messages.len());
=
= for message in &messages {
- let body = message
- .body()
- .expect("Failed to extract a body from the message");
- let body = std::str::from_utf8(body)
- .expect("Failed to extract a body")
- .to_string();
-
- println!("{}", body);
+ let envelope = message.envelope().unwrap();
+ let subject = envelope
+ .subject
+ .map(|bytes| std::str::from_utf8(bytes).unwrap())
+ .unwrap_or("[No subject]");
+
+ let date = envelope
+ .date
+ .map(|bytes| std::str::from_utf8(bytes).unwrap())
+ .unwrap_or("[No date]");
+
+ let text = message
+ .text()
+ .map(|bytes| std::str::from_utf8(bytes).unwrap())
+ .unwrap_or("[No text]");
+
+ println!("\n<{date}> {subject}\n\n{text}");
= }
=
= if log::log_enabled!(log::Level::Debug) {Replace "extern crate" with "use" statements
On by
Relevant discussion here:
https://users.rust-lang.org/t/why-do-so-many-examples-use-extern-crate/48459/
index 8ed8808..519ce49 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,7 @@
-extern crate imap;
-extern crate native_tls;
-
=use env_logger;
+use imap;
=use log::{debug, info};
+use native_tls;
=use native_tls::TlsStream;
=use std::env;
=use std::fs;Use parsemail crate to decode the content
On by
This solves the transfer encoding (e.g. quoted-printable) problem mentioned in previous commit.
index dd774ce..e726c69 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -83,6 +83,16 @@ version = "1.0.0"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
=
+[[package]]
+name = "charset"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18e9079d1a12a2cc2bffb5db039c43661836ead4082120d5844f02555aca2d46"
+dependencies = [
+ "base64",
+ "encoding_rs",
+]
+
=[[package]]
=name = "chrono"
=version = "0.4.31"
@@ -113,6 +123,12 @@ version = "0.8.6"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
=
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
=[[package]]
=name = "denote-tools"
=version = "0.1.0"
@@ -120,9 +136,19 @@ dependencies = [
= "env_logger",
= "imap",
= "log",
+ "mailparse",
= "native-tls",
=]
=
+[[package]]
+name = "encoding_rs"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
+dependencies = [
+ "cfg-if",
+]
+
=[[package]]
=name = "env_logger"
=version = "0.10.1"
@@ -284,6 +310,17 @@ version = "0.4.20"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
=
+[[package]]
+name = "mailparse"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d096594926cab442e054e047eb8c1402f7d5b2272573b97ba68aa40629f9757"
+dependencies = [
+ "charset",
+ "data-encoding",
+ "quoted_printable",
+]
+
=[[package]]
=name = "memchr"
=version = "2.7.1"
@@ -402,6 +439,12 @@ dependencies = [
= "proc-macro2",
=]
=
+[[package]]
+name = "quoted_printable"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79ec282e887b434b68c18fe5c121d38e72a5cf35119b59e54ec5b992ea9c8eb0"
+
=[[package]]
=name = "redox_syscall"
=version = "0.4.1"index dab776d..9e3b01d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,3 +10,4 @@ imap = "2.4.1"
=native-tls = "0.2.11"
=env_logger = "0.10.1"
=log = "0.4.20"
+mailparse = "0.14.1"index 519ce49..c8f7099 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,8 @@
=use env_logger;
=use imap;
=use log::{debug, info};
+use mailparse::parse_mail;
+use mailparse::MailHeaderMap;
=use native_tls;
=use native_tls::TlsStream;
=use std::env;
@@ -32,25 +34,16 @@ fn main() {
= session.select(&config.mailbox).unwrap();
= debug!("Inbox selected: {}", &config.mailbox);
=
- let messages = session.fetch("1:*", "(BODY[TEXT] ENVELOPE)").unwrap();
+ let messages = session.fetch("1:*", "RFC822").unwrap();
= debug!("{} messages fetched", messages.len());
=
= for message in &messages {
- let envelope = message.envelope().unwrap();
- let subject = envelope
- .subject
- .map(|bytes| std::str::from_utf8(bytes).unwrap())
- .unwrap_or("[No subject]");
-
- let date = envelope
- .date
- .map(|bytes| std::str::from_utf8(bytes).unwrap())
- .unwrap_or("[No date]");
-
- let text = message
- .text()
- .map(|bytes| std::str::from_utf8(bytes).unwrap())
- .unwrap_or("[No text]");
+ let body = message.body().unwrap();
+ let parsed = parse_mail(body).unwrap();
+
+ let subject = parsed.headers.get_first_value("Subject").unwrap();
+ let date = parsed.headers.get_first_value("Date").unwrap();
+ let text = parsed.get_body().unwrap();
=
= println!("\n<{date}> {subject}\n\n{text}");
= }Introduce a Note struct with parsed timestamp
On by
index e726c69..9c60094 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -133,6 +133,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
=name = "denote-tools"
=version = "0.1.0"
=dependencies = [
+ "chrono",
= "env_logger",
= "imap",
= "log",index 9e3b01d..de3dae8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,3 +11,4 @@ native-tls = "0.2.11"
=env_logger = "0.10.1"
=log = "0.4.20"
=mailparse = "0.14.1"
+chrono = "0.4.31"index c8f7099..69e211d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,5 @@
+use chrono::DateTime;
+use chrono::Utc;
=use env_logger;
=use imap;
=use log::{debug, info};
@@ -38,14 +40,8 @@ fn main() {
= debug!("{} messages fetched", messages.len());
=
= for message in &messages {
- let body = message.body().unwrap();
- let parsed = parse_mail(body).unwrap();
-
- let subject = parsed.headers.get_first_value("Subject").unwrap();
- let date = parsed.headers.get_first_value("Date").unwrap();
- let text = parsed.get_body().unwrap();
-
- println!("\n<{date}> {subject}\n\n{text}");
+ let note = Note::from_email_message(&message);
+ println!("\n<{}> {}\n\n{}", note.timestamp, note.title, note.content);
= }
=
= if log::log_enabled!(log::Level::Debug) {
@@ -58,6 +54,34 @@ fn main() {
= info!("Done.");
=}
=
+struct Note {
+ title: String,
+ timestamp: DateTime<Utc>,
+ content: String,
+}
+
+impl Note {
+ // TODO: Return a result?
+ fn from_email_message(message: &imap::types::Fetch) -> Self {
+ let body = message.body().unwrap();
+ let parsed = parse_mail(body).unwrap();
+
+ let date = parsed
+ .headers
+ .get_first_value("Date")
+ .expect("No date in the message header");
+
+ let timestamp = chrono::DateTime::parse_from_rfc2822(&date)
+ .expect("Failed to parse the date {date} (expected RFC2822 format)");
+
+ Self {
+ title: parsed.headers.get_first_value("Subject").unwrap(),
+ timestamp: timestamp.into(),
+ content: parsed.get_body().unwrap(), // TODO: Convert to markdown (gfm)
+ }
+ }
+}
+
=// TODO: Return a result. How to combine different types of errors (io, tls, imap)?
=fn imap_connect(config: &IMAPConfig) -> imap::Session<TlsStream<TcpStream>> {
= // Setup TLS with a certificate from Proton BridgeUse Pandoc to convert HTML to markdown
On by
index 9c60094..91f1909 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -139,8 +139,15 @@ dependencies = [
= "log",
= "mailparse",
= "native-tls",
+ "pandoc",
=]
=
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
=[[package]]
=name = "encoding_rs"
=version = "0.8.33"
@@ -265,6 +272,15 @@ dependencies = [
= "windows-sys",
=]
=
+[[package]]
+name = "itertools"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
+dependencies = [
+ "either",
+]
+
=[[package]]
=name = "js-sys"
=version = "0.3.67"
@@ -416,6 +432,15 @@ dependencies = [
= "vcpkg",
=]
=
+[[package]]
+name = "pandoc"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "463d53d1a77a4291203dbf9d461365609e6857c95bd7d807098bffdc0a02a65c"
+dependencies = [
+ "itertools",
+]
+
=[[package]]
=name = "pkg-config"
=version = "0.3.28"index de3dae8..6e68e6d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,3 +12,4 @@ env_logger = "0.10.1"
=log = "0.4.20"
=mailparse = "0.14.1"
=chrono = "0.4.31"
+pandoc = "0.8.11"index 51fe718..a656f12 100644
--- a/flake.nix
+++ b/flake.nix
@@ -16,6 +16,7 @@
= inherit system overlays;
= };
= buildInputs = with pkgs; [
+ pandoc
= ];
= nativeBuildInputs = with pkgs; [
= (rust-bin.fromRustupToolchainFile ./rust-toolchain.toml)index 69e211d..846ef51 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,8 @@ use mailparse::parse_mail;
=use mailparse::MailHeaderMap;
=use native_tls;
=use native_tls::TlsStream;
+use pandoc;
+use pandoc::Pandoc;
=use std::env;
=use std::fs;
=use std::net::TcpStream;
@@ -74,14 +76,38 @@ impl Note {
= let timestamp = chrono::DateTime::parse_from_rfc2822(&date)
= .expect("Failed to parse the date {date} (expected RFC2822 format)");
=
+ let text = parsed
+ .get_body()
+ .expect("Failed to extract body from the message");
+
+ let content = content_to_markdown(text);
+
= Self {
= title: parsed.headers.get_first_value("Subject").unwrap(),
= timestamp: timestamp.into(),
- content: parsed.get_body().unwrap(), // TODO: Convert to markdown (gfm)
+ content,
= }
= }
=}
=
+fn content_to_markdown(text: String) -> String {
+ let mut pandoc = Pandoc::new();
+ pandoc.set_input(pandoc::InputKind::Pipe(text));
+ pandoc.set_output(pandoc::OutputKind::Pipe);
+ // TODO: Support other input formats, depending on the Content-Type header
+ pandoc.set_input_format(pandoc::InputFormat::Html, Vec::new());
+ pandoc.set_output_format(pandoc::OutputFormat::MarkdownGithub, Vec::new());
+
+ // This is a bit awkward. Do I have to do it like that?
+ if let pandoc::PandocOutput::ToBuffer(markdown) =
+ pandoc.execute().expect("Conversion of content failed")
+ {
+ markdown
+ } else {
+ panic!("Unexpected output kind from pandoc")
+ }
+}
+
=// TODO: Return a result. How to combine different types of errors (io, tls, imap)?
=fn imap_connect(config: &IMAPConfig) -> imap::Session<TlsStream<TcpStream>> {
= // Setup TLS with a certificate from Proton BridgeFix nix build unable to find openssl
On by
The nix build command would fail like this:
$ make result
cachix use software-garden
nix --experimental-features "nix-command flakes" build --print-build-logs
[...]
denote-tools> error: failed to run custom build command for `openssl-sys v0.9.98`
denote-tools> Caused by:
denote-tools> process didn't exit successfully: `/build/34wxjp5hsqxrabchml7p8hrhai7b1yx3-source/target/release/build/openssl-sys-8496b3a34109daeb/build-script-main` (exit status: 101)
denote-tools> --- stdout
denote-tools> cargo:rerun-if-env-changed=X86_64_UNKNOWN_LINUX_GNU_OPENSSL_LIB_DIR
denote-tools> X86_64_UNKNOWN_LINUX_GNU_OPENSSL_LIB_DIR unset
denote-tools> cargo:rerun-if-env-changed=OPENSSL_LIB_DIR
denote-tools> OPENSSL_LIB_DIR unset
denote-tools> cargo:rerun-if-env-changed=X86_64_UNKNOWN_LINUX_GNU_OPENSSL_INCLUDE_DIR
denote-tools> X86_64_UNKNOWN_LINUX_GNU_OPENSSL_INCLUDE_DIR unset
denote-tools> cargo:rerun-if-env-changed=OPENSSL_INCLUDE_DIR
denote-tools> OPENSSL_INCLUDE_DIR unset
denote-tools> cargo:rerun-if-env-changed=X86_64_UNKNOWN_LINUX_GNU_OPENSSL_DIR
denote-tools> X86_64_UNKNOWN_LINUX_GNU_OPENSSL_DIR unset
denote-tools> cargo:rerun-if-env-changed=OPENSSL_DIR
denote-tools> OPENSSL_DIR unset
denote-tools> cargo:rerun-if-env-changed=OPENSSL_NO_PKG_CONFIG
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_x86_64-unknown-linux-gnu
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_x86_64_unknown_linux_gnu
denote-tools> cargo:rerun-if-env-changed=HOST_PKG_CONFIG
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG
denote-tools> cargo:rerun-if-env-changed=OPENSSL_STATIC
denote-tools> cargo:rerun-if-env-changed=OPENSSL_DYNAMIC
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_ALL_STATIC
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_ALL_DYNAMIC
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64-unknown-linux-gnu
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_PATH_x86_64_unknown_linux_gnu
denote-tools> cargo:rerun-if-env-changed=HOST_PKG_CONFIG_PATH
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_PATH
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64-unknown-linux-gnu
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR_x86_64_unknown_linux_gnu
denote-tools> cargo:rerun-if-env-changed=HOST_PKG_CONFIG_LIBDIR
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_LIBDIR
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64-unknown-linux-gnu
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR_x86_64_unknown_linux_gnu
denote-tools> cargo:rerun-if-env-changed=HOST_PKG_CONFIG_SYSROOT_DIR
denote-tools> cargo:rerun-if-env-changed=PKG_CONFIG_SYSROOT_DIR
denote-tools> run pkg_config fail:
denote-tools> pkg-config exited with status code 1
denote-tools> > PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --libs --cflags openssl
denote-tools> The system library `openssl` required by crate `openssl-sys` was not found.
denote-tools> The file `openssl.pc` needs to be installed and the PKG_CONFIG_PATH environment variable must contain its parent directory.
denote-tools> The PKG_CONFIG_PATH environment variable is not set.
denote-tools> HINT: if you have installed the library, try setting PKG_CONFIG_PATH to the directory containing `openssl.pc`.
denote-tools> --- stderr
denote-tools> thread 'main' panicked at /build/cargo-vendor-dir/openssl-sys-0.9.98/build/find_normal.rs:190:5:
denote-tools> Could not find directory of OpenSSL installation, and this `-sys` crate cannot
denote-tools> proceed without this knowledge. If OpenSSL is installed and this crate had
denote-tools> trouble finding it, you can set the `OPENSSL_DIR` environment variable for the
denote-tools> compilation process.
denote-tools> Make sure you also have the development packages of openssl installed.
denote-tools> For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora.
denote-tools> If you're in a situation where you think the directory *should* be found
denote-tools> automatically, please open a bug at https://github.com/sfackler/rust-openssl
denote-tools> and include information about your system as well as this message.
denote-tools> $HOST = x86_64-unknown-linux-gnu
denote-tools> $TARGET = x86_64-unknown-linux-gnu
denote-tools> openssl-sys = 0.9.98
denote-tools> note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
denote-tools> warning: build failed, waiting for other jobs to finish...index a656f12..a00b755 100644
--- a/flake.nix
+++ b/flake.nix
@@ -49,6 +49,7 @@
= };
=
= inherit buildInputs nativeBuildInputs;
+ PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";
= };
= }
= );Usa a template to output a denote formatted note
On by
The YAML frontmatter is not escaped yet.
index 91f1909..8e7aa91 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -32,6 +32,50 @@ version = "0.5.2"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
=
+[[package]]
+name = "askama"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
+dependencies = [
+ "askama_derive",
+ "askama_escape",
+ "humansize",
+ "num-traits",
+ "percent-encoding",
+]
+
+[[package]]
+name = "askama_derive"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ccf09143e56923c12e027b83a9553210a3c58322ed8419a53461b14a4dccd85"
+dependencies = [
+ "askama_parser",
+ "basic-toml",
+ "mime",
+ "mime_guess",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn",
+]
+
+[[package]]
+name = "askama_escape"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
+
+[[package]]
+name = "askama_parser"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "262eb9cf7be51269c5f2951eeda9ccd14d6934e437457f47b4f066bf55a6770d"
+dependencies = [
+ "nom 7.1.3",
+]
+
=[[package]]
=name = "autocfg"
=version = "1.1.0"
@@ -44,6 +88,15 @@ version = "0.13.1"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
=
+[[package]]
+name = "basic-toml"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5"
+dependencies = [
+ "serde",
+]
+
=[[package]]
=name = "bitflags"
=version = "1.3.2"
@@ -133,6 +186,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
=name = "denote-tools"
=version = "0.1.0"
=dependencies = [
+ "askama",
= "chrono",
= "env_logger",
= "imap",
@@ -207,6 +261,15 @@ version = "0.3.3"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
=
+[[package]]
+name = "humansize"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
+dependencies = [
+ "libm",
+]
+
=[[package]]
=name = "humantime"
=version = "2.1.0"
@@ -248,7 +311,7 @@ dependencies = [
= "imap-proto",
= "lazy_static",
= "native-tls",
- "nom",
+ "nom 5.1.3",
= "regex",
=]
=
@@ -258,7 +321,7 @@ version = "0.10.2"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "16a6def1d5ac8975d70b3fd101d57953fe3278ef2ee5d7816cba54b1d1dfc22f"
=dependencies = [
- "nom",
+ "nom 5.1.3",
=]
=
=[[package]]
@@ -315,6 +378,12 @@ version = "0.2.152"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
=
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
=[[package]]
=name = "linux-raw-sys"
=version = "0.4.12"
@@ -344,6 +413,28 @@ version = "2.7.1"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
=
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
=[[package]]
=name = "native-tls"
=version = "0.2.11"
@@ -373,6 +464,16 @@ dependencies = [
= "version_check",
=]
=
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
=[[package]]
=name = "num-traits"
=version = "0.2.17"
@@ -441,6 +542,12 @@ dependencies = [
= "itertools",
=]
=
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
=[[package]]
=name = "pkg-config"
=version = "0.3.28"
@@ -560,6 +667,26 @@ dependencies = [
= "libc",
=]
=
+[[package]]
+name = "serde"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
=[[package]]
=name = "static_assertions"
=version = "1.1.0"
@@ -599,6 +726,15 @@ dependencies = [
= "winapi-util",
=]
=
+[[package]]
+name = "unicase"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
+dependencies = [
+ "version_check",
+]
+
=[[package]]
=name = "unicode-ident"
=version = "1.0.12"index 6e68e6d..1d10d6e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,3 +13,4 @@ log = "0.4.20"
=mailparse = "0.14.1"
=chrono = "0.4.31"
=pandoc = "0.8.11"
+askama = "0.12.1"index 846ef51..429c6a3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+use askama::Template;
=use chrono::DateTime;
=use chrono::Utc;
=use env_logger;
@@ -43,7 +44,7 @@ fn main() {
=
= for message in &messages {
= let note = Note::from_email_message(&message);
- println!("\n<{}> {}\n\n{}", note.timestamp, note.title, note.content);
+ println!("{}", note.to_markdown());
= }
=
= if log::log_enabled!(log::Level::Debug) {
@@ -88,6 +89,18 @@ impl Note {
= content,
= }
= }
+
+ fn to_markdown(&self) -> String {
+ let markdown = MarkdownNoteTemplate {
+ title: self.title.clone(),
+ date: self.timestamp.to_rfc3339(),
+ identifier: self.timestamp.format("%Y%m%dT%H%M%S").to_string(),
+ content: self.content.clone(),
+ };
+ markdown
+ .render()
+ .expect("Failed to render the note as markdown")
+ }
=}
=
=fn content_to_markdown(text: String) -> String {
@@ -108,6 +121,15 @@ fn content_to_markdown(text: String) -> String {
= }
=}
=
+#[derive(Template)]
+#[template(path = "note.md")]
+struct MarkdownNoteTemplate {
+ title: String,
+ date: String,
+ identifier: String,
+ content: String,
+}
+
=// TODO: Return a result. How to combine different types of errors (io, tls, imap)?
=fn imap_connect(config: &IMAPConfig) -> imap::Session<TlsStream<TcpStream>> {
= // Setup TLS with a certificate from Proton Bridgenew file mode 100644
index 0000000..e69a34b
--- /dev/null
+++ b/templates/note.md
@@ -0,0 +1,8 @@
+---
+title: {{ title }}
+date: {{ date }}
+tags: []
+identifier: "{{ identifier }}"
+---
+
+{{ content }}Use yaml filters to format values in the template
On by
Interestingly, the filter breaks lines after atomic values (like strings and dates). This is remedied by using trim filter.
Another advantage is that DateTime value can be passed to the template directly, and the filter converts it, as long as serde feature is enabled on chrono and serde-yaml on askama.
index 8e7aa91..2582b84 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -43,6 +43,8 @@ dependencies = [
= "humansize",
= "num-traits",
= "percent-encoding",
+ "serde",
+ "serde_yaml",
=]
=
=[[package]]
@@ -156,6 +158,7 @@ dependencies = [
= "iana-time-zone",
= "js-sys",
= "num-traits",
+ "serde",
= "wasm-bindgen",
= "windows-targets 0.48.5",
=]
@@ -255,6 +258,12 @@ version = "0.1.1"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
=
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
=[[package]]
=name = "hermit-abi"
=version = "0.3.3"
@@ -324,6 +333,16 @@ dependencies = [
= "nom 5.1.3",
=]
=
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
=[[package]]
=name = "is-terminal"
=version = "0.4.10"
@@ -344,6 +363,12 @@ dependencies = [
= "either",
=]
=
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
=[[package]]
=name = "js-sys"
=version = "0.3.67"
@@ -687,6 +712,19 @@ dependencies = [
= "syn",
=]
=
+[[package]]
+name = "serde_yaml"
+version = "0.9.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
=[[package]]
=name = "static_assertions"
=version = "1.1.0"
@@ -741,6 +779,12 @@ version = "1.0.12"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
=
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
+
=[[package]]
=name = "vcpkg"
=version = "0.2.15"index 1d10d6e..3f7b7e6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,6 @@ native-tls = "0.2.11"
=env_logger = "0.10.1"
=log = "0.4.20"
=mailparse = "0.14.1"
-chrono = "0.4.31"
+chrono = { version = "0.4.31", features = [ "serde" ] }
=pandoc = "0.8.11"
-askama = "0.12.1"
+askama = { version = "0.12.1", features = [ "serde-yaml" ] }index 429c6a3..29896de 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -93,7 +93,7 @@ impl Note {
= fn to_markdown(&self) -> String {
= let markdown = MarkdownNoteTemplate {
= title: self.title.clone(),
- date: self.timestamp.to_rfc3339(),
+ date: self.timestamp,
= identifier: self.timestamp.format("%Y%m%dT%H%M%S").to_string(),
= content: self.content.clone(),
= };
@@ -125,7 +125,7 @@ fn content_to_markdown(text: String) -> String {
=#[template(path = "note.md")]
=struct MarkdownNoteTemplate {
= title: String,
- date: String,
+ date: DateTime<Utc>,
= identifier: String,
= content: String,
=}index e69a34b..6eb0075 100644
--- a/templates/note.md
+++ b/templates/note.md
@@ -1,8 +1,7 @@
=---
-title: {{ title }}
-date: {{ date }}
-tags: []
-identifier: "{{ identifier }}"
+title: {{ title|yaml|trim }}
+date: {{ date|yaml|trim }}
+identifier: {{ identifier|yaml|trim }}
=---
=
={{ content }}Write imported notes to files
On by
There is a new configuration variable imap_export_path that needs to point to an existing directory where notes will be saved.
index 2582b84..f216e5f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -197,8 +197,15 @@ dependencies = [
= "mailparse",
= "native-tls",
= "pandoc",
+ "slug",
=]
=
+[[package]]
+name = "deunicode"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae2a35373c5c74340b79ae6780b498b2b183915ec5dacf263aac5a099bf485a"
+
=[[package]]
=name = "either"
=version = "1.9.0"
@@ -725,6 +732,16 @@ dependencies = [
= "unsafe-libyaml",
=]
=
+[[package]]
+name = "slug"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4"
+dependencies = [
+ "deunicode",
+ "wasm-bindgen",
+]
+
=[[package]]
=name = "static_assertions"
=version = "1.1.0"index 3f7b7e6..f6db161 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,3 +14,4 @@ mailparse = "0.14.1"
=chrono = { version = "0.4.31", features = [ "serde" ] }
=pandoc = "0.8.11"
=askama = { version = "0.12.1", features = [ "serde-yaml" ] }
+slug = "0.1.5"new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/notes/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignoreindex 29896de..4b68613 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,9 +10,11 @@ use native_tls;
=use native_tls::TlsStream;
=use pandoc;
=use pandoc::Pandoc;
+use slug::slugify;
=use std::env;
=use std::fs;
=use std::net::TcpStream;
+use std::path::Path;
=
=fn main() {
= env_logger::init();
@@ -42,9 +44,16 @@ fn main() {
= let messages = session.fetch("1:*", "RFC822").unwrap();
= debug!("{} messages fetched", messages.len());
=
+ let export_path = Path::new(&config.export_path);
= for message in &messages {
= let note = Note::from_email_message(&message);
- println!("{}", note.to_markdown());
+ let path = export_path.join(note.filename());
+ info!(
+ "Importing {path} {title}",
+ path = path.display(),
+ title = ¬e.title
+ );
+ fs::write(path, note.to_markdown()).expect("Failed to write the file");
= }
=
= if log::log_enabled!(log::Level::Debug) {
@@ -94,13 +103,28 @@ impl Note {
= let markdown = MarkdownNoteTemplate {
= title: self.title.clone(),
= date: self.timestamp,
- identifier: self.timestamp.format("%Y%m%dT%H%M%S").to_string(),
+ identifier: self.identifier(),
= content: self.content.clone(),
+ // TODO: Support tags
= };
= markdown
= .render()
= .expect("Failed to render the note as markdown")
= }
+
+ fn identifier(&self) -> String {
+ self.timestamp.format("%Y%m%dT%H%M%S").to_string()
+ }
+
+ fn filename(&self) -> String {
+ // TODO: Support tags in file name.
+ // NOTE: If tags are present, then after slug there is __ and then tags separated by _.
+ format!(
+ "{identifier}--{slug}.md",
+ identifier = self.identifier(),
+ slug = slugify(&self.title),
+ )
+ }
=}
=
=fn content_to_markdown(text: String) -> String {
@@ -181,6 +205,7 @@ pub struct IMAPConfig {
= password: String,
= mailbox: String,
= cart_path: Option<String>,
+ export_path: String,
=}
=
=impl IMAPConfig {
@@ -198,6 +223,8 @@ impl IMAPConfig {
= .expect("There should be an environment variable named 'imap_password'"),
= mailbox: env::var("imap_mailbox")
= .expect("There should be an environment variable named 'imap_mailbox'"),
+ export_path: env::var("imap_export_path")
+ .expect("There should be an environment variable named 'imap_export_path'"),
= cart_path: env::var("imap_cert_path")
= .map(Option::Some)
= .or_else(|error| {index 6eb0075..4f46f51 100644
--- a/templates/note.md
+++ b/templates/note.md
@@ -4,4 +4,5 @@ date: {{ date|yaml|trim }}
=identifier: {{ identifier|yaml|trim }}
=---
=
-{{ content }}
+{{ content|trim }}
+Prevent overwriting or duplicating existing notes
On by
index f216e5f..9b83e24 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -192,6 +192,7 @@ dependencies = [
= "askama",
= "chrono",
= "env_logger",
+ "glob",
= "imap",
= "log",
= "mailparse",
@@ -265,6 +266,12 @@ version = "0.1.1"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
=
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
=[[package]]
=name = "hashbrown"
=version = "0.12.3"index f6db161..35b6701 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,3 +15,4 @@ chrono = { version = "0.4.31", features = [ "serde" ] }
=pandoc = "0.8.11"
=askama = { version = "0.12.1", features = [ "serde-yaml" ] }
=slug = "0.1.5"
+glob = "0.3.1"index 4b68613..03d1eec 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,7 @@ use askama::Template;
=use chrono::DateTime;
=use chrono::Utc;
=use env_logger;
+use glob::glob;
=use imap;
=use log::{debug, info};
=use mailparse::parse_mail;
@@ -47,13 +48,32 @@ fn main() {
= let export_path = Path::new(&config.export_path);
= for message in &messages {
= let note = Note::from_email_message(&message);
- let path = export_path.join(note.filename());
- info!(
- "Importing {path} {title}",
- path = path.display(),
- title = ¬e.title
+
+ let pattern = format!(
+ "{export_path}/{identifier}--*",
+ export_path = export_path.display(),
+ identifier = note.identifier()
= );
- fs::write(path, note.to_markdown()).expect("Failed to write the file");
+
+ if let Some(existing) = glob(&pattern)
+ .expect("glob pattern should work just fine")
+ .next()
+ {
+ let existing =
+ existing.expect("it should be possible to extract path of the preexisting note");
+ info!("Skipping import of '{title}'. There already exists note with the same identifier: {existing}",
+ title = ¬e.title,
+ existing = existing.display()
+ );
+ } else {
+ let path = export_path.join(note.filename());
+ info!(
+ "Importing {path} {title}",
+ path = path.display(),
+ title = ¬e.title
+ );
+ fs::write(path, note.to_markdown()).expect("Failed to write the file");
+ }
= }
=
= if log::log_enabled!(log::Level::Debug) {Create a readme file
On by
new file mode 100644
index 0000000..d233c9f
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+Liberate my notes from my laptop