diff --git a/examples/crystal/.editorconfig b/examples/crystal/.editorconfig new file mode 100644 index 0000000000..163eb75c85 --- /dev/null +++ b/examples/crystal/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.cr] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/examples/crystal/.gitignore b/examples/crystal/.gitignore new file mode 100644 index 0000000000..0bb75ea03f --- /dev/null +++ b/examples/crystal/.gitignore @@ -0,0 +1,5 @@ +/docs/ +/lib/ +/bin/ +/.shards/ +*.dwarf diff --git a/examples/crystal/README.md b/examples/crystal/README.md new file mode 100644 index 0000000000..bee3491979 --- /dev/null +++ b/examples/crystal/README.md @@ -0,0 +1,27 @@ +# crystal + +TODO: Write a description here + +## Installation + +TODO: Write installation instructions here + +## Usage + +TODO: Write usage instructions here + +## Development + +TODO: Write development instructions here + +## Contributing + +1. Fork it () +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request + +## Contributors + +- [Jake Runzer](https://github.com/your-github-user) - creator and maintainer diff --git a/examples/crystal/shard.lock b/examples/crystal/shard.lock new file mode 100644 index 0000000000..4f3e149cca --- /dev/null +++ b/examples/crystal/shard.lock @@ -0,0 +1,2 @@ +version: 2.0 +shards: {} diff --git a/examples/crystal/shard.yml b/examples/crystal/shard.yml new file mode 100644 index 0000000000..9757dbb9f4 --- /dev/null +++ b/examples/crystal/shard.yml @@ -0,0 +1,13 @@ +name: crystal +version: 0.1.0 + +authors: + - Jake Runzer + +targets: + crystal: + main: src/crystal.cr + +crystal: 1.4.1 + +license: MIT diff --git a/examples/crystal/spec/crystal_spec.cr b/examples/crystal/spec/crystal_spec.cr new file mode 100644 index 0000000000..4369bd5b02 --- /dev/null +++ b/examples/crystal/spec/crystal_spec.cr @@ -0,0 +1,9 @@ +require "./spec_helper" + +describe Crystal do + # TODO: Write tests + + it "works" do + false.should eq(true) + end +end diff --git a/examples/crystal/spec/spec_helper.cr b/examples/crystal/spec/spec_helper.cr new file mode 100644 index 0000000000..3b3837a52d --- /dev/null +++ b/examples/crystal/spec/spec_helper.cr @@ -0,0 +1,2 @@ +require "spec" +require "../src/crystal" diff --git a/examples/crystal/src/crystal.cr b/examples/crystal/src/crystal.cr new file mode 100644 index 0000000000..ae98b291b0 --- /dev/null +++ b/examples/crystal/src/crystal.cr @@ -0,0 +1,4 @@ +# TODO: Write documentation for `Crystal` +module Crystal + puts "Hello from Crystal!" +end diff --git a/src/lib.rs b/src/lib.rs index 3d541e1e83..da921adc24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,8 +6,9 @@ use crate::{ AppBuilderOptions, }, providers::{ - deno::DenoProvider, go::GolangProvider, haskell::HaskellStackProvider, node::NodeProvider, - python::PythonProvider, rust::RustProvider, + crystal::CrystalProvider, deno::DenoProvider, go::GolangProvider, + haskell::HaskellStackProvider, node::NodeProvider, python::PythonProvider, + rust::RustProvider, }, }; use anyhow::{bail, Result}; @@ -25,6 +26,7 @@ pub fn get_providers() -> Vec<&'static dyn Provider> { &RustProvider {}, &PythonProvider {}, &HaskellStackProvider {}, + &CrystalProvider {}, ] } diff --git a/src/providers/crystal.rs b/src/providers/crystal.rs new file mode 100644 index 0000000000..3e7f56c6d4 --- /dev/null +++ b/src/providers/crystal.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; + +use super::Provider; +use crate::nixpacks::{ + app::App, + environment::Environment, + nix::Pkg, + phase::{BuildPhase, InstallPhase, SetupPhase, StartPhase}, +}; +use anyhow::{Context, Result}; +use serde::Deserialize; + +// https://github.com/crystal-lang/shards/blob/master/docs/shard.yml.adoc +#[derive(Deserialize, Debug)] +pub struct ShardYaml { + pub name: String, + pub targets: HashMap>, +} + +pub struct CrystalProvider {} + +impl Provider for CrystalProvider { + fn name(&self) -> &str { + "crystal" + } + + fn detect(&self, app: &App, _env: &Environment) -> Result { + Ok(app.includes_file("shard.yml")) + } + + fn setup(&self, _app: &App, _env: &Environment) -> Result> { + Ok(Some(SetupPhase::new(vec![ + Pkg::new("crystal"), + Pkg::new("shards"), + ]))) + } + + fn install(&self, _app: &App, _env: &Environment) -> Result> { + Ok(Some(InstallPhase::new("shards install".to_string()))) + } + + fn build(&self, _app: &App, _env: &Environment) -> Result> { + Ok(Some(BuildPhase::new("shards build --release".to_string()))) + } + + fn start(&self, app: &App, _env: &Environment) -> Result> { + let config = CrystalProvider::get_config(app)?; + let target_names = config.targets.keys().cloned().collect::>(); + let start_phase = StartPhase::new(format!( + "./bin/{}", + target_names + .get(0) + .ok_or_else(|| anyhow::anyhow!("Unable to get executable name"))? + )); + + Ok(Some(start_phase)) + } +} + +impl CrystalProvider { + fn get_config(app: &App) -> Result { + app.read_yaml::("shard.yml") + .context("Reading shard.yml") + } +} diff --git a/src/providers/mod.rs b/src/providers/mod.rs index dabcf201b1..43b49bc407 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -5,6 +5,7 @@ use crate::nixpacks::{ }; use anyhow::Result; +pub mod crystal; pub mod deno; pub mod go; pub mod haskell; diff --git a/tests/docker_run_tests.rs b/tests/docker_run_tests.rs index 708d20be78..c7e71197f7 100644 --- a/tests/docker_run_tests.rs +++ b/tests/docker_run_tests.rs @@ -197,6 +197,13 @@ fn test_haskell_stack() { assert!(output.contains("Hello from Haskell")); } +#[test] +fn test_crystal() { + let name = simple_build("./examples/crystal"); + let output = run_image(name); + assert!(output.contains("Hello from Crystal")); +} + #[test] fn test_cowsay() { let name = Uuid::new_v4().to_string(); diff --git a/tests/generate_plan_tests.rs b/tests/generate_plan_tests.rs index 6fc6de8104..7a9c98ab69 100644 --- a/tests/generate_plan_tests.rs +++ b/tests/generate_plan_tests.rs @@ -420,6 +420,28 @@ fn test_haskell_stack() -> Result<()> { Ok(()) } +#[test] +fn test_crystal() -> Result<()> { + let plan = gen_plan( + "./examples/crystal", + Vec::new(), + None, + None, + Vec::new(), + false, + )?; + assert_eq!( + plan.install.unwrap().cmd, + Some("shards install".to_string()) + ); + assert_eq!( + plan.build.unwrap().cmd, + Some("shards build --release".to_string()) + ); + assert_eq!(plan.start.unwrap().cmd, Some("./bin/crystal".to_string())); + Ok(()) +} + #[test] fn test_overriding_environment_variables() -> Result<()> { let plan = gen_plan(