From 52d314ca28528db890bfec1df3f735e47f0cfb38 Mon Sep 17 00:00:00 2001
From: Juan Wajnerman <jwajnerman@manas.com.ar>
Date: Tue, 21 Oct 2014 00:35:14 -0300
Subject: [PATCH] Added "deps" command to install project dependencies (really
 simple, buggy, incomplete and just a proof of concept of the dependency
 manager)

---
 src/compiler/crystal/command.cr  | 15 ++++++++++
 src/project/dependency.cr        |  5 ++++
 src/project/dsl.cr               | 14 +++++++++
 src/project/github_dependency.cr | 30 +++++++++++++++++++
 src/project/project.cr           | 50 ++++++++++++++++++++++++++++++++
 5 files changed, 114 insertions(+)
 create mode 100644 src/project/dependency.cr
 create mode 100644 src/project/dsl.cr
 create mode 100644 src/project/github_dependency.cr
 create mode 100644 src/project/project.cr

diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr
index 1180485b59..115367bbb2 100644
--- a/src/compiler/crystal/command.cr
+++ b/src/compiler/crystal/command.cr
@@ -4,6 +4,7 @@ module Crystal::Command
             Command:\n    \
             build                    compile program file\n    \
             browser                  open an http server to browse program file\n    \
+            deps                     install project dependencies\n    \
             eval                     eval code\n    \
             hierarchy                show type hierarchy\n    \
             run (default)            compile and run program file\n    \
@@ -25,6 +26,9 @@ module Crystal::Command
         when "browser" == command
           options.shift
           browser options
+        when "deps".starts_with?(command)
+          options.shift
+          deps options
         when "eval".starts_with?(command)
           options.shift
           eval options
@@ -113,6 +117,17 @@ module Crystal::Command
     execute output_filename, arguments
   end
 
+  private def self.deps(options)
+    compiler = Compiler.new
+    sources = gather_sources(["Projectfile"])
+    sources.insert 0, Compiler::Source.new("require", %(require "project"))
+
+    output_filename = tempfile "deps"
+
+    result = compiler.compile sources, output_filename
+    execute output_filename, options
+  end
+
   private def self.types(options)
     result = compile_no_build "types", options
     Crystal.print_types result.original_node
diff --git a/src/project/dependency.cr b/src/project/dependency.cr
new file mode 100644
index 0000000000..9459e72a56
--- /dev/null
+++ b/src/project/dependency.cr
@@ -0,0 +1,5 @@
+abstract class Dependency
+  abstract def install
+  property locked_version
+  property! name
+end
diff --git a/src/project/dsl.cr b/src/project/dsl.cr
new file mode 100644
index 0000000000..f607544e0e
--- /dev/null
+++ b/src/project/dsl.cr
@@ -0,0 +1,14 @@
+class Project
+  class DSL::Deps
+    def initialize(@project)
+    end
+
+    def github(repository)
+      @project.dependencies << GitHubDependency.new(repository)
+    end
+  end
+end
+
+def deps
+  with Project::DSL::Deps.new(Project::INSTANCE) yield
+end
diff --git a/src/project/github_dependency.cr b/src/project/github_dependency.cr
new file mode 100644
index 0000000000..964818dbfb
--- /dev/null
+++ b/src/project/github_dependency.cr
@@ -0,0 +1,30 @@
+class GitHubDependency < Dependency
+  def initialize(repo)
+    unless repo =~ /(.*)\/(.*)/
+      raise ProjectError.new("Invalid GitHub repository definition: #{repo}")
+    end
+
+    @author = $1
+    @name = @repository = $2
+    @target_dir = ".deps/#{@repository}"
+  end
+
+  def install
+    unless Dir.exists?(@target_dir)
+      `git clone git@github.com:#{@author}/#{@repository}.git #{@target_dir}`
+    end
+    `ln -sf ../#{@target_dir}/src libs/#{@repository}`
+
+    if @locked_version &&
+      if current_version != @locked_version
+        `git -C #{@target_dir} checkout -q #{@locked_version}`
+      end
+    else
+      @locked_version = current_version
+    end
+  end
+
+  def current_version
+    `git -C #{@target_dir} rev-parse HEAD`.chomp
+  end
+end
diff --git a/src/project/project.cr b/src/project/project.cr
new file mode 100644
index 0000000000..35a4ff9e59
--- /dev/null
+++ b/src/project/project.cr
@@ -0,0 +1,50 @@
+require "json"
+require "./*"
+
+class Project
+  INSTANCE = Project.new
+  property dependencies
+
+  def initialize
+    @dependencies = [] of Dependency
+  end
+
+  def install_deps
+    # Prepare required directories
+    Dir.mkdir_p ".deps"
+    Dir.mkdir_p "libs"
+
+    # Load lockfile
+    if File.exists?(".deps.lock")
+      lock = Json.parse(File.read(".deps.lock")) as Hash
+      @dependencies.each do |dep|
+        if locked_version = lock[dep.name]?
+          dep.locked_version = locked_version as String
+        end
+      end
+    end
+
+    # Install al dependencies
+    @dependencies.each &.install
+
+    # Save lockfile
+    lock = {} of String => String
+    @dependencies.each do |dep|
+      lock[dep.name] = dep.locked_version.not_nil!
+    end
+    File.write(".deps.lock", lock.to_json)
+  end
+end
+
+class ProjectError < Exception
+end
+
+redefine_main do |main|
+  begin
+    {{main}}
+  rescue ex : ProjectError
+    puts ex.message
+    exit 1
+  end
+  Project::INSTANCE.install_deps
+end
-- 
GitLab