summary refs log tree commit diff
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@gmail.com>2020-09-01 20:25:26 -0700
committerIrene Knapp <ireneista@gmail.com>2020-09-01 20:25:26 -0700
commit0c8e06d588038ae6e24d1898bd33b3ee5a938640 (patch)
tree74833939e37908366fe2cb71d639dbe641e054a6
Initial.
-rw-r--r--.envrc1
-rw-r--r--.gitignore5
-rw-r--r--Cargo.lock5
-rw-r--r--Cargo.nix619
-rw-r--r--Cargo.toml7
-rw-r--r--default.nix15
-rw-r--r--shell.nix17
-rw-r--r--src/error.rs24
-rw-r--r--src/main.rs25
9 files changed, 718 insertions, 0 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..be81fed
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+eval "$(lorri direnv)"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..31158d2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/target
+/result
+/result-*
+**/*.rs.bk
+*.swp
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..8d60690
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,5 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "shell"
+version = "0.1.0"
diff --git a/Cargo.nix b/Cargo.nix
new file mode 100644
index 0000000..e59159d
--- /dev/null
+++ b/Cargo.nix
@@ -0,0 +1,619 @@
+
+# This file was @generated by crate2nix 0.8.0 with the command:
+#   "generate"
+# See https://github.com/kolloch/crate2nix for more info.
+
+{ nixpkgs ? <nixpkgs>
+, pkgs ? import nixpkgs { config = {}; }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+, buildRustCrate ? pkgs.buildRustCrate
+  # This is used as the `crateOverrides` argument for `buildRustCrate`.
+, defaultCrateOverrides ? pkgs.defaultCrateOverrides
+  # The features to enable for the root_crate or the workspace_members.
+, rootFeatures ? [ "default" ]
+  # If true, throw errors instead of issueing deprecation warnings.
+, strictDeprecation ? false
+  # Whether to perform release builds: longer compile times, faster binaries.
+, release ? true
+}:
+
+rec {
+  #
+  # "public" attributes that we attempt to keep stable with new versions of crate2nix.
+  #
+
+  rootCrate = rec {
+    packageId = "shell";
+
+    # Use this attribute to refer to the derivation building your root crate package.
+    # You can override the features with rootCrate.build.override { features = [ "default" "feature1" ... ]; }.
+    build = internal.buildRustCrateWithFeatures {
+      inherit packageId;
+    };
+
+    # Debug support which might change between releases.
+    # File a bug if you depend on any for non-debug work!
+    debug = internal.debugCrate { inherit packageId; };
+  };
+  root_crate =
+    internal.deprecationWarning 
+      "root_crate is deprecated since crate2nix 0.4. Please use rootCrate instead." 
+      rootCrate.build;
+  # Refer your crate build derivation by name here.
+  # You can override the features with
+  # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }.
+  workspaceMembers = {
+    "shell" = rec {
+      packageId = "shell";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "shell";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+  };
+  workspace_members =
+    internal.deprecationWarning
+      "workspace_members is deprecated in crate2nix 0.4. Please use workspaceMembers instead."
+      lib.mapAttrs (n: v: v.build) workspaceMembers;
+
+  #
+  # "internal" ("private") attributes that may change in every new version of crate2nix.
+  #
+
+  internal = rec {
+    # Build and dependency information for crates.
+    # Many of the fields are passed one-to-one to buildRustCrate.
+    #
+    # Noteworthy:
+    # * `dependencies`/`buildDependencies`: similar to the corresponding fields for buildRustCrate.
+    #   but with additional information which is used during dependency/feature resolution.
+    # * `resolvedDependencies`: the selected default features reported by cargo - only included for debugging.
+    # * `devDependencies` as of now not used by `buildRustCrate` but used to
+    #   inject test dependencies into the build
+
+    crates = {
+      "shell" = rec {
+        crateName = "shell";
+        version = "0.1.0";
+        edition = "2018";
+        crateBin = [
+          { name = "shell"; path = "src/main.rs"; }
+        ];
+        src = (builtins.filterSource sourceFilter ./.);
+        authors = [
+          "Irene Knapp <ireneista@gmail.com>"
+        ];
+        
+      };
+    };
+
+    #
+# crate2nix/default.nix (excerpt start)
+#
+
+  /* Target (platform) data for conditional dependencies.
+     This corresponds roughly to what buildRustCrate is setting.
+  */
+  defaultTarget = {
+    unix = true;
+    windows = false;
+    fuchsia = true;
+    test = false;
+
+    # This doesn't appear to be officially documented anywhere yet.
+    # See https://github.com/rust-lang-nursery/rust-forge/issues/101.
+    os = if stdenv.hostPlatform.isDarwin
+    then "macos"
+    else stdenv.hostPlatform.parsed.kernel.name;
+    arch = stdenv.hostPlatform.parsed.cpu.name;
+    family = "unix";
+    env = "gnu";
+    endian =
+      if stdenv.hostPlatform.parsed.cpu.significantByte.name == "littleEndian"
+      then "little" else "big";
+    pointer_width = toString stdenv.hostPlatform.parsed.cpu.bits;
+    vendor = stdenv.hostPlatform.parsed.vendor.name;
+    debug_assertions = false;
+  };
+
+  /* Filters common temp files and build files. */
+  # TODO(pkolloch): Substitute with gitignore filter
+  sourceFilter = name: type:
+    let
+      baseName = builtins.baseNameOf (builtins.toString name);
+    in
+      ! (
+        # Filter out git
+        baseName == ".gitignore"
+        || (type == "directory" && baseName == ".git")
+
+        # Filter out build results
+        || (
+          type == "directory" && (
+            baseName == "target"
+            || baseName == "_site"
+            || baseName == ".sass-cache"
+            || baseName == ".jekyll-metadata"
+            || baseName == "build-artifacts"
+          )
+        )
+
+        # Filter out nix-build result symlinks        
+        || (
+          type == "symlink" && lib.hasPrefix "result" baseName
+        )
+
+        # Filter out IDE config
+        || (
+          type == "directory" && (
+            baseName == ".idea" || baseName == ".vscode"
+          )
+        ) || lib.hasSuffix ".iml" baseName
+
+        # Filter out nix build files
+        || baseName == "Cargo.nix"
+
+        # Filter out editor backup / swap files.
+        || lib.hasSuffix "~" baseName
+        || builtins.match "^\\.sw[a-z]$$" baseName != null
+        || builtins.match "^\\..*\\.sw[a-z]$$" baseName != null
+        || lib.hasSuffix ".tmp" baseName
+        || lib.hasSuffix ".bak" baseName
+        || baseName == "tests.nix"
+      );
+
+  /* Returns a crate which depends on successful test execution
+     of crate given as the second argument.
+
+     testCrateFlags: list of flags to pass to the test exectuable
+     testInputs: list of packages that should be available during test execution
+  */
+  crateWithTest = { crate, testCrate, testCrateFlags, testInputs }:
+    assert builtins.typeOf testCrateFlags == "list";
+    assert builtins.typeOf testInputs == "list";
+    let
+      # override the `crate` so that it will build and execute tests instead of
+      # building the actual lib and bin targets We just have to pass `--test`
+      # to rustc and it will do the right thing.  We execute the tests and copy
+      # their log and the test executables to $out for later inspection.
+      test = let
+        drv = testCrate.override (
+          _: {
+            buildTests = true;
+          }
+        );
+      in
+        pkgs.runCommand "run-tests-${testCrate.name}" {
+          inherit testCrateFlags;
+          buildInputs = testInputs;
+        } ''
+          set -ex
+          cd ${crate.src}
+          for file in ${drv}/tests/*; do
+            $file $testCrateFlags 2>&1 | tee -a $out
+          done
+        '';
+    in
+      crate.overrideAttrs (
+        old: {
+          checkPhase = ''
+            test -e ${test}
+          '';
+          passthru = (old.passthru or {}) // {
+            inherit test;
+          };
+        }
+      );
+
+  /* A restricted overridable version of builtRustCratesWithFeatures. */
+  buildRustCrateWithFeatures =
+    { packageId
+    , features ? rootFeatures
+    , crateOverrides ? defaultCrateOverrides
+    , buildRustCrateFunc ? (
+        if crateOverrides == pkgs.defaultCrateOverrides
+        then buildRustCrate
+        else buildRustCrate.override {
+          defaultCrateOverrides = crateOverrides;
+        }
+      )
+    , runTests ? false
+    , testCrateFlags ? []
+    , testInputs ? []
+    }:
+      lib.makeOverridable
+        (
+          { features, crateOverrides, runTests, testCrateFlags, testInputs }:
+            let
+              builtRustCrates = builtRustCratesWithFeatures {
+                inherit packageId features buildRustCrateFunc;
+                runTests = false;
+              };
+              builtTestRustCrates = builtRustCratesWithFeatures {
+                inherit packageId features buildRustCrateFunc;
+                runTests = true;
+              };
+              drv = builtRustCrates.${packageId};
+              testDrv = builtTestRustCrates.${packageId};
+            in
+              if runTests then
+                crateWithTest {
+                  crate = drv;
+                  testCrate = testDrv;
+                  inherit testCrateFlags testInputs;
+                }
+              else drv
+        )
+        { inherit features crateOverrides runTests testCrateFlags testInputs; };
+
+  /* Returns an attr set with packageId mapped to the result of buildRustCrateFunc 
+     for the corresponding crate. 
+  */
+  builtRustCratesWithFeatures =
+    { packageId
+    , features
+    , crateConfigs ? crates
+    , buildRustCrateFunc
+    , runTests
+    , target ? defaultTarget
+    } @ args:
+      assert (builtins.isAttrs crateConfigs);
+      assert (builtins.isString packageId);
+      assert (builtins.isList features);
+      assert (builtins.isAttrs target);
+      assert (builtins.isBool runTests);
+      let
+        rootPackageId = packageId;
+        mergedFeatures = mergePackageFeatures (
+          args // {
+            inherit rootPackageId;
+            target = target // { test = runTests; };
+          }
+        );
+
+        buildByPackageId = packageId: buildByPackageIdImpl packageId;
+
+        # Memoize built packages so that reappearing packages are only built once.
+        builtByPackageId =
+          lib.mapAttrs (packageId: value: buildByPackageId packageId) crateConfigs;
+
+        buildByPackageIdImpl = packageId:
+          let
+            features = mergedFeatures."${packageId}" or [];
+            crateConfig' = crateConfigs."${packageId}";
+            crateConfig =
+              builtins.removeAttrs crateConfig' [ "resolvedDefaultFeatures" "devDependencies" ];
+            devDependencies =
+              lib.optionals
+                (runTests && packageId == rootPackageId)
+                (crateConfig'.devDependencies or []);
+            dependencies =
+              dependencyDerivations {
+                inherit builtByPackageId features target;
+                dependencies =
+                  (crateConfig.dependencies or [])
+                  ++ devDependencies;
+              };
+            buildDependencies =
+              dependencyDerivations {
+                inherit builtByPackageId features target;
+                dependencies = crateConfig.buildDependencies or [];
+              };
+
+            filterEnabledDependenciesForThis = dependencies: filterEnabledDependencies {
+              inherit dependencies features target;
+            };
+
+            dependenciesWithRenames =
+              lib.filter (d: d ? "rename") (
+                filterEnabledDependenciesForThis
+                  (
+                    (crateConfig.buildDependencies or [])
+                    ++ (crateConfig.dependencies or [])
+                    ++ devDependencies
+                  )
+              );
+
+            crateRenames =
+              builtins.listToAttrs
+                (map (d: { name = d.name; value = d.rename; }) dependenciesWithRenames);
+          in
+            buildRustCrateFunc (
+              crateConfig // {
+                src = crateConfig.src or (
+                  pkgs.fetchurl {
+                    name = "${crateConfig.crateName}-${crateConfig.version}.tar.gz";
+                    url = "https://crates.io/api/v1/crates/${crateConfig.crateName}/${crateConfig.version}/download";
+                    sha256 = crateConfig.sha256;
+                  }
+                );
+                inherit features dependencies buildDependencies crateRenames release;
+              }
+            );
+      in
+        builtByPackageId;
+
+  /* Returns the actual derivations for the given dependencies. */
+  dependencyDerivations =
+    { builtByPackageId
+    , features
+    , dependencies
+    , target
+    }:
+      assert (builtins.isAttrs builtByPackageId);
+      assert (builtins.isList features);
+      assert (builtins.isList dependencies);
+      assert (builtins.isAttrs target);
+      let
+        enabledDependencies = filterEnabledDependencies {
+          inherit dependencies features target;
+        };
+        depDerivation = dependency: builtByPackageId.${dependency.packageId};
+      in
+        map depDerivation enabledDependencies;
+
+  /* Returns a sanitized version of val with all values substituted that cannot
+     be serialized as JSON. 
+  */
+  sanitizeForJson = val:
+    if builtins.isAttrs val
+    then lib.mapAttrs (n: v: sanitizeForJson v) val
+    else if builtins.isList val
+    then builtins.map sanitizeForJson val
+    else if builtins.isFunction val
+    then "function"
+    else val;
+
+  /* Returns various tools to debug a crate. */
+  debugCrate = { packageId, target ? defaultTarget }:
+    assert (builtins.isString packageId);
+    let
+      debug = rec {
+        # The built tree as passed to buildRustCrate.
+        buildTree = buildRustCrateWithFeatures {
+          buildRustCrateFunc = lib.id;
+          inherit packageId;
+        };
+        sanitizedBuildTree = sanitizeForJson buildTree;
+        dependencyTree = sanitizeForJson (
+          buildRustCrateWithFeatures {
+            buildRustCrateFunc = crate: {
+              "01_crateName" = crate.crateName or false;
+              "02_features" = crate.features or [];
+              "03_dependencies" = crate.dependencies or [];
+            };
+            inherit packageId;
+          }
+        );
+        mergedPackageFeatures = mergePackageFeatures {
+          features = rootFeatures;
+          inherit packageId target;
+        };
+        diffedDefaultPackageFeatures = diffDefaultPackageFeatures {
+          inherit packageId target;
+        };
+      };
+    in
+      { internal = debug; };
+
+  /* Returns differences between cargo default features and crate2nix default
+     features.
+   
+     This is useful for verifying the feature resolution in crate2nix.
+  */
+  diffDefaultPackageFeatures =
+    { crateConfigs ? crates
+    , packageId
+    , target
+    }:
+      assert (builtins.isAttrs crateConfigs);
+      let
+        prefixValues = prefix: lib.mapAttrs (n: v: { "${prefix}" = v; });
+        mergedFeatures =
+          prefixValues
+            "crate2nix"
+            (mergePackageFeatures { inherit crateConfigs packageId target; features = [ "default" ]; });
+        configs = prefixValues "cargo" crateConfigs;
+        combined = lib.foldAttrs (a: b: a // b) {} [ mergedFeatures configs ];
+        onlyInCargo =
+          builtins.attrNames
+            (lib.filterAttrs (n: v: !(v ? "crate2nix") && (v ? "cargo")) combined);
+        onlyInCrate2Nix =
+          builtins.attrNames
+            (lib.filterAttrs (n: v: (v ? "crate2nix") && !(v ? "cargo")) combined);
+        differentFeatures = lib.filterAttrs
+          (
+            n: v:
+              (v ? "crate2nix")
+              && (v ? "cargo")
+              && (v.crate2nix.features or []) != (v."cargo".resolved_default_features or [])
+          )
+          combined;
+      in
+        builtins.toJSON {
+          inherit onlyInCargo onlyInCrate2Nix differentFeatures;
+        };
+
+  /* Returns an attrset mapping packageId to the list of enabled features.
+
+     If multiple paths to a dependency enable different features, the
+     corresponding feature sets are merged. Features in rust are additive.
+  */
+  mergePackageFeatures =
+    { crateConfigs ? crates
+    , packageId
+    , rootPackageId ? packageId
+    , features ? rootFeatures
+    , dependencyPath ? [ crates.${packageId}.crateName ]
+    , featuresByPackageId ? {}
+    , target
+      # Adds devDependencies to the crate with rootPackageId.
+    , runTests ? false
+    , ...
+    } @ args:
+      assert (builtins.isAttrs crateConfigs);
+      assert (builtins.isString packageId);
+      assert (builtins.isString rootPackageId);
+      assert (builtins.isList features);
+      assert (builtins.isList dependencyPath);
+      assert (builtins.isAttrs featuresByPackageId);
+      assert (builtins.isAttrs target);
+      assert (builtins.isBool runTests);
+      let
+        crateConfig = crateConfigs."${packageId}" or (builtins.throw "Package not found: ${packageId}");
+        expandedFeatures = expandFeatures (crateConfig.features or {}) features;
+
+        depWithResolvedFeatures = dependency:
+          let
+            packageId = dependency.packageId;
+            features = dependencyFeatures expandedFeatures dependency;
+          in
+            { inherit packageId features; };
+
+        resolveDependencies = cache: path: dependencies:
+          assert (builtins.isAttrs cache);
+          assert (builtins.isList dependencies);
+          let
+            enabledDependencies = filterEnabledDependencies {
+              inherit dependencies target;
+              features = expandedFeatures;
+            };
+            directDependencies = map depWithResolvedFeatures enabledDependencies;
+            foldOverCache = op: lib.foldl op cache directDependencies;
+          in
+            foldOverCache
+              (
+                cache: { packageId, features }:
+                  let
+                    cacheFeatures = cache.${packageId} or [];
+                    combinedFeatures = sortedUnique (cacheFeatures ++ features);
+                  in
+                    if cache ? ${packageId} && cache.${packageId} == combinedFeatures
+                    then cache
+                    else mergePackageFeatures {
+                      features = combinedFeatures;
+                      featuresByPackageId = cache;
+                      inherit crateConfigs packageId target runTests rootPackageId;
+                    }
+              );
+
+        cacheWithSelf =
+          let
+            cacheFeatures = featuresByPackageId.${packageId} or [];
+            combinedFeatures = sortedUnique (cacheFeatures ++ expandedFeatures);
+          in
+            featuresByPackageId // {
+              "${packageId}" = combinedFeatures;
+            };
+
+        cacheWithDependencies =
+          resolveDependencies cacheWithSelf "dep" (
+            crateConfig.dependencies or []
+            ++ lib.optionals
+              (runTests && packageId == rootPackageId)
+              (crateConfig.devDependencies or [])
+          );
+
+        cacheWithAll =
+          resolveDependencies
+            cacheWithDependencies "build"
+            (crateConfig.buildDependencies or []);
+      in
+        cacheWithAll;
+
+  /* Returns the enabled dependencies given the enabled features. */
+  filterEnabledDependencies = { dependencies, features, target }:
+    assert (builtins.isList dependencies);
+    assert (builtins.isList features);
+    assert (builtins.isAttrs target);
+
+    lib.filter
+      (
+        dep:
+          let
+            targetFunc = dep.target or (features: true);
+          in
+            targetFunc { inherit features target; }
+            && (
+              !(dep.optional or false)
+              || builtins.any (doesFeatureEnableDependency dep) features
+            )
+      )
+      dependencies;
+
+  /* Returns whether the given feature should enable the given dependency. */
+  doesFeatureEnableDependency = { name, rename ? null, ... }: feature:
+    let
+      prefix = "${name}/";
+      len = builtins.stringLength prefix;
+      startsWithPrefix = builtins.substring 0 len feature == prefix;
+    in
+      (rename == null && feature == name)
+      || (rename != null && rename == feature)
+      || startsWithPrefix;
+
+  /* Returns the expanded features for the given inputFeatures by applying the
+     rules in featureMap.
+
+     featureMap is an attribute set which maps feature names to lists of further
+     feature names to enable in case this feature is selected.
+  */
+  expandFeatures = featureMap: inputFeatures:
+    assert (builtins.isAttrs featureMap);
+    assert (builtins.isList inputFeatures);
+    let
+      expandFeature = feature:
+        assert (builtins.isString feature);
+        [ feature ] ++ (expandFeatures featureMap (featureMap."${feature}" or []));
+      outFeatures = builtins.concatMap expandFeature inputFeatures;
+    in
+      sortedUnique outFeatures;
+
+  /*
+     Returns the actual features for the given dependency.
+    
+     features: The features of the crate that refers this dependency.
+  */
+  dependencyFeatures = features: dependency:
+    assert (builtins.isList features);
+    assert (builtins.isAttrs dependency);
+    let
+      defaultOrNil = if dependency.usesDefaultFeatures or true
+      then [ "default" ]
+      else [];
+      explicitFeatures = dependency.features or [];
+      additionalDependencyFeatures =
+        let
+          dependencyPrefix = (dependency.rename or dependency.name) + "/";
+          dependencyFeatures =
+            builtins.filter (f: lib.hasPrefix dependencyPrefix f) features;
+        in
+          builtins.map (lib.removePrefix dependencyPrefix) dependencyFeatures;
+    in
+      defaultOrNil ++ explicitFeatures ++ additionalDependencyFeatures;
+
+  /* Sorts and removes duplicates from a list of strings. */
+  sortedUnique = features:
+    assert (builtins.isList features);
+    assert (builtins.all builtins.isString features);
+    let
+      outFeaturesSet = lib.foldl (set: feature: set // { "${feature}" = 1; }) {} features;
+      outFeaturesUnique = builtins.attrNames outFeaturesSet;
+    in
+      builtins.sort (a: b: a < b) outFeaturesUnique;
+
+  deprecationWarning = message: value:
+    if strictDeprecation
+    then builtins.throw "strictDeprecation enabled, aborting: ${message}"
+    else builtins.trace message value;
+
+  #
+  # crate2nix/default.nix (excerpt end)
+  #
+
+  };
+}
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..985f7dc
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "shell"
+version = "0.1.0"
+authors = ["Irene Knapp <ireneista@gmail.com>"]
+edition = "2018"
+
+[dependencies]
diff --git a/default.nix b/default.nix
new file mode 100644
index 0000000..30b44d2
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,15 @@
+# Use `nix-build`, not `nix build`, when doing development on this file. It
+# does a better job of propagating error messages from the build tools upward.
+
+let pkgs = import <nixpkgs> { };
+    crateOverrides = pkgs.defaultCrateOverrides.override {
+      openssl = pkgs.libressl;
+    } // { };
+    parameterOverrides = { };
+    cargo = pkgs.callPackage ./Cargo.nix {
+      defaultCrateOverrides = crateOverrides;
+    };
+in
+builtins.mapAttrs
+    (key: value: value.build.override parameterOverrides)
+    cargo.workspaceMembers
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..d675097
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,17 @@
+let
+  pkgs = import <nixpkgs> {};
+  crate2nix-src = pkgs.fetchFromGitHub {
+    owner = "kolloch";
+    repo = "crate2nix";
+    rev = "0.8.0";
+    sha256 = "17mmf5sqn0fmpqrf52icq92nf1sy5yacwx9vafk43piaq433ba56";
+  };
+  crate2nix = pkgs.callPackage (import crate2nix-src) { };
+in
+pkgs.mkShell {
+  buildInputs = with pkgs; [
+    cargo
+    crate2nix
+    rustc
+  ];
+}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..7054551
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,24 @@
+#[derive(Debug)]
+pub enum Error {
+    IO(std::io::Error),
+}
+
+impl std::error::Error for Error { }
+
+impl std::fmt::Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Error::IO(e) => e.fmt(f),
+        }
+    }
+}
+
+impl std::cmp::PartialEq for Error {
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Error::IO(_), Error::IO(_)) =>
+                false,
+        }
+    }
+}
+
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..5ca6d46
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,25 @@
+pub mod error;
+
+use error::Error;
+
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+
+fn main() -> Result<()> {
+    std::process::exit(match repl() {
+        Ok(()) => 0,
+        Err(ref e) => {
+            eprintln!("{}", e);
+            1
+        }
+    })
+}
+
+
+fn repl() -> Result<()> {
+    println!("Hello, terminal!");
+
+    Ok(())
+}
+