├── .ameba.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── bin
└── vicr
├── shard.lock
├── shard.yml
├── spec
├── spec_helper.cr
├── vicr
│ ├── config
│ │ ├── compiler_spec.cr
│ │ ├── editor_spec.cr
│ │ └── run_file_spec.cr
│ └── service
│ │ ├── carc_in_spec.cr
│ │ └── github_spec.cr
└── vicr_spec.cr
└── src
├── vicr.cr
└── vicr
├── buffer.cr
├── cli.cr
├── config
├── compiler.cr
├── editor.cr
├── run_file.cr
└── settings.cr
├── runner.cr
├── service
├── carc_in.cr
└── github.cr
└── version.cr
/.ameba.yml:
--------------------------------------------------------------------------------
1 | LineLength:
2 | # Disallows lines longer that MaxLength number of symbols.
3 | Enabled: true
4 | MaxLength: 105
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /doc/
2 | /lib/
3 | /.crystal/
4 | /.shards/
5 | /out
6 | /bin/ameba*
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: crystal
2 | install:
3 | - shards install
4 | script:
5 | - crystal spec
6 | - bin/ameba
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Vitalii Elenhaupt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | OUT_DIR=./out
2 |
3 | all: build
4 |
5 | build:
6 | mkdir -p $(OUT_DIR)
7 | crystal build --release bin/vicr -o $(OUT_DIR)/vicr
8 |
9 | install:
10 | cp $(OUT_DIR)/vicr /usr/local/bin/
11 |
12 | run:
13 | $(OUT_DIR)/vicr
14 |
15 | clean:
16 | rm -rf $(OUT_DIR) .crystal .deps libs
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://travis-ci.org/veelenga/vicr)
4 |
5 | Vicr stands for **"Vim-like Interactive CRystal"** and represents a tiny command line application
6 | that designed to quickly execute Crystal code with fast feedback and options to proceed:
7 |
8 | 
9 |
10 | ## Installation
11 |
12 | ### OS X
13 |
14 | ```sh
15 | $ brew tap veelenga/tap
16 | $ brew install vicr
17 | ```
18 |
19 | ### From sources:
20 |
21 | ```sh
22 | $ git clone https://github.com/veelenga/vicr
23 | $ cd vicr
24 | $ make
25 | $ sudo make install
26 | ```
27 |
28 | ## Usage
29 |
30 | Open terminal, run `vicr`, write your Crystal program, save and exit.
31 |
32 | ### Options
33 |
34 | Vicr is able to load file content for you to start playing with Crystal code straight away.
35 | For example:
36 |
37 | ```sh
38 | # loads local file
39 | $ vicr src/vicr/cli.cr
40 |
41 | # loads Github file
42 | $ vicr https://github.com/manastech/crystal/blob/master/samples/2048.cr
43 |
44 | # loads Github gist
45 | $ vicr https://gist.github.com/veelenga/a5b861ccd32ff559b7d2#file-benchmark_test-cr
46 |
47 | # loads CarcIn file
48 | $ vicr https://carc.in/#/r/rlj
49 |
50 | # loads play.crystal-lang.org file
51 | $ vicr https://play.crystal-lang.org/#/r/rlj
52 |
53 | # loads raw file
54 | $ vicr http://example.com/program.cr
55 | ```
56 |
57 | Use help (`-h` flag) for more information.
58 |
59 | ### Customization
60 |
61 | It is possible to configure Vicr start-up settings using `~/.vicr/init.yaml` configuration file.
62 | You can customize settings and use your favorite editor and even change compiler params:
63 |
64 | ```yml
65 | # ~/.vicr/init.yaml
66 | ---
67 | editor:
68 | executable: nvim
69 | args:
70 | - "--cmd"
71 | - "set paste"
72 |
73 | compiler:
74 | executable: crystal
75 | args_before:
76 | - "run"
77 | - "--no-debug"
78 | ```
79 |
80 | ## Contributing
81 |
82 | If you feel like you have a good idea to be implemented, please open a discussion.
83 | If you found a defect and enough motivated to fix it, pull requests are welcome.
84 |
--------------------------------------------------------------------------------
/bin/vicr:
--------------------------------------------------------------------------------
1 | require "../src/vicr"
2 |
3 | Vicr::Cli.run
4 |
--------------------------------------------------------------------------------
/shard.lock:
--------------------------------------------------------------------------------
1 | version: 2.0
2 | shards:
3 | ameba:
4 | git: https://github.com/crystal-ameba/ameba.git
5 | version: 1.0.0
6 |
7 |
--------------------------------------------------------------------------------
/shard.yml:
--------------------------------------------------------------------------------
1 | name: vicr
2 | version: 0.5.0
3 | crystal: 0.27.0
4 |
5 | authors:
6 | - Vitalii Elenhaupt
7 |
8 | license: MIT
9 |
10 | development_dependencies:
11 | ameba:
12 | github: crystal-ameba/ameba
13 | version: ~> 1.0
14 |
--------------------------------------------------------------------------------
/spec/spec_helper.cr:
--------------------------------------------------------------------------------
1 | require "spec"
2 | require "../src/vicr/service/*"
3 | require "../src/vicr/config/*"
4 |
--------------------------------------------------------------------------------
/spec/vicr/config/compiler_spec.cr:
--------------------------------------------------------------------------------
1 | require "../../spec_helper"
2 |
3 | module Vicr::Config
4 | describe Compiler do
5 | context "yaml mapping" do
6 | it "maps properties" do
7 | compiler = Compiler.from_yaml({
8 | executable: "test",
9 | args_before: ["1", "2"],
10 | args_after: ["3"],
11 | }.to_yaml)
12 |
13 | compiler.executable.should eq "test"
14 | compiler.args_before.should eq ["1", "2"]
15 | compiler.args_after.should eq ["3"]
16 | end
17 |
18 | it "allows args_before to be nil" do
19 | compiler = Compiler.from_yaml({
20 | executable: "test",
21 | args_after: [""],
22 | }.to_yaml)
23 | compiler.args_before.should be nil
24 | end
25 |
26 | it "allows args_after to be nil" do
27 | compiler = Compiler.from_yaml({
28 | executable: "test",
29 | args_before: [""],
30 | }.to_yaml)
31 | compiler.args_after.should be nil
32 | end
33 |
34 | it "requires executable" do
35 | expect_raises(YAML::ParseException) {
36 | Compiler.from_yaml({args_before: [""]}.to_yaml)
37 | }
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/spec/vicr/config/editor_spec.cr:
--------------------------------------------------------------------------------
1 | require "../../spec_helper"
2 |
3 | module Vicr::Config
4 | describe Editor do
5 | context "yaml mapping" do
6 | it "maps properties" do
7 | editor = Editor.from_yaml({
8 | executable: "test",
9 | args: ["1", "2"],
10 | }.to_yaml)
11 | editor.executable.should eq "test"
12 | editor.args.should eq ["1", "2"]
13 | end
14 |
15 | it "requires executable" do
16 | expect_raises(YAML::ParseException) {
17 | Editor.from_yaml({args: [""]}.to_yaml)
18 | }
19 | end
20 |
21 | it "allows args to be nil" do
22 | editor = Editor.from_yaml({executable: "test"}.to_yaml)
23 | editor.args.should be_nil
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/vicr/config/run_file_spec.cr:
--------------------------------------------------------------------------------
1 | require "../../spec_helper"
2 |
3 | def test_run_file
4 | tempfile = File.tempfile "run_file"
5 | run_file = Vicr::RunFile.new tempfile.path
6 | begin
7 | yield run_file
8 | rescue e
9 | tempfile.close
10 | raise e
11 | end
12 | end
13 |
14 | module Vicr
15 | describe RunFile do
16 | describe "#path" do
17 | it "returns path to file" do
18 | test_run_file do |run_file|
19 | run_file.path.should_not be_nil
20 | end
21 | end
22 | end
23 |
24 | describe "#delete" do
25 | it "removes file" do
26 | test_run_file do |run_file|
27 | File.exists?(run_file.path).should be_true
28 | run_file.delete
29 | File.exists?(run_file.path).should be_false
30 | end
31 | end
32 | end
33 |
34 | describe "#create_new" do
35 | it "creates new file" do
36 | test_run_file do |run_file|
37 | run_file.delete
38 | File.exists?(run_file.path).should be_false
39 | run_file.create_new
40 | File.exists?(run_file.path).should be_true
41 | end
42 | end
43 | end
44 |
45 | describe "#write" do
46 | it "writes into file" do
47 | test_run_file do |run_file|
48 | run_file.write("test")
49 | run_file.lines.should eq ["test"]
50 | end
51 | end
52 | end
53 |
54 | describe "#lines" do
55 | it "returns empty string when file is empty" do
56 | test_run_file do |run_file|
57 | run_file.lines.should eq [] of String
58 | end
59 | end
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/spec/vicr/service/carc_in_spec.cr:
--------------------------------------------------------------------------------
1 | require "../../spec_helper"
2 |
3 | module Vicr::Service
4 | describe CarcIn do
5 | describe ".raw" do
6 | it "returns path to raw file on carc.in" do
7 | CarcIn.raw("https://carc.in/#/r/rlc")
8 | .should eq "https://carc.in/runs/rlc.cr"
9 | end
10 |
11 | it "returns nil if this is not carc.in path" do
12 | CarcIn.raw("https://github.com/test/test.cr").should be_nil
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/spec/vicr/service/github_spec.cr:
--------------------------------------------------------------------------------
1 | require "../../spec_helper"
2 |
3 | module Vicr::Service
4 | describe Github do
5 | describe ".raw" do
6 | it "returns path to raw file on github" do
7 | Github.raw("https://github.com/user/repo/blob/master/file.cr")
8 | .should eq "https://raw.githubusercontent.com/user/repo/master/file.cr"
9 | end
10 |
11 | it "returns nil if this is not github path" do
12 | Github.raw("https://example.com/user/repo/blob/master/file.cr")
13 | .should eq nil
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/vicr_spec.cr:
--------------------------------------------------------------------------------
1 | require "./spec_helper"
2 |
--------------------------------------------------------------------------------
/src/vicr.cr:
--------------------------------------------------------------------------------
1 | require "./vicr/*"
2 |
--------------------------------------------------------------------------------
/src/vicr/buffer.cr:
--------------------------------------------------------------------------------
1 | require "./service/*"
2 |
3 | module Vicr
4 | module Buffer
5 | extend self
6 | include Service
7 |
8 | def load(path : String)
9 | buffer = File.read path if File.exists? path
10 | buffer ||= load_http_file path if path.starts_with? "http"
11 | buffer || raise "Unable to resolve '#{path}'"
12 | end
13 |
14 | private def load_http_file(path : String)
15 | raw = Github.raw(path) || Github.gist_raw(path, "Crystal") || CarcIn.raw(path) || path
16 | resp = HTTP::Client.get raw
17 | resp.status_code == 200 ? resp.body : nil
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/src/vicr/cli.cr:
--------------------------------------------------------------------------------
1 | require "option_parser"
2 | require "http/client"
3 | require "json"
4 | require "./service/*"
5 |
6 | module Vicr
7 | class Cli
8 | def self.run(args = ARGV)
9 | Cli.new.run(args)
10 | end
11 |
12 | def run(args)
13 | path, debug, clear = nil, false, false
14 |
15 | OptionParser.parse(args) do |parser|
16 | parser.banner = <<-USAGE
17 | Usage: vicr [switches] [arguments] or
18 | vicr [path to file to load]
19 | USAGE
20 | parser.on("-f PATH", "--file PATH", "File to load") { |p| path = p }
21 | parser.on("-c", "--clear", "Clear current buffer") { clear = true }
22 | parser.on("-d", "--debug", "Debug output") { debug = true }
23 | parser.on("-v", "--version", "Show version") { puts VERSION; exit }
24 | parser.on("-h", "--help", "Show this help") { puts parser; exit }
25 | parser.unknown_args do |unknown_args|
26 | path ||= unknown_args.first if unknown_args.size == 1
27 | end
28 | end
29 |
30 | opts = {} of Symbol => String
31 | opts[:buffer] = Buffer.load(path.not_nil!) if path
32 | opts[:buffer] = "" if clear
33 |
34 | Runner.new(opts).start
35 | rescue e
36 | e.inspect_with_backtrace STDERR if debug
37 | puts e.message.colorize :red
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/src/vicr/config/compiler.cr:
--------------------------------------------------------------------------------
1 | require "yaml"
2 |
3 | module Vicr::Config
4 | struct Compiler
5 | include YAML::Serializable
6 |
7 | property executable : String
8 | property args_before : Array(String)?
9 | property args_after : Array(String)?
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/src/vicr/config/editor.cr:
--------------------------------------------------------------------------------
1 | require "yaml"
2 |
3 | module Vicr::Config
4 | struct Editor
5 | include YAML::Serializable
6 |
7 | property executable : String
8 | property args : Array(String)?
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/src/vicr/config/run_file.cr:
--------------------------------------------------------------------------------
1 | module Vicr
2 | class RunFile
3 | getter! :path
4 |
5 | def initialize(path, buffer = nil)
6 | @path = File.expand_path path
7 | buffer ? write buffer : File.exists?(@path) || create_new
8 | end
9 |
10 | def delete
11 | File.delete @path if File.exists? @path
12 | end
13 |
14 | def create_new
15 | Dir.mkdir_p Config::Settings::DIR
16 | File.new @path, "w"
17 | end
18 |
19 | def write(buffer : String)
20 | File.write @path, buffer
21 | end
22 |
23 | def lines
24 | File.read_lines @path
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/src/vicr/config/settings.cr:
--------------------------------------------------------------------------------
1 | require "yaml"
2 | require "../config/*"
3 |
4 | module Vicr::Config
5 | class Settings
6 | DIR = File.expand_path("~/.vicr", home: true)
7 |
8 | include YAML::Serializable
9 |
10 | property editor : Editor = editor_default
11 | property compiler : Compiler = compiler_default
12 |
13 | def run_file
14 | DIR + "/run.cr"
15 | end
16 |
17 | def self.load
18 | settings = File.exists?(settings_filepath) ? File.read(settings_filepath) : "{}"
19 | Settings.from_yaml settings
20 | end
21 |
22 | def self.settings_filepath
23 | DIR + "/init.yaml"
24 | end
25 |
26 | def self.editor_default
27 | Editor.from_yaml({executable: "vim"}.to_yaml)
28 | end
29 |
30 | def self.compiler_default
31 | Compiler.from_yaml({executable: "crystal", args_before: %w(run --no-debug)}.to_yaml)
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/src/vicr/runner.cr:
--------------------------------------------------------------------------------
1 | require "colorize"
2 | require "./config/*"
3 |
4 | module Vicr
5 | class Runner
6 | include Config
7 |
8 | @editor : Editor
9 | @compiler : Compiler
10 |
11 | def initialize(opts)
12 | settings = Settings.load
13 |
14 | @editor = settings.editor
15 | @compiler = settings.compiler
16 | @run_file = RunFile.new settings.run_file, opts[:buffer]?
17 |
18 | @editor_args = Array(String).new
19 | @editor_args.concat @editor.args.not_nil! if @editor.args
20 | @editor_args << @run_file.path
21 |
22 | @compiler_args = Array(String).new
23 | @compiler_args.concat @compiler.args_before.not_nil! if @compiler.args_before
24 | @compiler_args << @run_file.path
25 | @compiler_args.concat @compiler.args_after.not_nil! if @compiler.args_after
26 | end
27 |
28 | def start
29 | edit; run
30 | loop { act next_action }
31 | end
32 |
33 | def act(action)
34 | case action
35 | when :run
36 | run
37 | when :edit
38 | edit; run
39 | when :new
40 | @run_file.create_new
41 | edit; run
42 | when :quit
43 | exit
44 | end
45 | end
46 |
47 | def next_action
48 | puts "(r)un (e)dit (n)ew (q)uit".colorize :yellow
49 | print ">> ".colorize(:red)
50 |
51 | action = :unknown
52 | while action == :unknown
53 | action = STDIN.raw do |io|
54 | case io.gets 1
55 | when "r", "\r"
56 | :run
57 | when "e", " "
58 | :edit
59 | when "n"
60 | :new
61 | when "\e", "\u{3}", "\u{4}", "q", "Q"
62 | :quit
63 | else
64 | :unknown
65 | end
66 | end
67 | end
68 | puts action.colorize :green
69 | action
70 | end
71 |
72 | def edit
73 | system(@editor.executable, @editor_args) ||
74 | raise "Unable to edit '#{@run_file.path}' using '#{@editor.executable}'"
75 | end
76 |
77 | def run
78 | system(@compiler.executable, @compiler_args)
79 | end
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/src/vicr/service/carc_in.cr:
--------------------------------------------------------------------------------
1 | module Vicr::Service::CarcIn
2 | extend self
3 |
4 | def raw(path : String)
5 | if md = path.match /(carc\.in|play\.crystal-lang\.org)\/.*\/.*\/(.*)/
6 | "https://carc.in/runs/#{md[2]}.cr"
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/src/vicr/service/github.cr:
--------------------------------------------------------------------------------
1 | require "http/client"
2 | require "json"
3 | require "levenshtein"
4 |
5 | module Vicr::Service::Github
6 | extend self
7 |
8 | def raw(path : String)
9 | if md = path.match /github\.com\/(.*)\/(.*)\/blob\/(.*)\/(.*)/
10 | user, repo, branch, file = md[1], md[2], md[3], md[4]
11 | "https://raw.githubusercontent.com/#{user}/#{repo}/#{branch}/#{file}"
12 | end
13 | end
14 |
15 | def gist_raw(path : String, language : String = nil)
16 | path, filename = path.split("#file-") + [nil]
17 | if path && (md = path.match /gist\.github\.com\/.*\/(.*)/)
18 | files = gist_files md[1]
19 | if filename
20 | files.map { |file| {url: file[:raw_url], dist: Levenshtein.distance file[:filename], filename} }
21 | .sort_by!(&.[:dist].to_i)
22 | .first[:url].as String
23 | else
24 | (files.find { |file| file[:language] == language } || files.first)[:raw_url]
25 | end
26 | end
27 | end
28 |
29 | private def gist(id : String)
30 | resp = HTTP::Client.get "https://api.github.com/gists/" + id
31 | return JSON.parse(resp.body) if resp.status_code == 200
32 | raise ArgumentError.new "Gist with id '#{id}' not found"
33 | end
34 |
35 | private def gist_files(id : String)
36 | gist_files = [] of NamedTuple(filename: String, language: String, raw_url: String)
37 | gist(id)["files"].as_h.each do |file, value|
38 | gist_files << {
39 | filename: file.to_s,
40 | language: value["language"].to_s,
41 | raw_url: value["raw_url"].to_s,
42 | }
43 | end
44 | gist_files
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/src/vicr/version.cr:
--------------------------------------------------------------------------------
1 | module Vicr
2 | VERSION = "0.5.0"
3 | end
4 |
--------------------------------------------------------------------------------