diff --git a/.direnv/bin/nix-direnv-reload b/.direnv/bin/nix-direnv-reload
new file mode 100755
index 0000000..3d35aab
--- /dev/null
+++ b/.direnv/bin/nix-direnv-reload
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+set -e
+if [[ ! -d "/home/ryan/Documents/Code/vocab-assistant" ]]; then
+ echo "Cannot find source directory; Did you move it?"
+ echo "(Looking for "/home/ryan/Documents/Code/vocab-assistant")"
+ echo 'Cannot force reload with this script - use "direnv reload" manually and then try again'
+ exit 1
+fi
+
+# rebuild the cache forcefully
+_nix_direnv_force_reload=1 direnv exec "/home/ryan/Documents/Code/vocab-assistant" true
+
+# Update the mtime for .envrc.
+# This will cause direnv to reload again - but without re-building.
+touch "/home/ryan/Documents/Code/vocab-assistant/.envrc"
+
+# Also update the timestamp of whatever profile_rc we have.
+# This makes sure that we know we are up to date.
+touch -r "/home/ryan/Documents/Code/vocab-assistant/.envrc" "/home/ryan/Documents/Code/vocab-assistant/.direnv"/*.rc
diff --git a/.direnv/flake-inputs/01x5k4nlxcpyd85nnr0b9gm89rm8ff4x-source b/.direnv/flake-inputs/01x5k4nlxcpyd85nnr0b9gm89rm8ff4x-source
new file mode 120000
index 0000000..f7fce9f
--- /dev/null
+++ b/.direnv/flake-inputs/01x5k4nlxcpyd85nnr0b9gm89rm8ff4x-source
@@ -0,0 +1 @@
+/nix/store/01x5k4nlxcpyd85nnr0b9gm89rm8ff4x-source
\ No newline at end of file
diff --git a/.direnv/flake-inputs/0g8skbfikvvink4qbh14m1dl1623ji1i-source b/.direnv/flake-inputs/0g8skbfikvvink4qbh14m1dl1623ji1i-source
new file mode 120000
index 0000000..044797f
--- /dev/null
+++ b/.direnv/flake-inputs/0g8skbfikvvink4qbh14m1dl1623ji1i-source
@@ -0,0 +1 @@
+/nix/store/0g8skbfikvvink4qbh14m1dl1623ji1i-source
\ No newline at end of file
diff --git a/.direnv/flake-inputs/0yw40qnrar3lvc5hax5n49abl57apjbn-source b/.direnv/flake-inputs/0yw40qnrar3lvc5hax5n49abl57apjbn-source
new file mode 120000
index 0000000..27f7157
--- /dev/null
+++ b/.direnv/flake-inputs/0yw40qnrar3lvc5hax5n49abl57apjbn-source
@@ -0,0 +1 @@
+/nix/store/0yw40qnrar3lvc5hax5n49abl57apjbn-source
\ No newline at end of file
diff --git a/.direnv/flake-inputs/yj1wxm9hh8610iyzqnz75kvs6xl8j3my-source b/.direnv/flake-inputs/yj1wxm9hh8610iyzqnz75kvs6xl8j3my-source
new file mode 120000
index 0000000..f17959f
--- /dev/null
+++ b/.direnv/flake-inputs/yj1wxm9hh8610iyzqnz75kvs6xl8j3my-source
@@ -0,0 +1 @@
+/nix/store/yj1wxm9hh8610iyzqnz75kvs6xl8j3my-source
\ No newline at end of file
diff --git a/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa b/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa
new file mode 120000
index 0000000..264eaed
--- /dev/null
+++ b/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa
@@ -0,0 +1 @@
+/nix/store/r30vn7a4yvcnidgnvn3zmaqdar7lynkz-nix-shell-env
\ No newline at end of file
diff --git a/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc b/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc
new file mode 100644
index 0000000..2c30a40
--- /dev/null
+++ b/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc
@@ -0,0 +1,2093 @@
+unset shellHook
+PATH=${PATH:-}
+nix_saved_PATH="$PATH"
+XDG_DATA_DIRS=${XDG_DATA_DIRS:-}
+nix_saved_XDG_DATA_DIRS="$XDG_DATA_DIRS"
+AR='ar'
+export AR
+AS='as'
+export AS
+BASH='/nix/store/ki4if6b0w5bqv8dc5lrjp8xm7wjy9dlf-bash-5.2p37/bin/bash'
+CC='gcc'
+export CC
+CONFIG_SHELL='/nix/store/ki4if6b0w5bqv8dc5lrjp8xm7wjy9dlf-bash-5.2p37/bin/bash'
+export CONFIG_SHELL
+CXX='g++'
+export CXX
+HOSTTYPE='x86_64'
+HOST_PATH='/nix/store/vkxs2i4js42i26ay7vfqzyibsdbl4lrm-bash-interactive-5.2p37/bin:/nix/store/j2jn1l1s1kcjmf66jfdqci5frv5mq5ap-elixir-1.18.3/bin:/nix/store/d61vvbfyn3ijzs6snxyjf2pprg7c88ah-elixir-ls-0.27.1/bin:/nix/store/87hsv7pc321h80j7223209nxc8vmabqb-nodejs-22.14.0/bin:/nix/store/n433jdgmrkacnpkwfj9j8h6haaghl3f6-flyctl-0.3.87/bin:/nix/store/liqg1rrgwl4dfkpf4d533f4sag4ig75c-inotify-tools-4.23.9.0/bin:/nix/store/vz67zaqyfln2jcqwar682kdlrxfx9vq1-coreutils-9.6/bin:/nix/store/cfd3hysxk7b4m584s539hzm2bc013lsg-findutils-4.10.0/bin:/nix/store/kdrp6y7k352jb9ysjprzvmrlmiblz0jq-diffutils-3.10/bin:/nix/store/l97jblhqk9ga2dl01ysd3wf51mwqcd6s-gnused-4.9/bin:/nix/store/h3xhfhg0vv3k3hvkcwkmdg99r4zm9mcl-gnugrep-3.11/bin:/nix/store/kz9cfpxf08fdsgxgh6zp1k1y5npsnjpr-gawk-5.3.1/bin:/nix/store/j9gjzsbag34n3ic3yg4ra0p5yl8wcbhg-gnutar-1.35/bin:/nix/store/g9h265xkvsma74irj4389djs39k96d72-gzip-1.13/bin:/nix/store/p406mc3r448lfrl0qgqm4cg8qax7jw5v-bzip2-1.0.8-bin/bin:/nix/store/zkman1hvq58ica17s06r9hq62k300c1v-gnumake-4.4.1/bin:/nix/store/ki4if6b0w5bqv8dc5lrjp8xm7wjy9dlf-bash-5.2p37/bin:/nix/store/gkw3fwmfvn06gz7hh6vwdskh3s1lkvcv-patch-2.7.6/bin:/nix/store/7w6d3dwm5zs6vn37wmsbi0szvx0w98gy-xz-5.6.4-bin/bin:/nix/store/i4fn3frirjgrfsmwkbr4srfpry18x5dz-file-5.46/bin'
+export HOST_PATH
+IFS='
+'
+IN_NIX_SHELL='impure'
+export IN_NIX_SHELL
+LD='ld'
+export LD
+LINENO='76'
+LOCALE_ARCHIVE='/nix/store/hgybbbzg778wasmw5g1hzm7a8n4wwd5h-glibc-locales-2.40-66/lib/locale/locale-archive'
+export LOCALE_ARCHIVE
+MACHTYPE='x86_64-pc-linux-gnu'
+NIX_BINTOOLS='/nix/store/kinjsav83qgiiy8mwgypnnc9sn8m8r9y-binutils-wrapper-2.43.1'
+export NIX_BINTOOLS
+NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu='1'
+export NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu
+NIX_BUILD_CORES='16'
+export NIX_BUILD_CORES
+NIX_CC='/nix/store/gj9lra51hwhxnhz05jqk5lh03wipamv0-gcc-wrapper-14-20241116'
+export NIX_CC
+NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu='1'
+export NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu
+NIX_CFLAGS_COMPILE=' -frandom-seed=r30vn7a4yv -isystem /nix/store/rah2m9cw7aa445x10librmi5wxwfb73k-bash-interactive-5.2p37-dev/include -isystem /nix/store/87hsv7pc321h80j7223209nxc8vmabqb-nodejs-22.14.0/include -isystem /nix/store/liqg1rrgwl4dfkpf4d533f4sag4ig75c-inotify-tools-4.23.9.0/include -isystem /nix/store/rah2m9cw7aa445x10librmi5wxwfb73k-bash-interactive-5.2p37-dev/include -isystem /nix/store/87hsv7pc321h80j7223209nxc8vmabqb-nodejs-22.14.0/include -isystem /nix/store/liqg1rrgwl4dfkpf4d533f4sag4ig75c-inotify-tools-4.23.9.0/include'
+export NIX_CFLAGS_COMPILE
+NIX_ENFORCE_NO_NATIVE='1'
+export NIX_ENFORCE_NO_NATIVE
+NIX_HARDENING_ENABLE='bindnow format fortify fortify3 pic relro stackclashprotection stackprotector strictoverflow zerocallusedregs'
+export NIX_HARDENING_ENABLE
+NIX_LDFLAGS='-rpath /home/ryan/Documents/Code/vocab-assistant/outputs/out/lib -L/nix/store/liqg1rrgwl4dfkpf4d533f4sag4ig75c-inotify-tools-4.23.9.0/lib -L/nix/store/liqg1rrgwl4dfkpf4d533f4sag4ig75c-inotify-tools-4.23.9.0/lib'
+export NIX_LDFLAGS
+NIX_NO_SELF_RPATH='1'
+NIX_STORE='/nix/store'
+export NIX_STORE
+NM='nm'
+export NM
+NODE_PATH='/nix/store/87hsv7pc321h80j7223209nxc8vmabqb-nodejs-22.14.0/lib/node_modules'
+export NODE_PATH
+OBJCOPY='objcopy'
+export OBJCOPY
+OBJDUMP='objdump'
+export OBJDUMP
+OLDPWD=''
+export OLDPWD
+OPTERR='1'
+OSTYPE='linux-gnu'
+PATH='/nix/store/qp4a5bxdck7c0w7l2mf47phhfk2nmscs-patchelf-0.15.0/bin:/nix/store/gj9lra51hwhxnhz05jqk5lh03wipamv0-gcc-wrapper-14-20241116/bin:/nix/store/aw0qxjd1phf16qhlwpdb4x87yymfv9rp-gcc-14-20241116/bin:/nix/store/cgp5cbv6b3djw35zkl4s77snh0navr3w-glibc-2.40-66-bin/bin:/nix/store/vz67zaqyfln2jcqwar682kdlrxfx9vq1-coreutils-9.6/bin:/nix/store/kinjsav83qgiiy8mwgypnnc9sn8m8r9y-binutils-wrapper-2.43.1/bin:/nix/store/dh0m67pv8snfcx1ic18jzp0x4cb10zhc-binutils-2.43.1/bin:/nix/store/vkxs2i4js42i26ay7vfqzyibsdbl4lrm-bash-interactive-5.2p37/bin:/nix/store/j2jn1l1s1kcjmf66jfdqci5frv5mq5ap-elixir-1.18.3/bin:/nix/store/d61vvbfyn3ijzs6snxyjf2pprg7c88ah-elixir-ls-0.27.1/bin:/nix/store/87hsv7pc321h80j7223209nxc8vmabqb-nodejs-22.14.0/bin:/nix/store/n433jdgmrkacnpkwfj9j8h6haaghl3f6-flyctl-0.3.87/bin:/nix/store/liqg1rrgwl4dfkpf4d533f4sag4ig75c-inotify-tools-4.23.9.0/bin:/nix/store/vz67zaqyfln2jcqwar682kdlrxfx9vq1-coreutils-9.6/bin:/nix/store/cfd3hysxk7b4m584s539hzm2bc013lsg-findutils-4.10.0/bin:/nix/store/kdrp6y7k352jb9ysjprzvmrlmiblz0jq-diffutils-3.10/bin:/nix/store/l97jblhqk9ga2dl01ysd3wf51mwqcd6s-gnused-4.9/bin:/nix/store/h3xhfhg0vv3k3hvkcwkmdg99r4zm9mcl-gnugrep-3.11/bin:/nix/store/kz9cfpxf08fdsgxgh6zp1k1y5npsnjpr-gawk-5.3.1/bin:/nix/store/j9gjzsbag34n3ic3yg4ra0p5yl8wcbhg-gnutar-1.35/bin:/nix/store/g9h265xkvsma74irj4389djs39k96d72-gzip-1.13/bin:/nix/store/p406mc3r448lfrl0qgqm4cg8qax7jw5v-bzip2-1.0.8-bin/bin:/nix/store/zkman1hvq58ica17s06r9hq62k300c1v-gnumake-4.4.1/bin:/nix/store/ki4if6b0w5bqv8dc5lrjp8xm7wjy9dlf-bash-5.2p37/bin:/nix/store/gkw3fwmfvn06gz7hh6vwdskh3s1lkvcv-patch-2.7.6/bin:/nix/store/7w6d3dwm5zs6vn37wmsbi0szvx0w98gy-xz-5.6.4-bin/bin:/nix/store/i4fn3frirjgrfsmwkbr4srfpry18x5dz-file-5.46/bin'
+export PATH
+PS4='+ '
+RANLIB='ranlib'
+export RANLIB
+READELF='readelf'
+export READELF
+SHELL='/nix/store/ki4if6b0w5bqv8dc5lrjp8xm7wjy9dlf-bash-5.2p37/bin/bash'
+export SHELL
+SIZE='size'
+export SIZE
+SOURCE_DATE_EPOCH='315532800'
+export SOURCE_DATE_EPOCH
+STRINGS='strings'
+export STRINGS
+STRIP='strip'
+export STRIP
+XDG_DATA_DIRS='/nix/store/qp4a5bxdck7c0w7l2mf47phhfk2nmscs-patchelf-0.15.0/share'
+export XDG_DATA_DIRS
+__structuredAttrs=''
+export __structuredAttrs
+_substituteStream_has_warned_replace_deprecation='false'
+buildInputs='/nix/store/rah2m9cw7aa445x10librmi5wxwfb73k-bash-interactive-5.2p37-dev /nix/store/j2jn1l1s1kcjmf66jfdqci5frv5mq5ap-elixir-1.18.3 /nix/store/d61vvbfyn3ijzs6snxyjf2pprg7c88ah-elixir-ls-0.27.1 /nix/store/87hsv7pc321h80j7223209nxc8vmabqb-nodejs-22.14.0 /nix/store/hgybbbzg778wasmw5g1hzm7a8n4wwd5h-glibc-locales-2.40-66 /nix/store/n433jdgmrkacnpkwfj9j8h6haaghl3f6-flyctl-0.3.87 /nix/store/liqg1rrgwl4dfkpf4d533f4sag4ig75c-inotify-tools-4.23.9.0'
+export buildInputs
+buildPhase='{ echo "------------------------------------------------------------";
+ echo " WARNING: the existence of this path is not guaranteed.";
+ echo " It is an internal implementation detail for pkgs.mkShell.";
+ echo "------------------------------------------------------------";
+ echo;
+ # Record all build inputs as runtime dependencies
+ export;
+} >> "$out"
+'
+export buildPhase
+builder='/nix/store/ki4if6b0w5bqv8dc5lrjp8xm7wjy9dlf-bash-5.2p37/bin/bash'
+export builder
+cmakeFlags=''
+export cmakeFlags
+configureFlags=''
+export configureFlags
+defaultBuildInputs=''
+defaultNativeBuildInputs='/nix/store/qp4a5bxdck7c0w7l2mf47phhfk2nmscs-patchelf-0.15.0 /nix/store/x604hxrmd9ck0irksbd3ilfnygc98x7a-update-autotools-gnu-config-scripts-hook /nix/store/h9lc1dpi14z7is86ffhl3ld569138595-audit-tmpdir.sh /nix/store/m54bmrhj6fqz8nds5zcj97w9s9bckc9v-compress-man-pages.sh /nix/store/wgrbkkaldkrlrni33ccvm3b6vbxzb656-make-symlinks-relative.sh /nix/store/5yzw0vhkyszf2d179m0qfkgxmp5wjjx4-move-docs.sh /nix/store/fyaryjvghbkpfnsyw97hb3lyb37s1pd6-move-lib64.sh /nix/store/kd4xwxjpjxi71jkm6ka0np72if9rm3y0-move-sbin.sh /nix/store/pag6l61paj1dc9sv15l7bm5c17xn5kyk-move-systemd-user-units.sh /nix/store/cmzya9irvxzlkh7lfy6i82gbp0saxqj3-multiple-outputs.sh /nix/store/jxknwvfj6kw8awi3lsjx1807q5q1sybz-no-broken-symlinks.sh /nix/store/hxv896faph0rqxjq2ycxpcrbnngc95sz-patch-shebangs.sh /nix/store/cickvswrvann041nqxb0rxilc46svw1n-prune-libtool-files.sh /nix/store/xyff06pkhki3qy1ls77w10s0v79c9il0-reproducible-builds.sh /nix/store/z7k98578dfzi6l3hsvbivzm7hfqlk0zc-set-source-date-epoch-to-latest.sh /nix/store/pilsssjjdxvdphlg2h19p0bfx5q0jzkn-strip.sh /nix/store/gj9lra51hwhxnhz05jqk5lh03wipamv0-gcc-wrapper-14-20241116'
+depsBuildBuild=''
+export depsBuildBuild
+depsBuildBuildPropagated=''
+export depsBuildBuildPropagated
+depsBuildTarget=''
+export depsBuildTarget
+depsBuildTargetPropagated=''
+export depsBuildTargetPropagated
+depsHostHost=''
+export depsHostHost
+depsHostHostPropagated=''
+export depsHostHostPropagated
+depsTargetTarget=''
+export depsTargetTarget
+depsTargetTargetPropagated=''
+export depsTargetTargetPropagated
+doCheck=''
+export doCheck
+doInstallCheck=''
+export doInstallCheck
+dontAddDisableDepTrack='1'
+export dontAddDisableDepTrack
+declare -a envBuildBuildHooks=()
+declare -a envBuildHostHooks=()
+declare -a envBuildTargetHooks=()
+declare -a envHostHostHooks=('ccWrapper_addCVars' 'bintoolsWrapper_addLDVars' 'addNodePath' )
+declare -a envHostTargetHooks=('ccWrapper_addCVars' 'bintoolsWrapper_addLDVars' 'addNodePath' )
+declare -a envTargetTargetHooks=()
+declare -a fixupOutputHooks=('if [ -z "${dontPatchELF-}" ]; then patchELF "$prefix"; fi' 'if [[ -z "${noAuditTmpdir-}" && -e "$prefix" ]]; then auditTmpdir "$prefix"; fi' 'if [ -z "${dontGzipMan-}" ]; then compressManPages "$prefix"; fi' '_moveLib64' '_moveSbin' '_moveSystemdUserUnits' 'patchShebangsAuto' '_pruneLibtoolFiles' '_doStrip' )
+guess='16'
+initialPath='/nix/store/vz67zaqyfln2jcqwar682kdlrxfx9vq1-coreutils-9.6 /nix/store/cfd3hysxk7b4m584s539hzm2bc013lsg-findutils-4.10.0 /nix/store/kdrp6y7k352jb9ysjprzvmrlmiblz0jq-diffutils-3.10 /nix/store/l97jblhqk9ga2dl01ysd3wf51mwqcd6s-gnused-4.9 /nix/store/h3xhfhg0vv3k3hvkcwkmdg99r4zm9mcl-gnugrep-3.11 /nix/store/kz9cfpxf08fdsgxgh6zp1k1y5npsnjpr-gawk-5.3.1 /nix/store/j9gjzsbag34n3ic3yg4ra0p5yl8wcbhg-gnutar-1.35 /nix/store/g9h265xkvsma74irj4389djs39k96d72-gzip-1.13 /nix/store/p406mc3r448lfrl0qgqm4cg8qax7jw5v-bzip2-1.0.8-bin /nix/store/zkman1hvq58ica17s06r9hq62k300c1v-gnumake-4.4.1 /nix/store/ki4if6b0w5bqv8dc5lrjp8xm7wjy9dlf-bash-5.2p37 /nix/store/gkw3fwmfvn06gz7hh6vwdskh3s1lkvcv-patch-2.7.6 /nix/store/7w6d3dwm5zs6vn37wmsbi0szvx0w98gy-xz-5.6.4-bin /nix/store/i4fn3frirjgrfsmwkbr4srfpry18x5dz-file-5.46'
+mesonFlags=''
+export mesonFlags
+name='nix-shell-env'
+export name
+nativeBuildInputs=''
+export nativeBuildInputs
+out='/home/ryan/Documents/Code/vocab-assistant/outputs/out'
+export out
+outputBin='out'
+outputDev='out'
+outputDevdoc='REMOVE'
+outputDevman='out'
+outputDoc='out'
+outputInclude='out'
+outputInfo='out'
+outputLib='out'
+outputMan='out'
+outputs='out'
+export outputs
+patches=''
+export patches
+phases='buildPhase'
+export phases
+pkg='/nix/store/gj9lra51hwhxnhz05jqk5lh03wipamv0-gcc-wrapper-14-20241116'
+declare -a pkgsBuildBuild=()
+declare -a pkgsBuildHost=('/nix/store/qp4a5bxdck7c0w7l2mf47phhfk2nmscs-patchelf-0.15.0' '/nix/store/x604hxrmd9ck0irksbd3ilfnygc98x7a-update-autotools-gnu-config-scripts-hook' '/nix/store/h9lc1dpi14z7is86ffhl3ld569138595-audit-tmpdir.sh' '/nix/store/m54bmrhj6fqz8nds5zcj97w9s9bckc9v-compress-man-pages.sh' '/nix/store/wgrbkkaldkrlrni33ccvm3b6vbxzb656-make-symlinks-relative.sh' '/nix/store/5yzw0vhkyszf2d179m0qfkgxmp5wjjx4-move-docs.sh' '/nix/store/fyaryjvghbkpfnsyw97hb3lyb37s1pd6-move-lib64.sh' '/nix/store/kd4xwxjpjxi71jkm6ka0np72if9rm3y0-move-sbin.sh' '/nix/store/pag6l61paj1dc9sv15l7bm5c17xn5kyk-move-systemd-user-units.sh' '/nix/store/cmzya9irvxzlkh7lfy6i82gbp0saxqj3-multiple-outputs.sh' '/nix/store/jxknwvfj6kw8awi3lsjx1807q5q1sybz-no-broken-symlinks.sh' '/nix/store/hxv896faph0rqxjq2ycxpcrbnngc95sz-patch-shebangs.sh' '/nix/store/cickvswrvann041nqxb0rxilc46svw1n-prune-libtool-files.sh' '/nix/store/xyff06pkhki3qy1ls77w10s0v79c9il0-reproducible-builds.sh' '/nix/store/z7k98578dfzi6l3hsvbivzm7hfqlk0zc-set-source-date-epoch-to-latest.sh' '/nix/store/pilsssjjdxvdphlg2h19p0bfx5q0jzkn-strip.sh' '/nix/store/gj9lra51hwhxnhz05jqk5lh03wipamv0-gcc-wrapper-14-20241116' '/nix/store/kinjsav83qgiiy8mwgypnnc9sn8m8r9y-binutils-wrapper-2.43.1' )
+declare -a pkgsBuildTarget=()
+declare -a pkgsHostHost=()
+declare -a pkgsHostTarget=('/nix/store/rah2m9cw7aa445x10librmi5wxwfb73k-bash-interactive-5.2p37-dev' '/nix/store/vkxs2i4js42i26ay7vfqzyibsdbl4lrm-bash-interactive-5.2p37' '/nix/store/j2jn1l1s1kcjmf66jfdqci5frv5mq5ap-elixir-1.18.3' '/nix/store/d61vvbfyn3ijzs6snxyjf2pprg7c88ah-elixir-ls-0.27.1' '/nix/store/87hsv7pc321h80j7223209nxc8vmabqb-nodejs-22.14.0' '/nix/store/hgybbbzg778wasmw5g1hzm7a8n4wwd5h-glibc-locales-2.40-66' '/nix/store/n433jdgmrkacnpkwfj9j8h6haaghl3f6-flyctl-0.3.87' '/nix/store/liqg1rrgwl4dfkpf4d533f4sag4ig75c-inotify-tools-4.23.9.0' )
+declare -a pkgsTargetTarget=()
+declare -a postFixupHooks=('_makeSymlinksRelativeInAllOutputs' '_multioutPropagateDev' 'noBrokenSymlinksInAllOutputs' )
+declare -a postUnpackHooks=('_updateSourceDateEpochFromSourceRoot' )
+declare -a preConfigureHooks=('_multioutConfig' )
+preConfigurePhases=' updateAutotoolsGnuConfigScriptsPhase'
+declare -a preFixupHooks=('_moveToShare' '_multioutDocs' '_multioutDevs' )
+preferLocalBuild='1'
+export preferLocalBuild
+prefix='/home/ryan/Documents/Code/vocab-assistant/outputs/out'
+declare -a propagatedBuildDepFiles=('propagated-build-build-deps' 'propagated-native-build-inputs' 'propagated-build-target-deps' )
+propagatedBuildInputs=''
+export propagatedBuildInputs
+declare -a propagatedHostDepFiles=('propagated-host-host-deps' 'propagated-build-inputs' )
+propagatedNativeBuildInputs=''
+export propagatedNativeBuildInputs
+declare -a propagatedTargetDepFiles=('propagated-target-target-deps' )
+shell='/nix/store/ki4if6b0w5bqv8dc5lrjp8xm7wjy9dlf-bash-5.2p37/bin/bash'
+export shell
+shellHook=''
+export shellHook
+stdenv='/nix/store/md93ycspqdq3arrv9ms0m4c33jssizgg-stdenv-linux'
+export stdenv
+strictDeps=''
+export strictDeps
+system='x86_64-linux'
+export system
+declare -a unpackCmdHooks=('_defaultUnpack' )
+_activatePkgs ()
+{
+
+ local hostOffset targetOffset;
+ local pkg;
+ for hostOffset in "${allPlatOffsets[@]}";
+ do
+ local pkgsVar="${pkgAccumVarVars[hostOffset + 1]}";
+ for targetOffset in "${allPlatOffsets[@]}";
+ do
+ (( hostOffset <= targetOffset )) || continue;
+ local pkgsRef="${pkgsVar}[$targetOffset - $hostOffset]";
+ local pkgsSlice="${!pkgsRef}[@]";
+ for pkg in ${!pkgsSlice+"${!pkgsSlice}"};
+ do
+ activatePackage "$pkg" "$hostOffset" "$targetOffset";
+ done;
+ done;
+ done
+}
+_addRpathPrefix ()
+{
+
+ if [ "${NIX_NO_SELF_RPATH:-0}" != 1 ]; then
+ export NIX_LDFLAGS="-rpath $1/lib ${NIX_LDFLAGS-}";
+ fi
+}
+_addToEnv ()
+{
+
+ local depHostOffset depTargetOffset;
+ local pkg;
+ for depHostOffset in "${allPlatOffsets[@]}";
+ do
+ local hookVar="${pkgHookVarVars[depHostOffset + 1]}";
+ local pkgsVar="${pkgAccumVarVars[depHostOffset + 1]}";
+ for depTargetOffset in "${allPlatOffsets[@]}";
+ do
+ (( depHostOffset <= depTargetOffset )) || continue;
+ local hookRef="${hookVar}[$depTargetOffset - $depHostOffset]";
+ if [[ -z "${strictDeps-}" ]]; then
+ local visitedPkgs="";
+ for pkg in "${pkgsBuildBuild[@]}" "${pkgsBuildHost[@]}" "${pkgsBuildTarget[@]}" "${pkgsHostHost[@]}" "${pkgsHostTarget[@]}" "${pkgsTargetTarget[@]}";
+ do
+ if [[ "$visitedPkgs" = *"$pkg"* ]]; then
+ continue;
+ fi;
+ runHook "${!hookRef}" "$pkg";
+ visitedPkgs+=" $pkg";
+ done;
+ else
+ local pkgsRef="${pkgsVar}[$depTargetOffset - $depHostOffset]";
+ local pkgsSlice="${!pkgsRef}[@]";
+ for pkg in ${!pkgsSlice+"${!pkgsSlice}"};
+ do
+ runHook "${!hookRef}" "$pkg";
+ done;
+ fi;
+ done;
+ done
+}
+_allFlags ()
+{
+
+ export system pname name version;
+ while IFS='' read -r varName; do
+ nixTalkativeLog "@${varName}@ -> ${!varName}";
+ args+=("--subst-var" "$varName");
+ done < <(awk 'BEGIN { for (v in ENVIRON) if (v ~ /^[a-z][a-zA-Z0-9_]*$/) print v }')
+}
+_assignFirst ()
+{
+
+ local varName="$1";
+ local _var;
+ local REMOVE=REMOVE;
+ shift;
+ for _var in "$@";
+ do
+ if [ -n "${!_var-}" ]; then
+ eval "${varName}"="${_var}";
+ return;
+ fi;
+ done;
+ echo;
+ echo "error: _assignFirst: could not find a non-empty variable whose name to assign to ${varName}.";
+ echo " The following variables were all unset or empty:";
+ echo " $*";
+ if [ -z "${out:-}" ]; then
+ echo ' If you do not want an "out" output in your derivation, make sure to define';
+ echo ' the other specific required outputs. This can be achieved by picking one';
+ echo " of the above as an output.";
+ echo ' You do not have to remove "out" if you want to have a different default';
+ echo ' output, because the first output is taken as a default.';
+ echo;
+ fi;
+ return 1
+}
+_callImplicitHook ()
+{
+
+ local def="$1";
+ local hookName="$2";
+ if declare -F "$hookName" > /dev/null; then
+ nixTalkativeLog "calling implicit '$hookName' function hook";
+ "$hookName";
+ else
+ if type -p "$hookName" > /dev/null; then
+ nixTalkativeLog "sourcing implicit '$hookName' script hook";
+ source "$hookName";
+ else
+ if [ -n "${!hookName:-}" ]; then
+ nixTalkativeLog "evaling implicit '$hookName' string hook";
+ eval "${!hookName}";
+ else
+ return "$def";
+ fi;
+ fi;
+ fi
+}
+_defaultUnpack ()
+{
+
+ local fn="$1";
+ local destination;
+ if [ -d "$fn" ]; then
+ destination="$(stripHash "$fn")";
+ if [ -e "$destination" ]; then
+ echo "Cannot copy $fn to $destination: destination already exists!";
+ echo "Did you specify two \"srcs\" with the same \"name\"?";
+ return 1;
+ fi;
+ cp -r --preserve=mode,timestamps --reflink=auto -- "$fn" "$destination";
+ else
+ case "$fn" in
+ *.tar.xz | *.tar.lzma | *.txz)
+ ( XZ_OPT="--threads=$NIX_BUILD_CORES" xz -d < "$fn";
+ true ) | tar xf - --mode=+w --warning=no-timestamp
+ ;;
+ *.tar | *.tar.* | *.tgz | *.tbz2 | *.tbz)
+ tar xf "$fn" --mode=+w --warning=no-timestamp
+ ;;
+ *)
+ return 1
+ ;;
+ esac;
+ fi
+}
+_doStrip ()
+{
+
+ local -ra flags=(dontStripHost dontStripTarget);
+ local -ra debugDirs=(stripDebugList stripDebugListTarget);
+ local -ra allDirs=(stripAllList stripAllListTarget);
+ local -ra stripCmds=(STRIP STRIP_FOR_TARGET);
+ local -ra ranlibCmds=(RANLIB RANLIB_FOR_TARGET);
+ stripDebugList=${stripDebugList[*]:-lib lib32 lib64 libexec bin sbin Applications Library/Frameworks};
+ stripDebugListTarget=${stripDebugListTarget[*]:-};
+ stripAllList=${stripAllList[*]:-};
+ stripAllListTarget=${stripAllListTarget[*]:-};
+ local i;
+ for i in ${!stripCmds[@]};
+ do
+ local -n flag="${flags[$i]}";
+ local -n debugDirList="${debugDirs[$i]}";
+ local -n allDirList="${allDirs[$i]}";
+ local -n stripCmd="${stripCmds[$i]}";
+ local -n ranlibCmd="${ranlibCmds[$i]}";
+ if [[ -n "${dontStrip-}" || -n "${flag-}" ]] || ! type -f "${stripCmd-}" 2> /dev/null 1>&2; then
+ continue;
+ fi;
+ stripDirs "$stripCmd" "$ranlibCmd" "$debugDirList" "${stripDebugFlags[*]:--S -p}";
+ stripDirs "$stripCmd" "$ranlibCmd" "$allDirList" "${stripAllFlags[*]:--s -p}";
+ done
+}
+_eval ()
+{
+
+ if declare -F "$1" > /dev/null 2>&1; then
+ "$@";
+ else
+ eval "$1";
+ fi
+}
+_logHook ()
+{
+
+ if [[ -z ${NIX_LOG_FD-} ]]; then
+ return;
+ fi;
+ local hookKind="$1";
+ local hookExpr="$2";
+ shift 2;
+ if declare -F "$hookExpr" > /dev/null 2>&1; then
+ nixTalkativeLog "calling '$hookKind' function hook '$hookExpr'" "$@";
+ else
+ if type -p "$hookExpr" > /dev/null; then
+ nixTalkativeLog "sourcing '$hookKind' script hook '$hookExpr'";
+ else
+ if [[ "$hookExpr" != "_callImplicitHook"* ]]; then
+ local exprToOutput;
+ if [[ ${NIX_DEBUG:-0} -ge 5 ]]; then
+ exprToOutput="$hookExpr";
+ else
+ local hookExprLine;
+ while IFS= read -r hookExprLine; do
+ hookExprLine="${hookExprLine#"${hookExprLine%%[![:space:]]*}"}";
+ if [[ -n "$hookExprLine" ]]; then
+ exprToOutput+="$hookExprLine\\n ";
+ fi;
+ done <<< "$hookExpr";
+ exprToOutput="${exprToOutput%%\\n }";
+ fi;
+ nixTalkativeLog "evaling '$hookKind' string hook '$exprToOutput'";
+ fi;
+ fi;
+ fi
+}
+_makeSymlinksRelative ()
+{
+
+ local symlinkTarget;
+ if [ "${dontRewriteSymlinks-}" ] || [ ! -e "$prefix" ]; then
+ return;
+ fi;
+ while IFS= read -r -d '' f; do
+ symlinkTarget=$(readlink "$f");
+ if [[ "$symlinkTarget"/ != "$prefix"/* ]]; then
+ continue;
+ fi;
+ if [ ! -e "$symlinkTarget" ]; then
+ echo "the symlink $f is broken, it points to $symlinkTarget (which is missing)";
+ fi;
+ echo "rewriting symlink $f to be relative to $prefix";
+ ln -snrf "$symlinkTarget" "$f";
+ done < <(find $prefix -type l -print0)
+}
+_makeSymlinksRelativeInAllOutputs ()
+{
+
+ local output;
+ for output in $(getAllOutputNames);
+ do
+ prefix="${!output}" _makeSymlinksRelative;
+ done
+}
+_moveLib64 ()
+{
+
+ if [ "${dontMoveLib64-}" = 1 ]; then
+ return;
+ fi;
+ if [ ! -e "$prefix/lib64" -o -L "$prefix/lib64" ]; then
+ return;
+ fi;
+ echo "moving $prefix/lib64/* to $prefix/lib";
+ mkdir -p $prefix/lib;
+ shopt -s dotglob;
+ for i in $prefix/lib64/*;
+ do
+ mv --no-clobber "$i" $prefix/lib;
+ done;
+ shopt -u dotglob;
+ rmdir $prefix/lib64;
+ ln -s lib $prefix/lib64
+}
+_moveSbin ()
+{
+
+ if [ "${dontMoveSbin-}" = 1 ]; then
+ return;
+ fi;
+ if [ ! -e "$prefix/sbin" -o -L "$prefix/sbin" ]; then
+ return;
+ fi;
+ echo "moving $prefix/sbin/* to $prefix/bin";
+ mkdir -p $prefix/bin;
+ shopt -s dotglob;
+ for i in $prefix/sbin/*;
+ do
+ mv "$i" $prefix/bin;
+ done;
+ shopt -u dotglob;
+ rmdir $prefix/sbin;
+ ln -s bin $prefix/sbin
+}
+_moveSystemdUserUnits ()
+{
+
+ if [ "${dontMoveSystemdUserUnits:-0}" = 1 ]; then
+ return;
+ fi;
+ if [ ! -e "${prefix:?}/lib/systemd/user" ]; then
+ return;
+ fi;
+ local source="$prefix/lib/systemd/user";
+ local target="$prefix/share/systemd/user";
+ echo "moving $source/* to $target";
+ mkdir -p "$target";
+ ( shopt -s dotglob;
+ for i in "$source"/*;
+ do
+ mv "$i" "$target";
+ done );
+ rmdir "$source";
+ ln -s "$target" "$source"
+}
+_moveToShare ()
+{
+
+ if [ -n "$__structuredAttrs" ]; then
+ if [ -z "${forceShare-}" ]; then
+ forceShare=(man doc info);
+ fi;
+ else
+ forceShare=(${forceShare:-man doc info});
+ fi;
+ if [[ -z "$out" ]]; then
+ return;
+ fi;
+ for d in "${forceShare[@]}";
+ do
+ if [ -d "$out/$d" ]; then
+ if [ -d "$out/share/$d" ]; then
+ echo "both $d/ and share/$d/ exist!";
+ else
+ echo "moving $out/$d to $out/share/$d";
+ mkdir -p $out/share;
+ mv $out/$d $out/share/;
+ fi;
+ fi;
+ done
+}
+_multioutConfig ()
+{
+
+ if [ "$(getAllOutputNames)" = "out" ] || [ -z "${setOutputFlags-1}" ]; then
+ return;
+ fi;
+ if [ -z "${shareDocName:-}" ]; then
+ local confScript="${configureScript:-}";
+ if [ -z "$confScript" ] && [ -x ./configure ]; then
+ confScript=./configure;
+ fi;
+ if [ -f "$confScript" ]; then
+ local shareDocName="$(sed -n "s/^PACKAGE_TARNAME='\(.*\)'$/\1/p" < "$confScript")";
+ fi;
+ if [ -z "$shareDocName" ] || echo "$shareDocName" | grep -q '[^a-zA-Z0-9_-]'; then
+ shareDocName="$(echo "$name" | sed 's/-[^a-zA-Z].*//')";
+ fi;
+ fi;
+ prependToVar configureFlags --bindir="${!outputBin}"/bin --sbindir="${!outputBin}"/sbin --includedir="${!outputInclude}"/include --mandir="${!outputMan}"/share/man --infodir="${!outputInfo}"/share/info --docdir="${!outputDoc}"/share/doc/"${shareDocName}" --libdir="${!outputLib}"/lib --libexecdir="${!outputLib}"/libexec --localedir="${!outputLib}"/share/locale;
+ prependToVar installFlags pkgconfigdir="${!outputDev}"/lib/pkgconfig m4datadir="${!outputDev}"/share/aclocal aclocaldir="${!outputDev}"/share/aclocal
+}
+_multioutDevs ()
+{
+
+ if [ "$(getAllOutputNames)" = "out" ] || [ -z "${moveToDev-1}" ]; then
+ return;
+ fi;
+ moveToOutput include "${!outputInclude}";
+ moveToOutput lib/pkgconfig "${!outputDev}";
+ moveToOutput share/pkgconfig "${!outputDev}";
+ moveToOutput lib/cmake "${!outputDev}";
+ moveToOutput share/aclocal "${!outputDev}";
+ for f in "${!outputDev}"/{lib,share}/pkgconfig/*.pc;
+ do
+ echo "Patching '$f' includedir to output ${!outputInclude}";
+ sed -i "/^includedir=/s,=\${prefix},=${!outputInclude}," "$f";
+ done
+}
+_multioutDocs ()
+{
+
+ local REMOVE=REMOVE;
+ moveToOutput share/info "${!outputInfo}";
+ moveToOutput share/doc "${!outputDoc}";
+ moveToOutput share/gtk-doc "${!outputDevdoc}";
+ moveToOutput share/devhelp/books "${!outputDevdoc}";
+ moveToOutput share/man "${!outputMan}";
+ moveToOutput share/man/man3 "${!outputDevman}"
+}
+_multioutPropagateDev ()
+{
+
+ if [ "$(getAllOutputNames)" = "out" ]; then
+ return;
+ fi;
+ local outputFirst;
+ for outputFirst in $(getAllOutputNames);
+ do
+ break;
+ done;
+ local propagaterOutput="$outputDev";
+ if [ -z "$propagaterOutput" ]; then
+ propagaterOutput="$outputFirst";
+ fi;
+ if [ -z "${propagatedBuildOutputs+1}" ]; then
+ local po_dirty="$outputBin $outputInclude $outputLib";
+ set +o pipefail;
+ propagatedBuildOutputs=`echo "$po_dirty" | tr -s ' ' '\n' | grep -v -F "$propagaterOutput" | sort -u | tr '\n' ' ' `;
+ set -o pipefail;
+ fi;
+ if [ -z "$propagatedBuildOutputs" ]; then
+ return;
+ fi;
+ mkdir -p "${!propagaterOutput}"/nix-support;
+ for output in $propagatedBuildOutputs;
+ do
+ echo -n " ${!output}" >> "${!propagaterOutput}"/nix-support/propagated-build-inputs;
+ done
+}
+_nixLogWithLevel ()
+{
+
+ [[ -z ${NIX_LOG_FD-} || ${NIX_DEBUG:-0} -lt ${1:?} ]] && return 0;
+ local logLevel;
+ case "${1:?}" in
+ 0)
+ logLevel=ERROR
+ ;;
+ 1)
+ logLevel=WARN
+ ;;
+ 2)
+ logLevel=NOTICE
+ ;;
+ 3)
+ logLevel=INFO
+ ;;
+ 4)
+ logLevel=TALKATIVE
+ ;;
+ 5)
+ logLevel=CHATTY
+ ;;
+ 6)
+ logLevel=DEBUG
+ ;;
+ 7)
+ logLevel=VOMIT
+ ;;
+ *)
+ echo "_nixLogWithLevel: called with invalid log level: ${1:?}" >&"$NIX_LOG_FD";
+ return 1
+ ;;
+ esac;
+ local callerName="${FUNCNAME[2]}";
+ if [[ $callerName == "_callImplicitHook" ]]; then
+ callerName="${hookName:?}";
+ fi;
+ printf "%s: %s: %s\n" "$logLevel" "$callerName" "${2:?}" >&"$NIX_LOG_FD"
+}
+_overrideFirst ()
+{
+
+ if [ -z "${!1-}" ]; then
+ _assignFirst "$@";
+ fi
+}
+_pruneLibtoolFiles ()
+{
+
+ if [ "${dontPruneLibtoolFiles-}" ] || [ ! -e "$prefix" ]; then
+ return;
+ fi;
+ find "$prefix" -type f -name '*.la' -exec grep -q '^# Generated by .*libtool' {} \; -exec grep -q "^old_library=''" {} \; -exec sed -i {} -e "/^dependency_libs='[^']/ c dependency_libs='' #pruned" \;
+}
+_updateSourceDateEpochFromSourceRoot ()
+{
+
+ if [ -n "$sourceRoot" ]; then
+ updateSourceDateEpoch "$sourceRoot";
+ fi
+}
+activatePackage ()
+{
+
+ local pkg="$1";
+ local -r hostOffset="$2";
+ local -r targetOffset="$3";
+ (( hostOffset <= targetOffset )) || exit 1;
+ if [ -f "$pkg" ]; then
+ nixTalkativeLog "sourcing setup hook '$pkg'";
+ source "$pkg";
+ fi;
+ if [[ -z "${strictDeps-}" || "$hostOffset" -le -1 ]]; then
+ addToSearchPath _PATH "$pkg/bin";
+ fi;
+ if (( hostOffset <= -1 )); then
+ addToSearchPath _XDG_DATA_DIRS "$pkg/share";
+ fi;
+ if [[ "$hostOffset" -eq 0 && -d "$pkg/bin" ]]; then
+ addToSearchPath _HOST_PATH "$pkg/bin";
+ fi;
+ if [[ -f "$pkg/nix-support/setup-hook" ]]; then
+ nixTalkativeLog "sourcing setup hook '$pkg/nix-support/setup-hook'";
+ source "$pkg/nix-support/setup-hook";
+ fi
+}
+addEnvHooks ()
+{
+
+ local depHostOffset="$1";
+ shift;
+ local pkgHookVarsSlice="${pkgHookVarVars[$depHostOffset + 1]}[@]";
+ local pkgHookVar;
+ for pkgHookVar in "${!pkgHookVarsSlice}";
+ do
+ eval "${pkgHookVar}s"'+=("$@")';
+ done
+}
+addNodePath ()
+{
+
+ addToSearchPath NODE_PATH "$1/lib/node_modules"
+}
+addToSearchPath ()
+{
+
+ addToSearchPathWithCustomDelimiter ":" "$@"
+}
+addToSearchPathWithCustomDelimiter ()
+{
+
+ local delimiter="$1";
+ local varName="$2";
+ local dir="$3";
+ if [[ -d "$dir" && "${!varName:+${delimiter}${!varName}${delimiter}}" != *"${delimiter}${dir}${delimiter}"* ]]; then
+ export "${varName}=${!varName:+${!varName}${delimiter}}${dir}";
+ fi
+}
+appendToVar ()
+{
+
+ local -n nameref="$1";
+ local useArray type;
+ if [ -n "$__structuredAttrs" ]; then
+ useArray=true;
+ else
+ useArray=false;
+ fi;
+ if type=$(declare -p "$1" 2> /dev/null); then
+ case "${type#* }" in
+ -A*)
+ echo "appendToVar(): ERROR: trying to use appendToVar on an associative array, use variable+=([\"X\"]=\"Y\") instead." 1>&2;
+ return 1
+ ;;
+ -a*)
+ useArray=true
+ ;;
+ *)
+ useArray=false
+ ;;
+ esac;
+ fi;
+ shift;
+ if $useArray; then
+ nameref=(${nameref+"${nameref[@]}"} "$@");
+ else
+ nameref="${nameref-} $*";
+ fi
+}
+auditTmpdir ()
+{
+
+ local dir="$1";
+ [ -e "$dir" ] || return 0;
+ echo "checking for references to $TMPDIR/ in $dir...";
+ local i;
+ find "$dir" -type f -print0 | while IFS= read -r -d '' i; do
+ if [[ "$i" =~ .build-id ]]; then
+ continue;
+ fi;
+ if isELF "$i"; then
+ if {
+ printf :;
+ patchelf --print-rpath "$i"
+ } | grep -q -F ":$TMPDIR/"; then
+ echo "RPATH of binary $i contains a forbidden reference to $TMPDIR/";
+ exit 1;
+ fi;
+ fi;
+ if isScript "$i"; then
+ if [ -e "$(dirname "$i")/.$(basename "$i")-wrapped" ]; then
+ if grep -q -F "$TMPDIR/" "$i"; then
+ echo "wrapper script $i contains a forbidden reference to $TMPDIR/";
+ exit 1;
+ fi;
+ fi;
+ fi;
+ done
+}
+bintoolsWrapper_addLDVars ()
+{
+
+ local role_post;
+ getHostRoleEnvHook;
+ if [[ -d "$1/lib64" && ! -L "$1/lib64" ]]; then
+ export NIX_LDFLAGS${role_post}+=" -L$1/lib64";
+ fi;
+ if [[ -d "$1/lib" ]]; then
+ local -a glob=($1/lib/lib*);
+ if [ "${#glob[*]}" -gt 0 ]; then
+ export NIX_LDFLAGS${role_post}+=" -L$1/lib";
+ fi;
+ fi
+}
+buildPhase ()
+{
+
+ runHook preBuild;
+ if [[ -z "${makeFlags-}" && -z "${makefile:-}" && ! ( -e Makefile || -e makefile || -e GNUmakefile ) ]]; then
+ echo "no Makefile or custom buildPhase, doing nothing";
+ else
+ foundMakefile=1;
+ local flagsArray=(${enableParallelBuilding:+-j${NIX_BUILD_CORES}} SHELL="$SHELL");
+ concatTo flagsArray makeFlags makeFlagsArray buildFlags buildFlagsArray;
+ echoCmd 'build flags' "${flagsArray[@]}";
+ make ${makefile:+-f $makefile} "${flagsArray[@]}";
+ unset flagsArray;
+ fi;
+ runHook postBuild
+}
+ccWrapper_addCVars ()
+{
+
+ local role_post;
+ getHostRoleEnvHook;
+ if [ -d "$1/include" ]; then
+ export NIX_CFLAGS_COMPILE${role_post}+=" -isystem $1/include";
+ fi;
+ if [ -d "$1/Library/Frameworks" ]; then
+ export NIX_CFLAGS_COMPILE${role_post}+=" -iframework $1/Library/Frameworks";
+ fi
+}
+checkPhase ()
+{
+
+ runHook preCheck;
+ if [[ -z "${foundMakefile:-}" ]]; then
+ echo "no Makefile or custom checkPhase, doing nothing";
+ runHook postCheck;
+ return;
+ fi;
+ if [[ -z "${checkTarget:-}" ]]; then
+ if make -n ${makefile:+-f $makefile} check > /dev/null 2>&1; then
+ checkTarget="check";
+ else
+ if make -n ${makefile:+-f $makefile} test > /dev/null 2>&1; then
+ checkTarget="test";
+ fi;
+ fi;
+ fi;
+ if [[ -z "${checkTarget:-}" ]]; then
+ echo "no check/test target in ${makefile:-Makefile}, doing nothing";
+ else
+ local flagsArray=(${enableParallelChecking:+-j${NIX_BUILD_CORES}} SHELL="$SHELL");
+ concatTo flagsArray makeFlags makeFlagsArray checkFlags=VERBOSE=y checkFlagsArray checkTarget;
+ echoCmd 'check flags' "${flagsArray[@]}";
+ make ${makefile:+-f $makefile} "${flagsArray[@]}";
+ unset flagsArray;
+ fi;
+ runHook postCheck
+}
+compressManPages ()
+{
+
+ local dir="$1";
+ if [ -L "$dir"/share ] || [ -L "$dir"/share/man ] || [ ! -d "$dir/share/man" ]; then
+ return;
+ fi;
+ echo "gzipping man pages under $dir/share/man/";
+ find "$dir"/share/man/ -type f -a '!' -regex '.*\.\(bz2\|gz\|xz\)$' -print0 | while IFS= read -r -d '' f; do
+ if gzip -c -n "$f" > "$f".gz; then
+ rm "$f";
+ else
+ rm "$f".gz;
+ fi;
+ done;
+ find "$dir"/share/man/ -type l -a '!' -regex '.*\.\(bz2\|gz\|xz\)$' -print0 | sort -z | while IFS= read -r -d '' f; do
+ local target;
+ target="$(readlink -f "$f")";
+ if [ -f "$target".gz ]; then
+ ln -sf "$target".gz "$f".gz && rm "$f";
+ fi;
+ done
+}
+concatStringsSep ()
+{
+
+ local sep="$1";
+ local name="$2";
+ local type oldifs;
+ if type=$(declare -p "$name" 2> /dev/null); then
+ local -n nameref="$name";
+ case "${type#* }" in
+ -A*)
+ echo "concatStringsSep(): ERROR: trying to use concatStringsSep on an associative array." 1>&2;
+ return 1
+ ;;
+ -a*)
+ local IFS="$(printf '\036')"
+ ;;
+ *)
+ local IFS=" "
+ ;;
+ esac;
+ local ifs_separated="${nameref[*]}";
+ echo -n "${ifs_separated//"$IFS"/"$sep"}";
+ fi
+}
+concatTo ()
+{
+
+ local -;
+ set -o noglob;
+ local -n targetref="$1";
+ shift;
+ local arg default name type;
+ for arg in "$@";
+ do
+ IFS="=" read -r name default <<< "$arg";
+ local -n nameref="$name";
+ if [[ -z "${nameref[*]}" && -n "$default" ]]; then
+ targetref+=("$default");
+ else
+ if type=$(declare -p "$name" 2> /dev/null); then
+ case "${type#* }" in
+ -A*)
+ echo "concatTo(): ERROR: trying to use concatTo on an associative array." 1>&2;
+ return 1
+ ;;
+ -a*)
+ targetref+=("${nameref[@]}")
+ ;;
+ *)
+ if [[ "$name" = *"Array" ]]; then
+ nixErrorLog "concatTo(): $name is not declared as array, treating as a singleton. This will become an error in future";
+ targetref+=(${nameref+"${nameref[@]}"});
+ else
+ targetref+=(${nameref-});
+ fi
+ ;;
+ esac;
+ fi;
+ fi;
+ done
+}
+configurePhase ()
+{
+
+ runHook preConfigure;
+ : "${configureScript=}";
+ if [[ -z "$configureScript" && -x ./configure ]]; then
+ configureScript=./configure;
+ fi;
+ if [ -z "${dontFixLibtool:-}" ]; then
+ export lt_cv_deplibs_check_method="${lt_cv_deplibs_check_method-pass_all}";
+ local i;
+ find . -iname "ltmain.sh" -print0 | while IFS='' read -r -d '' i; do
+ echo "fixing libtool script $i";
+ fixLibtool "$i";
+ done;
+ CONFIGURE_MTIME_REFERENCE=$(mktemp configure.mtime.reference.XXXXXX);
+ find . -executable -type f -name configure -exec grep -l 'GNU Libtool is free software; you can redistribute it and/or modify' {} \; -exec touch -r {} "$CONFIGURE_MTIME_REFERENCE" \; -exec sed -i s_/usr/bin/file_file_g {} \; -exec touch -r "$CONFIGURE_MTIME_REFERENCE" {} \;;
+ rm -f "$CONFIGURE_MTIME_REFERENCE";
+ fi;
+ if [[ -z "${dontAddPrefix:-}" && -n "$prefix" ]]; then
+ prependToVar configureFlags "${prefixKey:---prefix=}$prefix";
+ fi;
+ if [[ -f "$configureScript" ]]; then
+ if [ -z "${dontAddDisableDepTrack:-}" ]; then
+ if grep -q dependency-tracking "$configureScript"; then
+ prependToVar configureFlags --disable-dependency-tracking;
+ fi;
+ fi;
+ if [ -z "${dontDisableStatic:-}" ]; then
+ if grep -q enable-static "$configureScript"; then
+ prependToVar configureFlags --disable-static;
+ fi;
+ fi;
+ if [ -z "${dontPatchShebangsInConfigure:-}" ]; then
+ patchShebangs --build "$configureScript";
+ fi;
+ fi;
+ if [ -n "$configureScript" ]; then
+ local -a flagsArray;
+ concatTo flagsArray configureFlags configureFlagsArray;
+ echoCmd 'configure flags' "${flagsArray[@]}";
+ $configureScript "${flagsArray[@]}";
+ unset flagsArray;
+ else
+ echo "no configure script, doing nothing";
+ fi;
+ runHook postConfigure
+}
+consumeEntire ()
+{
+
+ if IFS='' read -r -d '' "$1"; then
+ echo "consumeEntire(): ERROR: Input null bytes, won't process" 1>&2;
+ return 1;
+ fi
+}
+distPhase ()
+{
+
+ runHook preDist;
+ local flagsArray=();
+ concatTo flagsArray distFlags distFlagsArray distTarget=dist;
+ echo 'dist flags: %q' "${flagsArray[@]}";
+ make ${makefile:+-f $makefile} "${flagsArray[@]}";
+ if [ "${dontCopyDist:-0}" != 1 ]; then
+ mkdir -p "$out/tarballs";
+ cp -pvd ${tarballs[*]:-*.tar.gz} "$out/tarballs";
+ fi;
+ runHook postDist
+}
+dumpVars ()
+{
+
+ if [ "${noDumpEnvVars:-0}" != 1 ]; then
+ {
+ install -m 0600 /dev/null "$NIX_BUILD_TOP/env-vars" && export 2> /dev/null >| "$NIX_BUILD_TOP/env-vars"
+ } || true;
+ fi
+}
+echoCmd ()
+{
+
+ printf "%s:" "$1";
+ shift;
+ printf ' %q' "$@";
+ echo
+}
+exitHandler ()
+{
+
+ exitCode="$?";
+ set +e;
+ if [ -n "${showBuildStats:-}" ]; then
+ read -r -d '' -a buildTimes < <(times);
+ echo "build times:";
+ echo "user time for the shell ${buildTimes[0]}";
+ echo "system time for the shell ${buildTimes[1]}";
+ echo "user time for all child processes ${buildTimes[2]}";
+ echo "system time for all child processes ${buildTimes[3]}";
+ fi;
+ if (( "$exitCode" != 0 )); then
+ runHook failureHook;
+ if [ -n "${succeedOnFailure:-}" ]; then
+ echo "build failed with exit code $exitCode (ignored)";
+ mkdir -p "$out/nix-support";
+ printf "%s" "$exitCode" > "$out/nix-support/failed";
+ exit 0;
+ fi;
+ else
+ runHook exitHook;
+ fi;
+ return "$exitCode"
+}
+findInputs ()
+{
+
+ local -r pkg="$1";
+ local -r hostOffset="$2";
+ local -r targetOffset="$3";
+ (( hostOffset <= targetOffset )) || exit 1;
+ local varVar="${pkgAccumVarVars[hostOffset + 1]}";
+ local varRef="$varVar[$((targetOffset - hostOffset))]";
+ local var="${!varRef}";
+ unset -v varVar varRef;
+ local varSlice="$var[*]";
+ case " ${!varSlice-} " in
+ *" $pkg "*)
+ return 0
+ ;;
+ esac;
+ unset -v varSlice;
+ eval "$var"'+=("$pkg")';
+ if ! [ -e "$pkg" ]; then
+ echo "build input $pkg does not exist" 1>&2;
+ exit 1;
+ fi;
+ function mapOffset ()
+ {
+ local -r inputOffset="$1";
+ local -n outputOffset="$2";
+ if (( inputOffset <= 0 )); then
+ outputOffset=$((inputOffset + hostOffset));
+ else
+ outputOffset=$((inputOffset - 1 + targetOffset));
+ fi
+ };
+ local relHostOffset;
+ for relHostOffset in "${allPlatOffsets[@]}";
+ do
+ local files="${propagatedDepFilesVars[relHostOffset + 1]}";
+ local hostOffsetNext;
+ mapOffset "$relHostOffset" hostOffsetNext;
+ (( -1 <= hostOffsetNext && hostOffsetNext <= 1 )) || continue;
+ local relTargetOffset;
+ for relTargetOffset in "${allPlatOffsets[@]}";
+ do
+ (( "$relHostOffset" <= "$relTargetOffset" )) || continue;
+ local fileRef="${files}[$relTargetOffset - $relHostOffset]";
+ local file="${!fileRef}";
+ unset -v fileRef;
+ local targetOffsetNext;
+ mapOffset "$relTargetOffset" targetOffsetNext;
+ (( -1 <= hostOffsetNext && hostOffsetNext <= 1 )) || continue;
+ [[ -f "$pkg/nix-support/$file" ]] || continue;
+ local pkgNext;
+ read -r -d '' pkgNext < "$pkg/nix-support/$file" || true;
+ for pkgNext in $pkgNext;
+ do
+ findInputs "$pkgNext" "$hostOffsetNext" "$targetOffsetNext";
+ done;
+ done;
+ done
+}
+fixLibtool ()
+{
+
+ local search_path;
+ for flag in $NIX_LDFLAGS;
+ do
+ case $flag in
+ -L*)
+ search_path+=" ${flag#-L}"
+ ;;
+ esac;
+ done;
+ sed -i "$1" -e "s^eval \(sys_lib_search_path=\).*^\1'${search_path:-}'^" -e 's^eval sys_lib_.+search_path=.*^^'
+}
+fixupPhase ()
+{
+
+ local output;
+ for output in $(getAllOutputNames);
+ do
+ if [ -e "${!output}" ]; then
+ chmod -R u+w,u-s,g-s "${!output}";
+ fi;
+ done;
+ runHook preFixup;
+ local output;
+ for output in $(getAllOutputNames);
+ do
+ prefix="${!output}" runHook fixupOutput;
+ done;
+ recordPropagatedDependencies;
+ if [ -n "${setupHook:-}" ]; then
+ mkdir -p "${!outputDev}/nix-support";
+ substituteAll "$setupHook" "${!outputDev}/nix-support/setup-hook";
+ fi;
+ if [ -n "${setupHooks:-}" ]; then
+ mkdir -p "${!outputDev}/nix-support";
+ local hook;
+ for hook in ${setupHooks[@]};
+ do
+ local content;
+ consumeEntire content < "$hook";
+ substituteAllStream content "file '$hook'" >> "${!outputDev}/nix-support/setup-hook";
+ unset -v content;
+ done;
+ unset -v hook;
+ fi;
+ if [ -n "${propagatedUserEnvPkgs:-}" ]; then
+ mkdir -p "${!outputBin}/nix-support";
+ printWords $propagatedUserEnvPkgs > "${!outputBin}/nix-support/propagated-user-env-packages";
+ fi;
+ runHook postFixup
+}
+genericBuild ()
+{
+
+ export GZIP_NO_TIMESTAMPS=1;
+ if [ -f "${buildCommandPath:-}" ]; then
+ source "$buildCommandPath";
+ return;
+ fi;
+ if [ -n "${buildCommand:-}" ]; then
+ eval "$buildCommand";
+ return;
+ fi;
+ if [ -z "${phases[*]:-}" ]; then
+ phases="${prePhases[*]:-} unpackPhase patchPhase ${preConfigurePhases[*]:-} configurePhase ${preBuildPhases[*]:-} buildPhase checkPhase ${preInstallPhases[*]:-} installPhase ${preFixupPhases[*]:-} fixupPhase installCheckPhase ${preDistPhases[*]:-} distPhase ${postPhases[*]:-}";
+ fi;
+ for curPhase in ${phases[*]};
+ do
+ runPhase "$curPhase";
+ done
+}
+getAllOutputNames ()
+{
+
+ if [ -n "$__structuredAttrs" ]; then
+ echo "${!outputs[*]}";
+ else
+ echo "$outputs";
+ fi
+}
+getHostRole ()
+{
+
+ getRole "$hostOffset"
+}
+getHostRoleEnvHook ()
+{
+
+ getRole "$depHostOffset"
+}
+getRole ()
+{
+
+ case $1 in
+ -1)
+ role_post='_FOR_BUILD'
+ ;;
+ 0)
+ role_post=''
+ ;;
+ 1)
+ role_post='_FOR_TARGET'
+ ;;
+ *)
+ echo "binutils-wrapper-2.43.1: used as improper sort of dependency" 1>&2;
+ return 1
+ ;;
+ esac
+}
+getTargetRole ()
+{
+
+ getRole "$targetOffset"
+}
+getTargetRoleEnvHook ()
+{
+
+ getRole "$depTargetOffset"
+}
+getTargetRoleWrapper ()
+{
+
+ case $targetOffset in
+ -1)
+ export NIX_BINTOOLS_WRAPPER_TARGET_BUILD_x86_64_unknown_linux_gnu=1
+ ;;
+ 0)
+ export NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu=1
+ ;;
+ 1)
+ export NIX_BINTOOLS_WRAPPER_TARGET_TARGET_x86_64_unknown_linux_gnu=1
+ ;;
+ *)
+ echo "binutils-wrapper-2.43.1: used as improper sort of dependency" 1>&2;
+ return 1
+ ;;
+ esac
+}
+installCheckPhase ()
+{
+
+ runHook preInstallCheck;
+ if [[ -z "${foundMakefile:-}" ]]; then
+ echo "no Makefile or custom installCheckPhase, doing nothing";
+ else
+ if [[ -z "${installCheckTarget:-}" ]] && ! make -n ${makefile:+-f $makefile} "${installCheckTarget:-installcheck}" > /dev/null 2>&1; then
+ echo "no installcheck target in ${makefile:-Makefile}, doing nothing";
+ else
+ local flagsArray=(${enableParallelChecking:+-j${NIX_BUILD_CORES}} SHELL="$SHELL");
+ concatTo flagsArray makeFlags makeFlagsArray installCheckFlags installCheckFlagsArray installCheckTarget=installcheck;
+ echoCmd 'installcheck flags' "${flagsArray[@]}";
+ make ${makefile:+-f $makefile} "${flagsArray[@]}";
+ unset flagsArray;
+ fi;
+ fi;
+ runHook postInstallCheck
+}
+installPhase ()
+{
+
+ runHook preInstall;
+ if [[ -z "${makeFlags-}" && -z "${makefile:-}" && ! ( -e Makefile || -e makefile || -e GNUmakefile ) ]]; then
+ echo "no Makefile or custom installPhase, doing nothing";
+ runHook postInstall;
+ return;
+ else
+ foundMakefile=1;
+ fi;
+ if [ -n "$prefix" ]; then
+ mkdir -p "$prefix";
+ fi;
+ local flagsArray=(${enableParallelInstalling:+-j${NIX_BUILD_CORES}} SHELL="$SHELL");
+ concatTo flagsArray makeFlags makeFlagsArray installFlags installFlagsArray installTargets=install;
+ echoCmd 'install flags' "${flagsArray[@]}";
+ make ${makefile:+-f $makefile} "${flagsArray[@]}";
+ unset flagsArray;
+ runHook postInstall
+}
+isELF ()
+{
+
+ local fn="$1";
+ local fd;
+ local magic;
+ exec {fd}< "$fn";
+ read -r -n 4 -u "$fd" magic;
+ exec {fd}>&-;
+ if [ "$magic" = 'ELF' ]; then
+ return 0;
+ else
+ return 1;
+ fi
+}
+isMachO ()
+{
+
+ local fn="$1";
+ local fd;
+ local magic;
+ exec {fd}< "$fn";
+ read -r -n 4 -u "$fd" magic;
+ exec {fd}>&-;
+ if [[ "$magic" = $(echo -ne "\xfe\xed\xfa\xcf") || "$magic" = $(echo -ne "\xcf\xfa\xed\xfe") ]]; then
+ return 0;
+ else
+ if [[ "$magic" = $(echo -ne "\xfe\xed\xfa\xce") || "$magic" = $(echo -ne "\xce\xfa\xed\xfe") ]]; then
+ return 0;
+ else
+ if [[ "$magic" = $(echo -ne "\xca\xfe\xba\xbe") || "$magic" = $(echo -ne "\xbe\xba\xfe\xca") ]]; then
+ return 0;
+ else
+ return 1;
+ fi;
+ fi;
+ fi
+}
+isScript ()
+{
+
+ local fn="$1";
+ local fd;
+ local magic;
+ exec {fd}< "$fn";
+ read -r -n 2 -u "$fd" magic;
+ exec {fd}>&-;
+ if [[ "$magic" =~ \#! ]]; then
+ return 0;
+ else
+ return 1;
+ fi
+}
+mapOffset ()
+{
+
+ local -r inputOffset="$1";
+ local -n outputOffset="$2";
+ if (( inputOffset <= 0 )); then
+ outputOffset=$((inputOffset + hostOffset));
+ else
+ outputOffset=$((inputOffset - 1 + targetOffset));
+ fi
+}
+moveToOutput ()
+{
+
+ local patt="$1";
+ local dstOut="$2";
+ local output;
+ for output in $(getAllOutputNames);
+ do
+ if [ "${!output}" = "$dstOut" ]; then
+ continue;
+ fi;
+ local srcPath;
+ for srcPath in "${!output}"/$patt;
+ do
+ if [ ! -e "$srcPath" ] && [ ! -L "$srcPath" ]; then
+ continue;
+ fi;
+ if [ "$dstOut" = REMOVE ]; then
+ echo "Removing $srcPath";
+ rm -r "$srcPath";
+ else
+ local dstPath="$dstOut${srcPath#${!output}}";
+ echo "Moving $srcPath to $dstPath";
+ if [ -d "$dstPath" ] && [ -d "$srcPath" ]; then
+ rmdir "$srcPath" --ignore-fail-on-non-empty;
+ if [ -d "$srcPath" ]; then
+ mv -t "$dstPath" "$srcPath"/*;
+ rmdir "$srcPath";
+ fi;
+ else
+ mkdir -p "$(readlink -m "$dstPath/..")";
+ mv "$srcPath" "$dstPath";
+ fi;
+ fi;
+ local srcParent="$(readlink -m "$srcPath/..")";
+ if [ -n "$(find "$srcParent" -maxdepth 0 -type d -empty 2> /dev/null)" ]; then
+ echo "Removing empty $srcParent/ and (possibly) its parents";
+ rmdir -p --ignore-fail-on-non-empty "$srcParent" 2> /dev/null || true;
+ fi;
+ done;
+ done
+}
+nixChattyLog ()
+{
+
+ _nixLogWithLevel 5 "$*"
+}
+nixDebugLog ()
+{
+
+ _nixLogWithLevel 6 "$*"
+}
+nixErrorLog ()
+{
+
+ _nixLogWithLevel 0 "$*"
+}
+nixInfoLog ()
+{
+
+ _nixLogWithLevel 3 "$*"
+}
+nixLog ()
+{
+
+ [[ -z ${NIX_LOG_FD-} ]] && return 0;
+ local callerName="${FUNCNAME[1]}";
+ if [[ $callerName == "_callImplicitHook" ]]; then
+ callerName="${hookName:?}";
+ fi;
+ printf "%s: %s\n" "$callerName" "$*" >&"$NIX_LOG_FD"
+}
+nixNoticeLog ()
+{
+
+ _nixLogWithLevel 2 "$*"
+}
+nixTalkativeLog ()
+{
+
+ _nixLogWithLevel 4 "$*"
+}
+nixVomitLog ()
+{
+
+ _nixLogWithLevel 7 "$*"
+}
+nixWarnLog ()
+{
+
+ _nixLogWithLevel 1 "$*"
+}
+noBrokenSymlinks ()
+{
+
+ local -r output="${1:?}";
+ local path;
+ local pathParent;
+ local symlinkTarget;
+ local -i numDanglingSymlinks=0;
+ local -i numReflexiveSymlinks=0;
+ if [[ ! -e $output ]]; then
+ nixWarnLog "skipping non-existent output $output";
+ return 0;
+ fi;
+ nixInfoLog "running on $output";
+ while IFS= read -r -d '' path; do
+ pathParent="$(dirname "$path")";
+ symlinkTarget="$(readlink "$path")";
+ if [[ $symlinkTarget == /* ]]; then
+ nixInfoLog "symlink $path points to absolute target $symlinkTarget";
+ else
+ nixInfoLog "symlink $path points to relative target $symlinkTarget";
+ symlinkTarget="$(realpath --no-symlinks --canonicalize-missing "$pathParent/$symlinkTarget")";
+ fi;
+ if [[ $symlinkTarget != "$NIX_STORE"/* ]]; then
+ nixInfoLog "symlink $path points outside the Nix store; ignoring";
+ continue;
+ fi;
+ if [[ $path == "$symlinkTarget" ]]; then
+ nixErrorLog "the symlink $path is reflexive";
+ numReflexiveSymlinks+=1;
+ else
+ if [[ ! -e $symlinkTarget ]]; then
+ nixErrorLog "the symlink $path points to a missing target: $symlinkTarget";
+ numDanglingSymlinks+=1;
+ else
+ nixDebugLog "the symlink $path is irreflexive and points to a target which exists";
+ fi;
+ fi;
+ done < <(find "$output" -type l -print0);
+ if ((numDanglingSymlinks > 0 || numReflexiveSymlinks > 0)); then
+ nixErrorLog "found $numDanglingSymlinks dangling symlinks and $numReflexiveSymlinks reflexive symlinks";
+ exit 1;
+ fi;
+ return 0
+}
+noBrokenSymlinksInAllOutputs ()
+{
+
+ if [[ -z ${dontCheckForBrokenSymlinks-} ]]; then
+ for output in $(getAllOutputNames);
+ do
+ noBrokenSymlinks "${!output}";
+ done;
+ fi
+}
+patchELF ()
+{
+
+ local dir="$1";
+ [ -e "$dir" ] || return 0;
+ echo "shrinking RPATHs of ELF executables and libraries in $dir";
+ local i;
+ while IFS= read -r -d '' i; do
+ if [[ "$i" =~ .build-id ]]; then
+ continue;
+ fi;
+ if ! isELF "$i"; then
+ continue;
+ fi;
+ echo "shrinking $i";
+ patchelf --shrink-rpath "$i" || true;
+ done < <(find "$dir" -type f -print0)
+}
+patchPhase ()
+{
+
+ runHook prePatch;
+ local -a patchesArray;
+ concatTo patchesArray patches;
+ for i in "${patchesArray[@]}";
+ do
+ echo "applying patch $i";
+ local uncompress=cat;
+ case "$i" in
+ *.gz)
+ uncompress="gzip -d"
+ ;;
+ *.bz2)
+ uncompress="bzip2 -d"
+ ;;
+ *.xz)
+ uncompress="xz -d"
+ ;;
+ *.lzma)
+ uncompress="lzma -d"
+ ;;
+ esac;
+ local -a flagsArray;
+ concatTo flagsArray patchFlags=-p1;
+ $uncompress < "$i" 2>&1 | patch "${flagsArray[@]}";
+ done;
+ runHook postPatch
+}
+patchShebangs ()
+{
+
+ local pathName;
+ local update=false;
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --host)
+ pathName=HOST_PATH;
+ shift
+ ;;
+ --build)
+ pathName=PATH;
+ shift
+ ;;
+ --update)
+ update=true;
+ shift
+ ;;
+ --)
+ shift;
+ break
+ ;;
+ -* | --*)
+ echo "Unknown option $1 supplied to patchShebangs" 1>&2;
+ return 1
+ ;;
+ *)
+ break
+ ;;
+ esac;
+ done;
+ echo "patching script interpreter paths in $@";
+ local f;
+ local oldPath;
+ local newPath;
+ local arg0;
+ local args;
+ local oldInterpreterLine;
+ local newInterpreterLine;
+ if [[ $# -eq 0 ]]; then
+ echo "No arguments supplied to patchShebangs" 1>&2;
+ return 0;
+ fi;
+ local f;
+ while IFS= read -r -d '' f; do
+ isScript "$f" || continue;
+ read -r oldInterpreterLine < "$f" || [ "$oldInterpreterLine" ];
+ read -r oldPath arg0 args <<< "${oldInterpreterLine:2}";
+ if [[ -z "${pathName:-}" ]]; then
+ if [[ -n $strictDeps && $f == "$NIX_STORE"* ]]; then
+ pathName=HOST_PATH;
+ else
+ pathName=PATH;
+ fi;
+ fi;
+ if [[ "$oldPath" == *"/bin/env" ]]; then
+ if [[ $arg0 == "-S" ]]; then
+ arg0=${args%% *};
+ [[ "$args" == *" "* ]] && args=${args#* } || args=;
+ newPath="$(PATH="${!pathName}" type -P "env" || true)";
+ args="-S $(PATH="${!pathName}" type -P "$arg0" || true) $args";
+ else
+ if [[ $arg0 == "-"* || $arg0 == *"="* ]]; then
+ echo "$f: unsupported interpreter directive \"$oldInterpreterLine\" (set dontPatchShebangs=1 and handle shebang patching yourself)" 1>&2;
+ exit 1;
+ else
+ newPath="$(PATH="${!pathName}" type -P "$arg0" || true)";
+ fi;
+ fi;
+ else
+ if [[ -z $oldPath ]]; then
+ oldPath="/bin/sh";
+ fi;
+ newPath="$(PATH="${!pathName}" type -P "$(basename "$oldPath")" || true)";
+ args="$arg0 $args";
+ fi;
+ newInterpreterLine="$newPath $args";
+ newInterpreterLine=${newInterpreterLine%${newInterpreterLine##*[![:space:]]}};
+ if [[ -n "$oldPath" && ( "$update" == true || "${oldPath:0:${#NIX_STORE}}" != "$NIX_STORE" ) ]]; then
+ if [[ -n "$newPath" && "$newPath" != "$oldPath" ]]; then
+ echo "$f: interpreter directive changed from \"$oldInterpreterLine\" to \"$newInterpreterLine\"";
+ escapedInterpreterLine=${newInterpreterLine//\\/\\\\};
+ timestamp=$(stat --printf "%y" "$f");
+ sed -i -e "1 s|.*|#\!$escapedInterpreterLine|" "$f";
+ touch --date "$timestamp" "$f";
+ fi;
+ fi;
+ done < <(find "$@" -type f -perm -0100 -print0)
+}
+patchShebangsAuto ()
+{
+
+ if [[ -z "${dontPatchShebangs-}" && -e "$prefix" ]]; then
+ if [[ "$output" != out && "$output" = "$outputDev" ]]; then
+ patchShebangs --build "$prefix";
+ else
+ patchShebangs --host "$prefix";
+ fi;
+ fi
+}
+prependToVar ()
+{
+
+ local -n nameref="$1";
+ local useArray type;
+ if [ -n "$__structuredAttrs" ]; then
+ useArray=true;
+ else
+ useArray=false;
+ fi;
+ if type=$(declare -p "$1" 2> /dev/null); then
+ case "${type#* }" in
+ -A*)
+ echo "prependToVar(): ERROR: trying to use prependToVar on an associative array." 1>&2;
+ return 1
+ ;;
+ -a*)
+ useArray=true
+ ;;
+ *)
+ useArray=false
+ ;;
+ esac;
+ fi;
+ shift;
+ if $useArray; then
+ nameref=("$@" ${nameref+"${nameref[@]}"});
+ else
+ nameref="$* ${nameref-}";
+ fi
+}
+printLines ()
+{
+
+ (( "$#" > 0 )) || return 0;
+ printf '%s\n' "$@"
+}
+printWords ()
+{
+
+ (( "$#" > 0 )) || return 0;
+ printf '%s ' "$@"
+}
+recordPropagatedDependencies ()
+{
+
+ declare -ra flatVars=(depsBuildBuildPropagated propagatedNativeBuildInputs depsBuildTargetPropagated depsHostHostPropagated propagatedBuildInputs depsTargetTargetPropagated);
+ declare -ra flatFiles=("${propagatedBuildDepFiles[@]}" "${propagatedHostDepFiles[@]}" "${propagatedTargetDepFiles[@]}");
+ local propagatedInputsIndex;
+ for propagatedInputsIndex in "${!flatVars[@]}";
+ do
+ local propagatedInputsSlice="${flatVars[$propagatedInputsIndex]}[@]";
+ local propagatedInputsFile="${flatFiles[$propagatedInputsIndex]}";
+ [[ -n "${!propagatedInputsSlice}" ]] || continue;
+ mkdir -p "${!outputDev}/nix-support";
+ printWords ${!propagatedInputsSlice} > "${!outputDev}/nix-support/$propagatedInputsFile";
+ done
+}
+runHook ()
+{
+
+ local hookName="$1";
+ shift;
+ local hooksSlice="${hookName%Hook}Hooks[@]";
+ local hook;
+ for hook in "_callImplicitHook 0 $hookName" ${!hooksSlice+"${!hooksSlice}"};
+ do
+ _logHook "$hookName" "$hook" "$@";
+ _eval "$hook" "$@";
+ done;
+ return 0
+}
+runOneHook ()
+{
+
+ local hookName="$1";
+ shift;
+ local hooksSlice="${hookName%Hook}Hooks[@]";
+ local hook ret=1;
+ for hook in "_callImplicitHook 1 $hookName" ${!hooksSlice+"${!hooksSlice}"};
+ do
+ _logHook "$hookName" "$hook" "$@";
+ if _eval "$hook" "$@"; then
+ ret=0;
+ break;
+ fi;
+ done;
+ return "$ret"
+}
+runPhase ()
+{
+
+ local curPhase="$*";
+ if [[ "$curPhase" = unpackPhase && -n "${dontUnpack:-}" ]]; then
+ return;
+ fi;
+ if [[ "$curPhase" = patchPhase && -n "${dontPatch:-}" ]]; then
+ return;
+ fi;
+ if [[ "$curPhase" = configurePhase && -n "${dontConfigure:-}" ]]; then
+ return;
+ fi;
+ if [[ "$curPhase" = buildPhase && -n "${dontBuild:-}" ]]; then
+ return;
+ fi;
+ if [[ "$curPhase" = checkPhase && -z "${doCheck:-}" ]]; then
+ return;
+ fi;
+ if [[ "$curPhase" = installPhase && -n "${dontInstall:-}" ]]; then
+ return;
+ fi;
+ if [[ "$curPhase" = fixupPhase && -n "${dontFixup:-}" ]]; then
+ return;
+ fi;
+ if [[ "$curPhase" = installCheckPhase && -z "${doInstallCheck:-}" ]]; then
+ return;
+ fi;
+ if [[ "$curPhase" = distPhase && -z "${doDist:-}" ]]; then
+ return;
+ fi;
+ showPhaseHeader "$curPhase";
+ dumpVars;
+ local startTime endTime;
+ startTime=$(date +"%s");
+ eval "${!curPhase:-$curPhase}";
+ endTime=$(date +"%s");
+ showPhaseFooter "$curPhase" "$startTime" "$endTime";
+ if [ "$curPhase" = unpackPhase ]; then
+ [ -n "${sourceRoot:-}" ] && chmod +x -- "${sourceRoot}";
+ cd -- "${sourceRoot:-.}";
+ fi
+}
+showPhaseFooter ()
+{
+
+ local phase="$1";
+ local startTime="$2";
+ local endTime="$3";
+ local delta=$(( endTime - startTime ));
+ (( delta < 30 )) && return;
+ local H=$((delta/3600));
+ local M=$((delta%3600/60));
+ local S=$((delta%60));
+ echo -n "$phase completed in ";
+ (( H > 0 )) && echo -n "$H hours ";
+ (( M > 0 )) && echo -n "$M minutes ";
+ echo "$S seconds"
+}
+showPhaseHeader ()
+{
+
+ local phase="$1";
+ echo "Running phase: $phase";
+ if [[ -z ${NIX_LOG_FD-} ]]; then
+ return;
+ fi;
+ printf "@nix { \"action\": \"setPhase\", \"phase\": \"%s\" }\n" "$phase" >&"$NIX_LOG_FD"
+}
+stripDirs ()
+{
+
+ local cmd="$1";
+ local ranlibCmd="$2";
+ local paths="$3";
+ local stripFlags="$4";
+ local excludeFlags=();
+ local pathsNew=;
+ [ -z "$cmd" ] && echo "stripDirs: Strip command is empty" 1>&2 && exit 1;
+ [ -z "$ranlibCmd" ] && echo "stripDirs: Ranlib command is empty" 1>&2 && exit 1;
+ local pattern;
+ if [ -n "${stripExclude:-}" ]; then
+ for pattern in "${stripExclude[@]}";
+ do
+ excludeFlags+=(-a '!' '(' -name "$pattern" -o -wholename "$prefix/$pattern" ')');
+ done;
+ fi;
+ local p;
+ for p in ${paths};
+ do
+ if [ -e "$prefix/$p" ]; then
+ pathsNew="${pathsNew} $prefix/$p";
+ fi;
+ done;
+ paths=${pathsNew};
+ if [ -n "${paths}" ]; then
+ echo "stripping (with command $cmd and flags $stripFlags) in $paths";
+ local striperr;
+ striperr="$(mktemp --tmpdir="$TMPDIR" 'striperr.XXXXXX')";
+ find $paths -type f "${excludeFlags[@]}" -a '!' -path "$prefix/lib/debug/*" -printf '%D-%i,%p\0' | sort -t, -k1,1 -u -z | cut -d, -f2- -z | xargs -r -0 -n1 -P "$NIX_BUILD_CORES" -- $cmd $stripFlags 2> "$striperr" || exit_code=$?;
+ [[ "$exit_code" = 123 || -z "$exit_code" ]] || ( cat "$striperr" 1>&2 && exit 1 );
+ rm "$striperr";
+ find $paths -name '*.a' -type f -exec $ranlibCmd '{}' \; 2> /dev/null;
+ fi
+}
+stripHash ()
+{
+
+ local strippedName casematchOpt=0;
+ strippedName="$(basename -- "$1")";
+ shopt -q nocasematch && casematchOpt=1;
+ shopt -u nocasematch;
+ if [[ "$strippedName" =~ ^[a-z0-9]{32}- ]]; then
+ echo "${strippedName:33}";
+ else
+ echo "$strippedName";
+ fi;
+ if (( casematchOpt )); then
+ shopt -s nocasematch;
+ fi
+}
+substitute ()
+{
+
+ local input="$1";
+ local output="$2";
+ shift 2;
+ if [ ! -f "$input" ]; then
+ echo "substitute(): ERROR: file '$input' does not exist" 1>&2;
+ return 1;
+ fi;
+ local content;
+ consumeEntire content < "$input";
+ if [ -e "$output" ]; then
+ chmod +w "$output";
+ fi;
+ substituteStream content "file '$input'" "$@" > "$output"
+}
+substituteAll ()
+{
+
+ local input="$1";
+ local output="$2";
+ local -a args=();
+ _allFlags;
+ substitute "$input" "$output" "${args[@]}"
+}
+substituteAllInPlace ()
+{
+
+ local fileName="$1";
+ shift;
+ substituteAll "$fileName" "$fileName" "$@"
+}
+substituteAllStream ()
+{
+
+ local -a args=();
+ _allFlags;
+ substituteStream "$1" "$2" "${args[@]}"
+}
+substituteInPlace ()
+{
+
+ local -a fileNames=();
+ for arg in "$@";
+ do
+ if [[ "$arg" = "--"* ]]; then
+ break;
+ fi;
+ fileNames+=("$arg");
+ shift;
+ done;
+ if ! [[ "${#fileNames[@]}" -gt 0 ]]; then
+ echo "substituteInPlace called without any files to operate on (files must come before options!)" 1>&2;
+ return 1;
+ fi;
+ for file in "${fileNames[@]}";
+ do
+ substitute "$file" "$file" "$@";
+ done
+}
+substituteStream ()
+{
+
+ local var=$1;
+ local description=$2;
+ shift 2;
+ while (( "$#" )); do
+ local replace_mode="$1";
+ case "$1" in
+ --replace)
+ if ! "$_substituteStream_has_warned_replace_deprecation"; then
+ echo "substituteStream() in derivation $name: WARNING: '--replace' is deprecated, use --replace-{fail,warn,quiet}. ($description)" 1>&2;
+ _substituteStream_has_warned_replace_deprecation=true;
+ fi;
+ replace_mode='--replace-warn'
+ ;&
+ --replace-quiet | --replace-warn | --replace-fail)
+ pattern="$2";
+ replacement="$3";
+ shift 3;
+ if ! [[ "${!var}" == *"$pattern"* ]]; then
+ if [ "$replace_mode" == --replace-warn ]; then
+ printf "substituteStream() in derivation $name: WARNING: pattern %q doesn't match anything in %s\n" "$pattern" "$description" 1>&2;
+ else
+ if [ "$replace_mode" == --replace-fail ]; then
+ printf "substituteStream() in derivation $name: ERROR: pattern %q doesn't match anything in %s\n" "$pattern" "$description" 1>&2;
+ return 1;
+ fi;
+ fi;
+ fi;
+ eval "$var"'=${'"$var"'//"$pattern"/"$replacement"}'
+ ;;
+ --subst-var)
+ local varName="$2";
+ shift 2;
+ if ! [[ "$varName" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
+ echo "substituteStream() in derivation $name: ERROR: substitution variables must be valid Bash names, \"$varName\" isn't." 1>&2;
+ return 1;
+ fi;
+ if [ -z ${!varName+x} ]; then
+ echo "substituteStream() in derivation $name: ERROR: variable \$$varName is unset" 1>&2;
+ return 1;
+ fi;
+ pattern="@$varName@";
+ replacement="${!varName}";
+ eval "$var"'=${'"$var"'//"$pattern"/"$replacement"}'
+ ;;
+ --subst-var-by)
+ pattern="@$2@";
+ replacement="$3";
+ eval "$var"'=${'"$var"'//"$pattern"/"$replacement"}';
+ shift 3
+ ;;
+ *)
+ echo "substituteStream() in derivation $name: ERROR: Invalid command line argument: $1" 1>&2;
+ return 1
+ ;;
+ esac;
+ done;
+ printf "%s" "${!var}"
+}
+unpackFile ()
+{
+
+ curSrc="$1";
+ echo "unpacking source archive $curSrc";
+ if ! runOneHook unpackCmd "$curSrc"; then
+ echo "do not know how to unpack source archive $curSrc";
+ exit 1;
+ fi
+}
+unpackPhase ()
+{
+
+ runHook preUnpack;
+ if [ -z "${srcs:-}" ]; then
+ if [ -z "${src:-}" ]; then
+ echo 'variable $src or $srcs should point to the source';
+ exit 1;
+ fi;
+ srcs="$src";
+ fi;
+ local -a srcsArray;
+ concatTo srcsArray srcs;
+ local dirsBefore="";
+ for i in *;
+ do
+ if [ -d "$i" ]; then
+ dirsBefore="$dirsBefore $i ";
+ fi;
+ done;
+ for i in "${srcsArray[@]}";
+ do
+ unpackFile "$i";
+ done;
+ : "${sourceRoot=}";
+ if [ -n "${setSourceRoot:-}" ]; then
+ runOneHook setSourceRoot;
+ else
+ if [ -z "$sourceRoot" ]; then
+ for i in *;
+ do
+ if [ -d "$i" ]; then
+ case $dirsBefore in
+ *\ $i\ *)
+
+ ;;
+ *)
+ if [ -n "$sourceRoot" ]; then
+ echo "unpacker produced multiple directories";
+ exit 1;
+ fi;
+ sourceRoot="$i"
+ ;;
+ esac;
+ fi;
+ done;
+ fi;
+ fi;
+ if [ -z "$sourceRoot" ]; then
+ echo "unpacker appears to have produced no directories";
+ exit 1;
+ fi;
+ echo "source root is $sourceRoot";
+ if [ "${dontMakeSourcesWritable:-0}" != 1 ]; then
+ chmod -R u+w -- "$sourceRoot";
+ fi;
+ runHook postUnpack
+}
+updateAutotoolsGnuConfigScriptsPhase ()
+{
+
+ if [ -n "${dontUpdateAutotoolsGnuConfigScripts-}" ]; then
+ return;
+ fi;
+ for script in config.sub config.guess;
+ do
+ for f in $(find . -type f -name "$script");
+ do
+ echo "Updating Autotools / GNU config script to a newer upstream version: $f";
+ cp -f "/nix/store/54jlh06dvn439jc4zaxm1x2xry1xinia-gnu-config-2024-01-01/$script" "$f";
+ done;
+ done
+}
+updateSourceDateEpoch ()
+{
+
+ local path="$1";
+ [[ $path == -* ]] && path="./$path";
+ local -a res=($(find "$path" -type f -not -newer "$NIX_BUILD_TOP/.." -printf '%T@ "%p"\0' | sort -n --zero-terminated | tail -n1 --zero-terminated | head -c -1));
+ local time="${res[0]//\.[0-9]*/}";
+ local newestFile="${res[1]}";
+ if [ "${time:-0}" -gt "$SOURCE_DATE_EPOCH" ]; then
+ echo "setting SOURCE_DATE_EPOCH to timestamp $time of file $newestFile";
+ export SOURCE_DATE_EPOCH="$time";
+ local now="$(date +%s)";
+ if [ "$time" -gt $((now - 60)) ]; then
+ echo "warning: file $newestFile may be generated; SOURCE_DATE_EPOCH may be non-deterministic";
+ fi;
+ fi
+}
+PATH="$PATH${nix_saved_PATH:+:$nix_saved_PATH}"
+XDG_DATA_DIRS="$XDG_DATA_DIRS${nix_saved_XDG_DATA_DIRS:+:$nix_saved_XDG_DATA_DIRS}"
+export NIX_BUILD_TOP="$(mktemp -d -t nix-shell.XXXXXX)"
+export TMP="$NIX_BUILD_TOP"
+export TMPDIR="$NIX_BUILD_TOP"
+export TEMP="$NIX_BUILD_TOP"
+export TEMPDIR="$NIX_BUILD_TOP"
+eval "${shellHook:-}"
diff --git a/.env b/.env
new file mode 100644
index 0000000..0cdec71
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+ANTHROPIC_API_KEY=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
+GOOGLE_TRANSLATE_API_KEY=AIzaSyAtJJsqoMp8VjRDzFEijAJo-vOhYC5n7co
\ No newline at end of file
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..0dc7e31
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1741865919,
+ "narHash": "sha256-4thdbnP6dlbdq+qZWTsm4ffAwoS8Tiq1YResB+RP6WE=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "573c650e8a14b2faa0041645ab18aed7e60f0c9a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..7c05db8
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,33 @@
+{
+ description = "Nix-flake Elixir development environment (adds nodejs and fly.io)";
+
+ inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+ inputs.flake-utils.url = "github:numtide/flake-utils";
+
+ outputs = { self, nixpkgs, flake-utils }:
+ flake-utils.lib.eachDefaultSystem (system:
+ let
+ inherit (pkgs.lib) optional optionals;
+ pkgs = import nixpkgs { inherit system; };
+
+ elixir = pkgs.beam.packages.erlang.elixir;
+ in
+ with pkgs;
+ {
+ devShell = pkgs.mkShell {
+ buildInputs = [
+ bashInteractive
+ elixir
+ elixir_ls
+ nodejs
+ glibcLocales
+ flyctl
+ ] ++ optional stdenv.isLinux inotify-tools
+ ++ optional stdenv.isDarwin terminal-notifier
+ ++ optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [
+ CoreFoundation
+ CoreServices
+ ]);
+ };
+ });
+}
diff --git a/vocab_assistant/.formatter.exs b/vocab_assistant/.formatter.exs
new file mode 100644
index 0000000..ef8840c
--- /dev/null
+++ b/vocab_assistant/.formatter.exs
@@ -0,0 +1,6 @@
+[
+ import_deps: [:ecto, :ecto_sql, :phoenix],
+ subdirectories: ["priv/*/migrations"],
+ plugins: [Phoenix.LiveView.HTMLFormatter],
+ inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
+]
diff --git a/vocab_assistant/.gitignore b/vocab_assistant/.gitignore
new file mode 100644
index 0000000..028b944
--- /dev/null
+++ b/vocab_assistant/.gitignore
@@ -0,0 +1,37 @@
+# The directory Mix will write compiled artifacts to.
+/_build/
+
+# If you run "mix test --cover", coverage assets end up here.
+/cover/
+
+# The directory Mix downloads your dependencies sources to.
+/deps/
+
+# Where 3rd-party dependencies like ExDoc output generated docs.
+/doc/
+
+# Ignore .fetch files in case you like to edit your project deps locally.
+/.fetch
+
+# If the VM crashes, it generates a dump, let's ignore it too.
+erl_crash.dump
+
+# Also ignore archive artifacts (built via "mix archive.build").
+*.ez
+
+# Temporary files, for example, from tests.
+/tmp/
+
+# Ignore package tarball (built via "mix hex.build").
+vocab_assistant-*.tar
+
+# Ignore assets that are produced by build tools.
+/priv/static/assets/
+
+# Ignore digested assets cache.
+/priv/static/cache_manifest.json
+
+# In case you use Node.js/npm, you want to ignore these.
+npm-debug.log
+/assets/node_modules/
+
diff --git a/vocab_assistant/README.md b/vocab_assistant/README.md
new file mode 100644
index 0000000..4387f0e
--- /dev/null
+++ b/vocab_assistant/README.md
@@ -0,0 +1,18 @@
+# VocabAssistant
+
+To start your Phoenix server:
+
+ * Run `mix setup` to install and setup dependencies
+ * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
+
+Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
+
+Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
+
+## Learn more
+
+ * Official website: https://www.phoenixframework.org/
+ * Guides: https://hexdocs.pm/phoenix/overview.html
+ * Docs: https://hexdocs.pm/phoenix
+ * Forum: https://elixirforum.com/c/phoenix-forum
+ * Source: https://github.com/phoenixframework/phoenix
diff --git a/vocab_assistant/assets/css/app.css b/vocab_assistant/assets/css/app.css
new file mode 100644
index 0000000..378c8f9
--- /dev/null
+++ b/vocab_assistant/assets/css/app.css
@@ -0,0 +1,5 @@
+@import "tailwindcss/base";
+@import "tailwindcss/components";
+@import "tailwindcss/utilities";
+
+/* This file is for your main application CSS */
diff --git a/vocab_assistant/assets/js/app.js b/vocab_assistant/assets/js/app.js
new file mode 100644
index 0000000..d5e278a
--- /dev/null
+++ b/vocab_assistant/assets/js/app.js
@@ -0,0 +1,44 @@
+// If you want to use Phoenix channels, run `mix help phx.gen.channel`
+// to get started and then uncomment the line below.
+// import "./user_socket.js"
+
+// You can include dependencies in two ways.
+//
+// The simplest option is to put them in assets/vendor and
+// import them using relative paths:
+//
+// import "../vendor/some-package.js"
+//
+// Alternatively, you can `npm install some-package --prefix assets` and import
+// them using a path starting with the package name:
+//
+// import "some-package"
+//
+
+// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
+import "phoenix_html"
+// Establish Phoenix Socket and LiveView configuration.
+import {Socket} from "phoenix"
+import {LiveSocket} from "phoenix_live_view"
+import topbar from "../vendor/topbar"
+
+let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
+let liveSocket = new LiveSocket("/live", Socket, {
+ longPollFallbackMs: 2500,
+ params: {_csrf_token: csrfToken}
+})
+
+// Show progress bar on live navigation and form submits
+topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
+window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
+window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
+
+// connect if there are any LiveViews on the page
+liveSocket.connect()
+
+// expose liveSocket on window for web console debug logs and latency simulation:
+// >> liveSocket.enableDebug()
+// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
+// >> liveSocket.disableLatencySim()
+window.liveSocket = liveSocket
+
diff --git a/vocab_assistant/assets/tailwind.config.js b/vocab_assistant/assets/tailwind.config.js
new file mode 100644
index 0000000..fa906a3
--- /dev/null
+++ b/vocab_assistant/assets/tailwind.config.js
@@ -0,0 +1,75 @@
+// See the Tailwind configuration guide for advanced usage
+// https://tailwindcss.com/docs/configuration
+
+const plugin = require("tailwindcss/plugin")
+const fs = require("fs")
+const path = require("path")
+
+module.exports = {
+ content: [
+ "./js/**/*.js",
+ "../lib/vocab_assistant_web.ex",
+ "../lib/vocab_assistant_web/**/*.*ex"
+ ],
+ theme: {
+ extend: {
+ colors: {
+ brand: "#FD4F00",
+ }
+ },
+ },
+ plugins: [
+ require("@tailwindcss/forms"),
+ // Allows prefixing tailwind classes with LiveView classes to add rules
+ // only when LiveView classes are applied, for example:
+ //
+ //
+ //
+ plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
+ plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
+ plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
+ plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
+
+ // Embeds Heroicons (https://heroicons.com) into your app.css bundle
+ // See your `CoreComponents.icon/1` for more information.
+ //
+ plugin(function({matchComponents, theme}) {
+ let iconsDir = path.join(__dirname, "../deps/heroicons/optimized")
+ let values = {}
+ let icons = [
+ ["", "/24/outline"],
+ ["-solid", "/24/solid"],
+ ["-mini", "/20/solid"],
+ ["-micro", "/16/solid"]
+ ]
+ icons.forEach(([suffix, dir]) => {
+ fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
+ let name = path.basename(file, ".svg") + suffix
+ values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
+ })
+ })
+ matchComponents({
+ "hero": ({name, fullPath}) => {
+ let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
+ let size = theme("spacing.6")
+ if (name.endsWith("-mini")) {
+ size = theme("spacing.5")
+ } else if (name.endsWith("-micro")) {
+ size = theme("spacing.4")
+ }
+ return {
+ [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
+ "-webkit-mask": `var(--hero-${name})`,
+ "mask": `var(--hero-${name})`,
+ "mask-repeat": "no-repeat",
+ "background-color": "currentColor",
+ "vertical-align": "middle",
+ "display": "inline-block",
+ "width": size,
+ "height": size
+ }
+ }
+ }, {values})
+ })
+ ]
+}
diff --git a/vocab_assistant/assets/vendor/topbar.js b/vocab_assistant/assets/vendor/topbar.js
new file mode 100644
index 0000000..4195727
--- /dev/null
+++ b/vocab_assistant/assets/vendor/topbar.js
@@ -0,0 +1,165 @@
+/**
+ * @license MIT
+ * topbar 2.0.0, 2023-02-04
+ * https://buunguyen.github.io/topbar
+ * Copyright (c) 2021 Buu Nguyen
+ */
+(function (window, document) {
+ "use strict";
+
+ // https://gist.github.com/paulirish/1579671
+ (function () {
+ var lastTime = 0;
+ var vendors = ["ms", "moz", "webkit", "o"];
+ for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+ window.requestAnimationFrame =
+ window[vendors[x] + "RequestAnimationFrame"];
+ window.cancelAnimationFrame =
+ window[vendors[x] + "CancelAnimationFrame"] ||
+ window[vendors[x] + "CancelRequestAnimationFrame"];
+ }
+ if (!window.requestAnimationFrame)
+ window.requestAnimationFrame = function (callback, element) {
+ var currTime = new Date().getTime();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = window.setTimeout(function () {
+ callback(currTime + timeToCall);
+ }, timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+ if (!window.cancelAnimationFrame)
+ window.cancelAnimationFrame = function (id) {
+ clearTimeout(id);
+ };
+ })();
+
+ var canvas,
+ currentProgress,
+ showing,
+ progressTimerId = null,
+ fadeTimerId = null,
+ delayTimerId = null,
+ addEvent = function (elem, type, handler) {
+ if (elem.addEventListener) elem.addEventListener(type, handler, false);
+ else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
+ else elem["on" + type] = handler;
+ },
+ options = {
+ autoRun: true,
+ barThickness: 3,
+ barColors: {
+ 0: "rgba(26, 188, 156, .9)",
+ ".25": "rgba(52, 152, 219, .9)",
+ ".50": "rgba(241, 196, 15, .9)",
+ ".75": "rgba(230, 126, 34, .9)",
+ "1.0": "rgba(211, 84, 0, .9)",
+ },
+ shadowBlur: 10,
+ shadowColor: "rgba(0, 0, 0, .6)",
+ className: null,
+ },
+ repaint = function () {
+ canvas.width = window.innerWidth;
+ canvas.height = options.barThickness * 5; // need space for shadow
+
+ var ctx = canvas.getContext("2d");
+ ctx.shadowBlur = options.shadowBlur;
+ ctx.shadowColor = options.shadowColor;
+
+ var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
+ for (var stop in options.barColors)
+ lineGradient.addColorStop(stop, options.barColors[stop]);
+ ctx.lineWidth = options.barThickness;
+ ctx.beginPath();
+ ctx.moveTo(0, options.barThickness / 2);
+ ctx.lineTo(
+ Math.ceil(currentProgress * canvas.width),
+ options.barThickness / 2
+ );
+ ctx.strokeStyle = lineGradient;
+ ctx.stroke();
+ },
+ createCanvas = function () {
+ canvas = document.createElement("canvas");
+ var style = canvas.style;
+ style.position = "fixed";
+ style.top = style.left = style.right = style.margin = style.padding = 0;
+ style.zIndex = 100001;
+ style.display = "none";
+ if (options.className) canvas.classList.add(options.className);
+ document.body.appendChild(canvas);
+ addEvent(window, "resize", repaint);
+ },
+ topbar = {
+ config: function (opts) {
+ for (var key in opts)
+ if (options.hasOwnProperty(key)) options[key] = opts[key];
+ },
+ show: function (delay) {
+ if (showing) return;
+ if (delay) {
+ if (delayTimerId) return;
+ delayTimerId = setTimeout(() => topbar.show(), delay);
+ } else {
+ showing = true;
+ if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
+ if (!canvas) createCanvas();
+ canvas.style.opacity = 1;
+ canvas.style.display = "block";
+ topbar.progress(0);
+ if (options.autoRun) {
+ (function loop() {
+ progressTimerId = window.requestAnimationFrame(loop);
+ topbar.progress(
+ "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
+ );
+ })();
+ }
+ }
+ },
+ progress: function (to) {
+ if (typeof to === "undefined") return currentProgress;
+ if (typeof to === "string") {
+ to =
+ (to.indexOf("+") >= 0 || to.indexOf("-") >= 0
+ ? currentProgress
+ : 0) + parseFloat(to);
+ }
+ currentProgress = to > 1 ? 1 : to;
+ repaint();
+ return currentProgress;
+ },
+ hide: function () {
+ clearTimeout(delayTimerId);
+ delayTimerId = null;
+ if (!showing) return;
+ showing = false;
+ if (progressTimerId != null) {
+ window.cancelAnimationFrame(progressTimerId);
+ progressTimerId = null;
+ }
+ (function loop() {
+ if (topbar.progress("+.1") >= 1) {
+ canvas.style.opacity -= 0.05;
+ if (canvas.style.opacity <= 0.05) {
+ canvas.style.display = "none";
+ fadeTimerId = null;
+ return;
+ }
+ }
+ fadeTimerId = window.requestAnimationFrame(loop);
+ })();
+ },
+ };
+
+ if (typeof module === "object" && typeof module.exports === "object") {
+ module.exports = topbar;
+ } else if (typeof define === "function" && define.amd) {
+ define(function () {
+ return topbar;
+ });
+ } else {
+ this.topbar = topbar;
+ }
+}.call(this, window, document));
diff --git a/vocab_assistant/config/config.exs b/vocab_assistant/config/config.exs
new file mode 100644
index 0000000..7722cea
--- /dev/null
+++ b/vocab_assistant/config/config.exs
@@ -0,0 +1,66 @@
+# This file is responsible for configuring your application
+# and its dependencies with the aid of the Config module.
+#
+# This configuration file is loaded before any dependency and
+# is restricted to this project.
+
+# General application configuration
+import Config
+
+config :vocab_assistant,
+ ecto_repos: [VocabAssistant.Repo],
+ generators: [timestamp_type: :utc_datetime]
+
+# Configures the endpoint
+config :vocab_assistant, VocabAssistantWeb.Endpoint,
+ url: [host: "localhost"],
+ adapter: Bandit.PhoenixAdapter,
+ render_errors: [
+ formats: [html: VocabAssistantWeb.ErrorHTML, json: VocabAssistantWeb.ErrorJSON],
+ layout: false
+ ],
+ pubsub_server: VocabAssistant.PubSub,
+ live_view: [signing_salt: "CIhYYoHS"]
+
+# Configures the mailer
+#
+# By default it uses the "Local" adapter which stores the emails
+# locally. You can see the emails in your browser, at "/dev/mailbox".
+#
+# For production it's recommended to configure a different adapter
+# at the `config/runtime.exs`.
+config :vocab_assistant, VocabAssistant.Mailer, adapter: Swoosh.Adapters.Local
+
+# Configure esbuild (the version is required)
+config :esbuild,
+ version: "0.17.11",
+ vocab_assistant: [
+ args:
+ ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
+ cd: Path.expand("../assets", __DIR__),
+ env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
+ ]
+
+# Configure tailwind (the version is required)
+config :tailwind,
+ version: "3.4.0",
+ vocab_assistant: [
+ args: ~w(
+ --config=tailwind.config.js
+ --input=css/app.css
+ --output=../priv/static/assets/app.css
+ ),
+ cd: Path.expand("../assets", __DIR__)
+ ]
+
+# Configures Elixir's Logger
+config :logger, :console,
+ format: "$time $metadata[$level] $message\n",
+ metadata: [:request_id]
+
+# Use Jason for JSON parsing in Phoenix
+config :phoenix, :json_library, Jason
+
+# Import environment specific config. This must remain at the bottom
+# of this file so it overrides the configuration defined above.
+import_config "#{config_env()}.exs"
diff --git a/vocab_assistant/config/dev.exs b/vocab_assistant/config/dev.exs
new file mode 100644
index 0000000..d49fba9
--- /dev/null
+++ b/vocab_assistant/config/dev.exs
@@ -0,0 +1,86 @@
+import Config
+
+# Configure your database
+config :vocab_assistant, VocabAssistant.Repo,
+ username: "vocab_assistant",
+ password: "pleasework",
+ hostname: "postgresql.home",
+ database: "vocab_assistant",
+ template: "template0",
+ stacktrace: true,
+ show_sensitive_data_on_connection_error: true,
+ pool_size: 10
+
+# For development, we disable any cache and enable
+# debugging and code reloading.
+#
+# The watchers configuration can be used to run external
+# watchers to your application. For example, we can use it
+# to bundle .js and .css sources.
+config :vocab_assistant, VocabAssistantWeb.Endpoint,
+ # Binding to loopback ipv4 address prevents access from other machines.
+ # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
+ http: [ip: {0, 0, 0, 0}, port: 4000],
+ check_origin: false,
+ code_reloader: true,
+ debug_errors: true,
+ secret_key_base: "Sgut8lRtaP0Hddn3GpJvQjpK/9duVJjNX4sGZXivWwepk04FW1SgHIpFJJZkpbn3",
+ watchers: [
+ esbuild: {Esbuild, :install_and_run, [:vocab_assistant, ~w(--sourcemap=inline --watch)]},
+ tailwind: {Tailwind, :install_and_run, [:vocab_assistant, ~w(--watch)]}
+ ]
+
+# ## SSL Support
+#
+# In order to use HTTPS in development, a self-signed
+# certificate can be generated by running the following
+# Mix task:
+#
+# mix phx.gen.cert
+#
+# Run `mix help phx.gen.cert` for more information.
+#
+# The `http:` config above can be replaced with:
+#
+# https: [
+# port: 4001,
+# cipher_suite: :strong,
+# keyfile: "priv/cert/selfsigned_key.pem",
+# certfile: "priv/cert/selfsigned.pem"
+# ],
+#
+# If desired, both `http:` and `https:` keys can be
+# configured to run both http and https servers on
+# different ports.
+
+# Watch static and templates for browser reloading.
+config :vocab_assistant, VocabAssistantWeb.Endpoint,
+ live_reload: [
+ patterns: [
+ ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
+ ~r"priv/gettext/.*(po)$",
+ ~r"lib/vocab_assistant_web/(controllers|live|components)/.*(ex|heex)$"
+ ]
+ ]
+
+# Enable dev routes for dashboard and mailbox
+config :vocab_assistant, dev_routes: true
+
+# Do not include metadata nor timestamps in development logs
+config :logger, :console, format: "[$level] $message\n"
+
+# Set a higher stacktrace during development. Avoid configuring such
+# in production as building large stacktraces may be expensive.
+config :phoenix, :stacktrace_depth, 20
+
+# Initialize plugs at runtime for faster development compilation
+config :phoenix, :plug_init_mode, :runtime
+
+config :phoenix_live_view,
+ # Include HEEx debug annotations as HTML comments in rendered markup
+ debug_heex_annotations: true,
+ # Enable helpful, but potentially expensive runtime checks
+ enable_expensive_runtime_checks: true
+
+# Disable swoosh api client as it is only required for production adapters.
+config :swoosh, :api_client, false
diff --git a/vocab_assistant/config/prod.exs b/vocab_assistant/config/prod.exs
new file mode 100644
index 0000000..a23cce0
--- /dev/null
+++ b/vocab_assistant/config/prod.exs
@@ -0,0 +1,21 @@
+import Config
+
+# Note we also include the path to a cache manifest
+# containing the digested version of static files. This
+# manifest is generated by the `mix assets.deploy` task,
+# which you should run after static files are built and
+# before starting your production server.
+config :vocab_assistant, VocabAssistantWeb.Endpoint,
+ cache_static_manifest: "priv/static/cache_manifest.json"
+
+# Configures Swoosh API Client
+config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: VocabAssistant.Finch
+
+# Disable Swoosh Local Memory Storage
+config :swoosh, local: false
+
+# Do not print debug messages in production
+config :logger, level: :info
+
+# Runtime production configuration, including reading
+# of environment variables, is done on config/runtime.exs.
diff --git a/vocab_assistant/config/runtime.exs b/vocab_assistant/config/runtime.exs
new file mode 100644
index 0000000..7849423
--- /dev/null
+++ b/vocab_assistant/config/runtime.exs
@@ -0,0 +1,121 @@
+import Config
+
+# config/runtime.exs is executed for all environments, including
+# during releases. It is executed after compilation and before the
+# system starts, so it is typically used to load production configuration
+# and secrets from environment variables or elsewhere. Do not define
+# any compile-time configuration in here, as it won't be applied.
+# The block below contains prod specific runtime configuration.
+
+# ## Using releases
+#
+# If you use `mix release`, you need to explicitly enable the server
+# by passing the PHX_SERVER=true when you start it:
+#
+# PHX_SERVER=true bin/vocab_assistant start
+#
+# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`
+# script that automatically sets the env var above.
+if System.get_env("PHX_SERVER") do
+ config :vocab_assistant, VocabAssistantWeb.Endpoint, server: true
+end
+
+if System.get_env("ANTHROPIC_API_KEY") do
+ config :vocab_assistant, :anthropic_api_key, System.get_env("ANTHROPIC_API_KEY")
+end
+
+if config_env() == :prod do
+ database_url =
+ System.get_env("DATABASE_URL") ||
+ raise """
+ environment variable DATABASE_URL is missing.
+ For example: ecto://USER:PASS@HOST/DATABASE
+ """
+
+ maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
+
+ config :vocab_assistant, VocabAssistant.Repo,
+ # ssl: true,
+ url: database_url,
+ pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
+ socket_options: maybe_ipv6
+
+ # The secret key base is used to sign/encrypt cookies and other secrets.
+ # A default value is used in config/dev.exs and config/test.exs but you
+ # want to use a different value for prod and you most likely don't want
+ # to check this value into version control, so we use an environment
+ # variable instead.
+ secret_key_base =
+ System.get_env("SECRET_KEY_BASE") ||
+ raise """
+ environment variable SECRET_KEY_BASE is missing.
+ You can generate one by calling: mix phx.gen.secret
+ """
+
+ host = System.get_env("PHX_HOST") || "example.com"
+ port = String.to_integer(System.get_env("PORT") || "4000")
+
+ config :vocab_assistant, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
+
+ config :vocab_assistant, VocabAssistantWeb.Endpoint,
+ url: [host: host, port: 443, scheme: "https"],
+ http: [
+ # Enable IPv6 and bind on all interfaces.
+ # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
+ # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0
+ # for details about using IPv6 vs IPv4 and loopback vs public addresses.
+ ip: {0, 0, 0, 0, 0, 0, 0, 0},
+ port: port
+ ],
+ secret_key_base: secret_key_base
+
+ # ## SSL Support
+ #
+ # To get SSL working, you will need to add the `https` key
+ # to your endpoint configuration:
+ #
+ # config :vocab_assistant, VocabAssistantWeb.Endpoint,
+ # https: [
+ # ...,
+ # port: 443,
+ # cipher_suite: :strong,
+ # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
+ # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
+ # ]
+ #
+ # The `cipher_suite` is set to `:strong` to support only the
+ # latest and more secure SSL ciphers. This means old browsers
+ # and clients may not be supported. You can set it to
+ # `:compatible` for wider support.
+ #
+ # `:keyfile` and `:certfile` expect an absolute path to the key
+ # and cert in disk or a relative path inside priv, for example
+ # "priv/ssl/server.key". For all supported SSL configuration
+ # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
+ #
+ # We also recommend setting `force_ssl` in your config/prod.exs,
+ # ensuring no data is ever sent via http, always redirecting to https:
+ #
+ # config :vocab_assistant, VocabAssistantWeb.Endpoint,
+ # force_ssl: [hsts: true]
+ #
+ # Check `Plug.SSL` for all available options in `force_ssl`.
+
+ # ## Configuring the mailer
+ #
+ # In production you need to configure the mailer to use a different adapter.
+ # Also, you may need to configure the Swoosh API client of your choice if you
+ # are not using SMTP. Here is an example of the configuration:
+ #
+ # config :vocab_assistant, VocabAssistant.Mailer,
+ # adapter: Swoosh.Adapters.Mailgun,
+ # api_key: System.get_env("MAILGUN_API_KEY"),
+ # domain: System.get_env("MAILGUN_DOMAIN")
+ #
+ # For this example you need include a HTTP client required by Swoosh API client.
+ # Swoosh supports Hackney and Finch out of the box:
+ #
+ # config :swoosh, :api_client, Swoosh.ApiClient.Hackney
+ #
+ # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.
+end
diff --git a/vocab_assistant/config/test.exs b/vocab_assistant/config/test.exs
new file mode 100644
index 0000000..6e5ccc2
--- /dev/null
+++ b/vocab_assistant/config/test.exs
@@ -0,0 +1,37 @@
+import Config
+
+# Configure your database
+#
+# The MIX_TEST_PARTITION environment variable can be used
+# to provide built-in test partitioning in CI environment.
+# Run `mix help test` for more information.
+config :vocab_assistant, VocabAssistant.Repo,
+ username: "postgres",
+ password: "postgres",
+ hostname: "localhost",
+ database: "vocab_assistant_test#{System.get_env("MIX_TEST_PARTITION")}",
+ pool: Ecto.Adapters.SQL.Sandbox,
+ pool_size: System.schedulers_online() * 2
+
+# We don't run a server during test. If one is required,
+# you can enable the server option below.
+config :vocab_assistant, VocabAssistantWeb.Endpoint,
+ http: [ip: {127, 0, 0, 1}, port: 4002],
+ secret_key_base: "8Z1sQyeN8zgSFZzwitaYkoROm0Upxmy2UIjou5p8MTIlsY5yv1Yg2g7Tdy/sGJ7D",
+ server: false
+
+# In test we don't send emails.
+config :vocab_assistant, VocabAssistant.Mailer, adapter: Swoosh.Adapters.Test
+
+# Disable swoosh api client as it is only required for production adapters.
+config :swoosh, :api_client, false
+
+# Print only warnings and errors during test
+config :logger, level: :warning
+
+# Initialize plugs at runtime for faster test compilation
+config :phoenix, :plug_init_mode, :runtime
+
+config :phoenix_live_view,
+ # Enable helpful, but potentially expensive runtime checks
+ enable_expensive_runtime_checks: true
diff --git a/vocab_assistant/lib/vocab_assistant.ex b/vocab_assistant/lib/vocab_assistant.ex
new file mode 100644
index 0000000..a49b9aa
--- /dev/null
+++ b/vocab_assistant/lib/vocab_assistant.ex
@@ -0,0 +1,75 @@
+defmodule VocabAssistant do
+ @moduledoc """
+ VocabAssistant keeps the contexts that define your domain
+ and business logic.
+
+ Contexts are also responsible for managing your data, regardless
+ if it comes from the database, an external API or others.
+ """
+ require Logger
+ @google_translate_url "https://www.googleapis.com/language/translate/v2"
+
+ def generate_sentence(word, language \\ "Hindi") do
+ client =
+ Anthropix.init(
+ "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"
+ )
+
+ system_prompt =
+ "
+ You are a language-learning assistant tasked with helping the user to learn vocabulary in #{language}.
+ A #{language} word will be provided; respond with a simple phrase or sentence in #{language} using that word.
+ Use markdown to bold the vocab word in question.
+ Ensure that the sentence has just enough complexity to associate the word with relevant context
+ (again, using other beginner-friendly words -- ideally within the 1000 most frequently-used words).
+ Try to incorporate the most closely-related word in the sentence, e.g. for 'curtain' use 'window', not just 'room'.
+ Respond with just the marked-up sentence, nothing else. If the given word is in English, use its #{language} translation.
+ "
+
+ msg = [
+ %{
+ role: "user",
+ content:
+ "Please provide a short, simple sentence in #{language} using the word \"#{word}\"."
+ }
+ ]
+
+ {:ok, %{"content" => [%{"text" => sentence}]}} =
+ Anthropix.chat(client,
+ model: "claude-3-7-sonnet-20250219",
+ system: system_prompt,
+ messages: msg
+ )
+
+ sentence
+ end
+
+ def google_translate(text, to, key) do
+ Logger.debug(@google_translate_url)
+ query = "#{@google_translate_url}?q=#{URI.encode(text)}&target=#{to}&key=#{key}"
+ # Capture errors
+ try do
+ {:ok, response} = HTTPoison.get(query, timeout: 10_000)
+
+ if response.status_code == 200 do
+ # Extract the translation
+ translation_from(response.body)
+ else
+ {:error, "Translation failed at #{query}"}
+ end
+ catch
+ kind, reason ->
+ Logger.debug("Failed to translate: #{inspect(kind)} , #{inspect(reason)}")
+ {:error, "Translation failed at #{query}"}
+ end
+ end
+
+ defp translation_from(body) do
+ result = JSON.decode!(body)
+
+ case result["data"]["translations"] do
+ [translation | _] -> {:ok, translation["translatedText"]}
+ [] -> {:error, "No translation found"}
+ end
+ end
+end
diff --git a/vocab_assistant/lib/vocab_assistant/application.ex b/vocab_assistant/lib/vocab_assistant/application.ex
new file mode 100644
index 0000000..9f7b576
--- /dev/null
+++ b/vocab_assistant/lib/vocab_assistant/application.ex
@@ -0,0 +1,36 @@
+defmodule VocabAssistant.Application do
+ # See https://hexdocs.pm/elixir/Application.html
+ # for more information on OTP Applications
+ @moduledoc false
+
+ use Application
+
+ @impl true
+ def start(_type, _args) do
+ children = [
+ VocabAssistantWeb.Telemetry,
+ VocabAssistant.Repo,
+ {DNSCluster, query: Application.get_env(:vocab_assistant, :dns_cluster_query) || :ignore},
+ {Phoenix.PubSub, name: VocabAssistant.PubSub},
+ # Start the Finch HTTP client for sending emails
+ {Finch, name: VocabAssistant.Finch},
+ # Start a worker by calling: VocabAssistant.Worker.start_link(arg)
+ # {VocabAssistant.Worker, arg},
+ # Start to serve requests, typically the last entry
+ VocabAssistantWeb.Endpoint
+ ]
+
+ # See https://hexdocs.pm/elixir/Supervisor.html
+ # for other strategies and supported options
+ opts = [strategy: :one_for_one, name: VocabAssistant.Supervisor]
+ Supervisor.start_link(children, opts)
+ end
+
+ # Tell Phoenix to update the endpoint configuration
+ # whenever the application is updated.
+ @impl true
+ def config_change(changed, _new, removed) do
+ VocabAssistantWeb.Endpoint.config_change(changed, removed)
+ :ok
+ end
+end
diff --git a/vocab_assistant/lib/vocab_assistant/mailer.ex b/vocab_assistant/lib/vocab_assistant/mailer.ex
new file mode 100644
index 0000000..fe04b6c
--- /dev/null
+++ b/vocab_assistant/lib/vocab_assistant/mailer.ex
@@ -0,0 +1,3 @@
+defmodule VocabAssistant.Mailer do
+ use Swoosh.Mailer, otp_app: :vocab_assistant
+end
diff --git a/vocab_assistant/lib/vocab_assistant/repo.ex b/vocab_assistant/lib/vocab_assistant/repo.ex
new file mode 100644
index 0000000..d872998
--- /dev/null
+++ b/vocab_assistant/lib/vocab_assistant/repo.ex
@@ -0,0 +1,5 @@
+defmodule VocabAssistant.Repo do
+ use Ecto.Repo,
+ otp_app: :vocab_assistant,
+ adapter: Ecto.Adapters.Postgres
+end
diff --git a/vocab_assistant/lib/vocab_assistant_web.ex b/vocab_assistant/lib/vocab_assistant_web.ex
new file mode 100644
index 0000000..b7efbee
--- /dev/null
+++ b/vocab_assistant/lib/vocab_assistant_web.ex
@@ -0,0 +1,113 @@
+defmodule VocabAssistantWeb do
+ @moduledoc """
+ The entrypoint for defining your web interface, such
+ as controllers, components, channels, and so on.
+
+ This can be used in your application as:
+
+ use VocabAssistantWeb, :controller
+ use VocabAssistantWeb, :html
+
+ The definitions below will be executed for every controller,
+ component, etc, so keep them short and clean, focused
+ on imports, uses and aliases.
+
+ Do NOT define functions inside the quoted expressions
+ below. Instead, define additional modules and import
+ those modules here.
+ """
+
+ def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)
+
+ def router do
+ quote do
+ use Phoenix.Router, helpers: false
+
+ # Import common connection and controller functions to use in pipelines
+ import Plug.Conn
+ import Phoenix.Controller
+ import Phoenix.LiveView.Router
+ end
+ end
+
+ def channel do
+ quote do
+ use Phoenix.Channel
+ end
+ end
+
+ def controller do
+ quote do
+ use Phoenix.Controller,
+ formats: [:html, :json],
+ layouts: [html: VocabAssistantWeb.Layouts]
+
+ import Plug.Conn
+ import VocabAssistantWeb.Gettext
+
+ unquote(verified_routes())
+ end
+ end
+
+ def live_view do
+ quote do
+ use Phoenix.LiveView,
+ layout: {VocabAssistantWeb.Layouts, :app}
+
+ unquote(html_helpers())
+ end
+ end
+
+ def live_component do
+ quote do
+ use Phoenix.LiveComponent
+
+ unquote(html_helpers())
+ end
+ end
+
+ def html do
+ quote do
+ use Phoenix.Component
+
+ # Import convenience functions from controllers
+ import Phoenix.Controller,
+ only: [get_csrf_token: 0, view_module: 1, view_template: 1]
+
+ # Include general helpers for rendering HTML
+ unquote(html_helpers())
+ end
+ end
+
+ defp html_helpers do
+ quote do
+ # HTML escaping functionality
+ import Phoenix.HTML
+ # Core UI components and translation
+ import VocabAssistantWeb.CoreComponents
+ import VocabAssistantWeb.Gettext
+
+ # Shortcut for generating JS commands
+ alias Phoenix.LiveView.JS
+
+ # Routes generation with the ~p sigil
+ unquote(verified_routes())
+ end
+ end
+
+ def verified_routes do
+ quote do
+ use Phoenix.VerifiedRoutes,
+ endpoint: VocabAssistantWeb.Endpoint,
+ router: VocabAssistantWeb.Router,
+ statics: VocabAssistantWeb.static_paths()
+ end
+ end
+
+ @doc """
+ When used, dispatch to the appropriate controller/live_view/etc.
+ """
+ defmacro __using__(which) when is_atom(which) do
+ apply(__MODULE__, which, [])
+ end
+end
diff --git a/vocab_assistant/lib/vocab_assistant_web/components/core_components.ex b/vocab_assistant/lib/vocab_assistant_web/components/core_components.ex
new file mode 100644
index 0000000..e0535ac
--- /dev/null
+++ b/vocab_assistant/lib/vocab_assistant_web/components/core_components.ex
@@ -0,0 +1,676 @@
+defmodule VocabAssistantWeb.CoreComponents do
+ @moduledoc """
+ Provides core UI components.
+
+ At first glance, this module may seem daunting, but its goal is to provide
+ core building blocks for your application, such as modals, tables, and
+ forms. The components consist mostly of markup and are well-documented
+ with doc strings and declarative assigns. You may customize and style
+ them in any way you want, based on your application growth and needs.
+
+ The default components use Tailwind CSS, a utility-first CSS framework.
+ See the [Tailwind CSS documentation](https://tailwindcss.com) to learn
+ how to customize them or feel free to swap in another framework altogether.
+
+ Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage.
+ """
+ use Phoenix.Component
+
+ alias Phoenix.LiveView.JS
+ import VocabAssistantWeb.Gettext
+
+ @doc """
+ Renders a modal.
+
+ ## Examples
+
+ <.modal id="confirm-modal">
+ This is a modal.
+
+
+ JS commands may be passed to the `:on_cancel` to configure
+ the closing/cancel event, for example:
+
+ <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}>
+ This is another modal.
+
+
+ """
+ attr :id, :string, required: true
+ attr :show, :boolean, default: false
+ attr :on_cancel, JS, default: %JS{}
+ slot :inner_block, required: true
+
+ def modal(assigns) do
+ ~H"""
+
+ """
+ end
+
+ @doc """
+ Shows the flash group with standard titles and content.
+
+ ## Examples
+
+ <.flash_group flash={@flash} />
+ """
+ attr :flash, :map, required: true, doc: "the map of flash messages"
+ attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
+
+ def flash_group(assigns) do
+ ~H"""
+
+ <.flash kind={:info} title={gettext("Success!")} flash={@flash} />
+ <.flash kind={:error} title={gettext("Error!")} flash={@flash} />
+ <.flash
+ id="client-error"
+ kind={:error}
+ title={gettext("We can't find the internet")}
+ phx-disconnected={show(".phx-client-error #client-error")}
+ phx-connected={hide("#client-error")}
+ hidden
+ >
+ <%= gettext("Attempting to reconnect") %>
+ <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
+
+
+ <.flash
+ id="server-error"
+ kind={:error}
+ title={gettext("Something went wrong!")}
+ phx-disconnected={show(".phx-server-error #server-error")}
+ phx-connected={hide("#server-error")}
+ hidden
+ >
+ <%= gettext("Hang in there while we get back on track") %>
+ <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
+
+
+ """
+ end
+
+ @doc """
+ Renders a simple form.
+
+ ## Examples
+
+ <.simple_form for={@form} phx-change="validate" phx-submit="save">
+ <.input field={@form[:email]} label="Email"/>
+ <.input field={@form[:username]} label="Username" />
+ <:actions>
+ <.button>Save
+
+
+ """
+ attr :for, :any, required: true, doc: "the datastructure for the form"
+ attr :as, :any, default: nil, doc: "the server side parameter to collect all input under"
+
+ attr :rest, :global,
+ include: ~w(autocomplete name rel action enctype method novalidate target multipart),
+ doc: "the arbitrary HTML attributes to apply to the form tag"
+
+ slot :inner_block, required: true
+ slot :actions, doc: "the slot for form actions, such as a submit button"
+
+ def simple_form(assigns) do
+ ~H"""
+ <.form :let={f} for={@for} as={@as} {@rest}>
+
+ <%= render_slot(@inner_block, f) %>
+
+ <%= render_slot(action, f) %>
+
+
+
+ """
+ end
+
+ @doc """
+ Renders a button.
+
+ ## Examples
+
+ <.button>Send!
+ <.button phx-click="go" class="ml-2">Send!
+ """
+ attr :type, :string, default: nil
+ attr :class, :string, default: nil
+ attr :rest, :global, include: ~w(disabled form name value)
+
+ slot :inner_block, required: true
+
+ def button(assigns) do
+ ~H"""
+
+ """
+ end
+
+ @doc """
+ Renders an input with label and error messages.
+
+ A `Phoenix.HTML.FormField` may be passed as argument,
+ which is used to retrieve the input name, id, and values.
+ Otherwise all attributes may be passed explicitly.
+
+ ## Types
+
+ This function accepts all HTML input types, considering that:
+
+ * You may also set `type="select"` to render a `