Skip to content

Commit

Permalink
Initial commit. Working manager for installing and switching versions.
Browse files Browse the repository at this point in the history
  • Loading branch information
faultyserver committed Apr 2, 2018
0 parents commit 21670d4
Show file tree
Hide file tree
Showing 19 changed files with 422 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[*.cr]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/doc/
/lib/
/bin/
/.shards/

# Libraries don't need dependency lock
# Dependencies will be locked in application that uses them
/shard.lock
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language: crystal
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2018 Jon Egeland

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# mtenv

`mtenv` is a version manager for the Myst programming language, inspired by rbenv and other similar version managers.

**It is currently a work in progress and should not be used for actual installations**. See the installation instructions in [Myst's README](https://github.com/myst-lang/myst#installation) for the time being.


# Installation

Since `mtenv` is contained in a single executable file, installing it is as simple as copying the single `mtenv` file to somewhere on your `$PATH`.

From a command line, something as simple as

```shell
# Download the script
curl -o mtenv 'https://raw.githubusercontent.com/myst-lang/mtenv/master/mtenv'
# Copy it onto the `PATH`
cp ./mtenv /usr/local/bin/mtenv
# Make sure it's executable
chmod +x /usr/local/bin/mtenv
```

And that's it! Running `mtenv` should now work and show you the usage instructions. However, `mtenv` also expects and depends on a `.mtenv` directory existing in your home directory (i.e., `~/.mtenv/`). This directory is used to store the installed versions of Myst and some other settings.

When first installing `mtenv`, run `mtenv setup` to make sure this directory exists and has the proper content:

```shell
mtenv setup
```

`mtenv setup` will:

- ensure that `~/.mtenv/` exists and add some basic configuration.
- create `/usr/local/bin/myst` as a symlink to `~/.mtenv/shims/myst`, the executable that `mtenv` manages to control `myst` versions.

Now you are fully set up to use `mtenv` and install new versions of Myst.

_Note_: If you have existing versions of Myst installed via Homebrew or any other method, be sure to remove them from your PATH before using `mtenv`! Otherwise `mtenv` may not take precedence and may cause unexpected behavior._


# Usage

`mtenv` uses sub-commands to perform its various tasks. To see these commands listed out, just run `mtenv` to see the usage instructions. Further explanations are given here.

- `mtenv install <version>`: install the version of Myst given by the argument. This will _not_ override the currently in-use version.
- `mtenv version`: show the version number of the currently in-use version of Myst.
- `mtenv installled`: show all currently-installed versions of Myst.
- `mtenv available`: show all versinos of Myst that are available for installation.
18 changes: 18 additions & 0 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: mtenv
version: 0.1.0
crystal: 0.24.1
license: MIT

description: |
The Myst language environment manager, inspired by rbenv and others.
authors:
- Jon <[email protected]>

targets:
mtenv:
main: src/mtenv.cr

dependencies:
admiral:
github: jwaldrip/admiral.cr
9 changes: 9 additions & 0 deletions spec/env_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "./spec_helper"

describe Env do
# TODO: Write tests

it "works" do
false.should eq(true)
end
end
2 changes: 2 additions & 0 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require "spec"
require "../src/env"
16 changes: 16 additions & 0 deletions src/commands/active.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class MTENV
class Active < Admiral::Command
define_help description: "Show the currently-active Myst version."

def run
active_version = `cat #{File.expand_path("~/.mtenv/global")}`
if active_version.empty?
puts "No Myst version is currently active."
else
puts active_version
end
end
end

register_sub_command active : Active
end
Empty file added src/commands/available.cr
Empty file.
35 changes: 35 additions & 0 deletions src/commands/implode.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class MTENV
class Implode < Admiral::Command
define_help description: "Completely uninstall mtenv and all Myst versions."

def run
require_confirmation!

unless Dir.exists?(File.expand_path("~/.mtenv"))
abort "~/.mtenv does not exist. Cannot ensure implosion."
end

shims_path = File.read(File.expand_path("~/.mtenv/shims_dir"))
puts ". Removing links to shims from #{shims_path}"
FileUtils.rm(File.join(shims_path, "myst"))

puts ". Removing `~/.mtenv`"
FileUtils.rm_r(File.expand_path("~/.mtenv"))

puts "Successfully imploded mtenv."
end


private def require_confirmation!
puts "Are you sure you want to completely uninstall mtenv? This cannot be undone."
puts "The `mtenv` command will remain installed, but all installations will be lost."
puts "Type 'implode' to confirm your intent."
print "> "
unless (confirmation = gets) && confirmation == "implode"
abort("Not uninstalling. Confirmation did not match 'implode'.")
end
end
end

register_sub_command implode : Implode
end
70 changes: 70 additions & 0 deletions src/commands/install.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require "tempfile"

class MTENV
class Install < Admiral::Command
define_help description: "Install a new version of Myst."

define_argument version : String,
description: "A SemVer version number of commit SHA to install",
required: true

define_argument name : String,
description: "An alias name to use for the new installation."

GITHUB_URL = "api.github.com/repos/myst-lang/myst/tarball"

def run
Util.require_setup!
validate_version!

tarball = Tempfile.new("myst-#{version_name}", ".tar.gz")
if download_version_tarball(arguments.version, to: tarball.path)
install_location = File.expand_path("~/.mtenv/versions/#{version_name}")
FileUtils.mkdir_p(install_location)
unpack_tarball(tarball.path, to: install_location)
build_executable(install_location)
else
STDERR.puts "Could not find a Myst version matching '#{arguments.version}'."
exit(1)
end

tarball.unlink
end

def version_name
arguments.name || arguments.version
end


private def validate_version!
unless Util.versionish?(arguments.version)
STDERR.puts "'#{arguments.version}' is not a valid version identifier."
STDERR.puts "Versions must be given as either SemVer numbers (vX.X.X) or commit SHAs"
exit(1)
end
end

private def download_version_tarball(version, to file)
# curl GitHub for a tarball of the myst repository at the requested version.
# -sL - run silently, and follow GitHub's redirects automatically
# > file - output the content into `file`.
`curl -sL #{GITHUB_URL}/#{version} > #{file}`
end

private def unpack_tarball(tar_path, to destination)
# Unpack the given tar ball into the given destination.
# -xf - unpack from a file
# -C - sets the destination for the unpack
# --strip-components=1 - remove the containing directory name from GitHub.
`tar -xf #{tar_path} -C #{destination} --strip-components=1`
end

private def build_executable(install_location)
FileUtils.cd(install_location) do
`shards build`
end
end
end

register_sub_command install : Install
end
59 changes: 59 additions & 0 deletions src/commands/setup.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
require "file_utils"

# The shim is written in `src/scripts/shim.sh`. This macro loads the content
# of that file into a string at compile time, avoiding the need to use a
# heredoc or bundle resource files when distributing this tool.
MYST_SHIM = {{ "#{`cat #{__DIR__}/../scripts/shim.sh`}" }}

class MTENV
class Setup < Admiral::Command
define_help description: "Ensure that `mtenv` is properly and fully installed."

def run
shims_location = ask_for_shims_location!
# Ensure the `.mtenv` dir exists in the home directory and has all of the
# necessary components (versions, global, shims, etc.).
FileUtils.cd(ENV["HOME"]) do
puts "Initializing `~/.mtenv/`"
FileUtils.mkdir_p(".mtenv/versions")
FileUtils.touch(".mtenv/global")
FileUtils.mkdir_p(".mtenv/shims")
# Store the shims location for use in the future (e.g., `implode`).
File.write(".mtenv/shims_dir", shims_location)

puts "Creating shims"
File.open(".mtenv/shims/myst", mode: "w", perm: 0o755) do |f|
f.truncate
f.puts(MYST_SHIM)
end

# Create mtenv-controlled shims for the Myst binary.
puts "Linking shims to #{shims_location}"
myst_path = File.join(shims_location, "myst")
File.symlink(File.expand_path("~/.mtenv/shims/myst"), myst_path)
end

puts "\nmtenv setup finished successfully."
end


private def ask_for_shims_location!
print "Where should mtenv create links to shims? (default '/usr/local/bin'): "
location = gets
if location.nil? || location.empty?
location = "/usr/local/bin"
end

location = File.expand_path(location)

if location && Dir.exists?(location)
return location
else
abort "Requested shims location `#{location}` does not exist. Aborting setup."
end

end
end

register_sub_command setup : Setup
end
24 changes: 24 additions & 0 deletions src/commands/uninstall.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require "file_utils"

class MTENV
class Uninstall < Admiral::Command
define_help description: "List all currently-installed Myst versions."

define_argument version : String,
description: "The name of the version to uninstall",
required: true

def run
version_path = File.expand_path("~/.mtenv/versions/#{arguments.version}")
if Dir.exists?(version_path)
puts "Uninstalling '#{arguments.version}'"
FileUtils.rm_r(version_path)
puts "Successfully uninstalled #{arguments.version}"
else
puts "Version '#{version_path}' is not an installed Myst version."
end
end
end

register_sub_command uninstall : Uninstall
end
26 changes: 26 additions & 0 deletions src/commands/use.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class MTENV
class Use < Admiral::Command
define_help description: "Set the version of Myst to use."

define_argument version : String,
description: "The name of the version to uninstall",
required: true

def run
version_path = File.expand_path("~/.mtenv/versions/#{arguments.version}")

unless Dir.exists?(version_path)
abort "Version #{arguments.version} is not currently installed."
end

File.open(File.expand_path("~/.mtenv/global"), "w") do |f|
f.truncate
f.print(arguments.version)
end

puts "Active Myst version is now #{arguments.version}"
end
end

register_sub_command use : Use
end
11 changes: 11 additions & 0 deletions src/commands/versions.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class MTENV
class Versions < Admiral::Command
define_help description: "List all currently-installed Myst versions."

def run
Process.exec("ls", [File.expand_path("~/.mtenv/versions")])
end
end

register_sub_command versions : Versions
end
15 changes: 15 additions & 0 deletions src/mtenv.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require "admiral"

require "./util.cr"

class MTENV < Admiral::Command
define_help description: "The Myst language environment manager."

def run
puts help
end
end

require "./commands/*"

MTENV.run
11 changes: 11 additions & 0 deletions src/scripts/shim.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
current_version=`cat ~/.mtenv/global`

if [ -z $current_version ]; then
echo "No version of Myst is currently active."
echo "Run \`mtenv use <version>\` to set a global version."
exit
fi

executable_path="~/.mtenv/versions/$current_version/bin/myst"

`$executable_path $@`
Loading

0 comments on commit 21670d4

Please sign in to comment.