source_win_bat 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +7 -0
- data/README.md +132 -0
- data/Rakefile +76 -0
- data/bin/init_sw +70 -0
- data/lib/source_win_bat.rb +287 -0
- data/lib/unixcompatenv.rb +107 -0
- data/source_win_bat.gemspec +17 -0
- data/test/exit.cmd +3 -0
- data/test/setcwd.cmd +6 -0
- data/test/setdoskey.cmd +7 -0
- data/test/setenv.cmd +6 -0
- data/test/setup_test.bash +24 -0
- data/test/tap.bash +37 -0
- data/test/test_ansicpchars.bash +22 -0
- data/test/test_exit.bash +13 -0
- data/test/test_optionparsebug.bash +7 -0
- data/test/test_setcwd.bash +10 -0
- data/test/test_setdoskey.bash +16 -0
- data/test/test_setenv.bash +13 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 13570792e56f831dce6bdc6c882950bfa681bedeb3e06bc6ef6ad4ef06b7a853
|
4
|
+
data.tar.gz: 0a68d985177b5f751b7fd7ebdf06440cda0269ec03347b261615ac919a80da3b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: db9581bba734ea26bad073cce02811c74ff748037e99165a57f07c81dad7590537b9af04bb445db084e249e7d73fb6e3d500e36ce863eac5778c29373cedfbd9
|
7
|
+
data.tar.gz: 2e8c8d97f2285224a2608dafda5045c36ce67790c30d26a0b70e030f97ba439c20f49657ec6400ee06b16be0493c72ff214596ac15083ebe23057792498f6746
|
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright 2019 Takaya Saeki
|
2
|
+
|
3
|
+
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:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
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.
|
data/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# SourceWinBat - Source a Windows Batch in Bash!
|
2
|
+
|
3
|
+
`sw`, or SourceWinBat is a CLI utility to run Windows batch files from WSL/MSYS2/Cygwin,
|
4
|
+
and sync the shell environments of Bash and the Windows batch files, including
|
5
|
+
* Environment variables
|
6
|
+
* Doskeys
|
7
|
+
* Working directories
|
8
|
+
|
9
|
+
By SourceWinBat, you can execute Windows initialization scripts in Bash as if you use `source`
|
10
|
+
command for initialization Bash scripts.
|
11
|
+
SourceWinBat helps you do your daily Windows work in your favorite UNIX-compatible environment.
|
12
|
+
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
After initalization, `sw` function is defined in your Bash environment.
|
17
|
+
Run your Windows batch files or Windows CLI commands by `sw`.
|
18
|
+
|
19
|
+
You can run a Windows batch file
|
20
|
+
```console
|
21
|
+
$ cat winbat.bat
|
22
|
+
@echo off
|
23
|
+
set ENV1=bar! ; Environment variables will be synced
|
24
|
+
echo foo! ; Execute Windows echo commands
|
25
|
+
ver ; Execute Windows ver command, which outputs the OS version
|
26
|
+
$ sw winbat.bat # winbat.bat is executed
|
27
|
+
foo!
|
28
|
+
|
29
|
+
Microsoft Windows [Version 10.0.17763.195]
|
30
|
+
$ echo $ENV1 # ENV1 is synced!
|
31
|
+
bar!
|
32
|
+
```
|
33
|
+
|
34
|
+
You can also run a Windows command directly.
|
35
|
+
```console
|
36
|
+
$ sw ver
|
37
|
+
|
38
|
+
Microsoft Windows [Version 10.0.17763.195]
|
39
|
+
```
|
40
|
+
|
41
|
+
## Examples
|
42
|
+
|
43
|
+
### Environment Syncing
|
44
|
+
|
45
|
+
SourceWinBat syncs environment variables, doskeys, and working directories of the
|
46
|
+
Bash environment with those of the Windows cmd environment where a batch file is executed.
|
47
|
+
|
48
|
+
#### 1. Environment variables
|
49
|
+
A batch file can see the exported environment variables of Bash.
|
50
|
+
Conversely, Bash has the environment variables defined by the batch file and Windows system
|
51
|
+
after the batch file is executed. `PATH` is properly converted.
|
52
|
+
|
53
|
+
```console
|
54
|
+
$ export UNIXENV="An UNIX environment variable is imported!"
|
55
|
+
$ cat syncenv.bat
|
56
|
+
echo %UNIXENV%
|
57
|
+
set WINENV=A Windows environment variable is imported!
|
58
|
+
set PATH=C:\any\path;%PATH%
|
59
|
+
$ sw syncenv.bat # syncenv sees the value of $UNIXENV, which we defined in Bash!
|
60
|
+
An UNIX environment variable is imported!
|
61
|
+
$ echo $WINENV # Now we can see $WINENV, which is set in synenv.bat!!
|
62
|
+
A Windows environment variable is imported!
|
63
|
+
$ echo $PATH # PATH is converted to the path of WSL
|
64
|
+
/mnt/c/any/path:/usr/bin/:/bin:(other paths go on...)
|
65
|
+
```
|
66
|
+
|
67
|
+
#### 2. Doskeys
|
68
|
+
SourceWinBat enables Bash to import doskeys from Windows Batch files as Bash functions.
|
69
|
+
|
70
|
+
```console
|
71
|
+
$ cat syncdoskey.bat
|
72
|
+
doskey echo1stparam=echo $1
|
73
|
+
doskey echoallparams=echo $*
|
74
|
+
doskey verver=ver
|
75
|
+
$ sw syncdoskey.bat
|
76
|
+
$ echo1stparam 1st 2nd 3rd # echo1stparam is imported!
|
77
|
+
1st
|
78
|
+
$ echo1stparam %OS% # "echo $1" is executed by cmd.exe, so %OS% is expanded
|
79
|
+
Windows_NT
|
80
|
+
$ echoallparams 1st 2nd 3rd
|
81
|
+
1st 2nd 3rd
|
82
|
+
$ verver
|
83
|
+
|
84
|
+
Microsoft Windows [Version 10.0.17763.195]
|
85
|
+
```
|
86
|
+
|
87
|
+
#### 3. Working directories
|
88
|
+
As `source` of built-in Bash command syncs working directories, SourceWinBat also syncs them.
|
89
|
+
|
90
|
+
```console
|
91
|
+
$ cd ~
|
92
|
+
$ cat syncwd.bat
|
93
|
+
pushd C:\Windows
|
94
|
+
pushd C:\Windows\system32
|
95
|
+
cd C:\Windows\system32\drivers
|
96
|
+
$ sw syncwd.bat
|
97
|
+
$ pwd # The current directory of Bash is changed
|
98
|
+
/mnt/c/Windows/System32/drivers
|
99
|
+
$ dirs # The directory stack is synced with that of the batch file
|
100
|
+
/mnt/c/Windows/System32/ /mnt/c/Windows /home/nullpo
|
101
|
+
```
|
102
|
+
|
103
|
+
## Installation
|
104
|
+
|
105
|
+
SourceWinBat is written in Ruby. You can install it by Gem.
|
106
|
+
```console
|
107
|
+
# gem install source_win_bat
|
108
|
+
```
|
109
|
+
|
110
|
+
Execute the line below to add the initialization in your `.bashrc`.
|
111
|
+
```console
|
112
|
+
$ echo 'eval "$(init_sw)"' >> ~/.bashrc
|
113
|
+
```
|
114
|
+
After restarting Bash, you will be able to use `sw` in your shell.
|
115
|
+
Currently, SourceWinBat supports only Bash as a shell.
|
116
|
+
|
117
|
+
## Requirements
|
118
|
+
|
119
|
+
### 1. For WSL users
|
120
|
+
|
121
|
+
October update or later is required. SourceWinBat requires ConPTY API.
|
122
|
+
|
123
|
+
### 2. For MSYS2 and Cygwin users
|
124
|
+
|
125
|
+
If you use MSYS2 and Cygwin with SourceWinBat, `winpty` command is required.
|
126
|
+
Clone it from its GitHub repository and build it from the source. The repository is https://github.com/rprichard/winpty .
|
127
|
+
For MSYS2 users, DO NOT install `winpty` via `pacman`. As of 2019/01/03, Pacman installs the latest released version, 0.4.3-1, but this version does not work anymore.
|
128
|
+
|
129
|
+
## TODOs
|
130
|
+
|
131
|
+
* Support non-ascii characters in Cygwin and MSYS2. SourceWinBat already supports them in WSL.
|
132
|
+
* Support shell operators in doskey such as pipe.
|
data/Rakefile
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative './lib/unixcompatenv'
|
2
|
+
|
3
|
+
task default: %w[test]
|
4
|
+
|
5
|
+
task :test do
|
6
|
+
|
7
|
+
# Get all compatibility environments to test
|
8
|
+
all_compat_envs = [:wsl, :msys, :cygwin]
|
9
|
+
installed_compat_envs = UnixCompatEnv.detect_installed_compat_envs
|
10
|
+
ignored_compat_envs = (ENV['IGNORED_COMPATENV'] || '').split(',')
|
11
|
+
.map(&:chomp)
|
12
|
+
env_to_readablestr = {wsl: "WSL", msys: "MSYS2", cygwin: "Cygwin"}
|
13
|
+
|
14
|
+
compat_envs = {}
|
15
|
+
all_compat_envs.each do |env|
|
16
|
+
next if ignored_compat_envs.include?(env_to_readablestr[env])
|
17
|
+
if installed_compat_envs.has_key?(env)
|
18
|
+
compat_envs[env] = installed_compat_envs[env]
|
19
|
+
next
|
20
|
+
end
|
21
|
+
path_env_name = "#{env_to_readablestr[env].upcase}_BASH_PATH"
|
22
|
+
if ENV[path_env_name] and File.exists?(ENV[path_env_name])
|
23
|
+
compat_envs[env] = ENV[path_env_name]
|
24
|
+
next
|
25
|
+
end
|
26
|
+
STDERR.puts "#{env_to_readablestr[env]} is not found. Set #{path_env_name} or add #{env_to_readablestr[env]} to IGNORED_COMPATENV environment variable. (Comma sepearated values)"
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
|
30
|
+
# Test
|
31
|
+
unix_double_quote = -> str {
|
32
|
+
'"' + str.gsub('\\') {|_| '\\\\'}.gsub('"') {|_| '\\"'}.gsub('$') {|_| '\\$'} + '"'
|
33
|
+
}
|
34
|
+
cmd_double_quote = -> str {
|
35
|
+
'"' + str.gsub('\\') {|_| '\\\\'}.gsub('"') {|_| '\\"'} + '"'
|
36
|
+
}
|
37
|
+
ok_results = {}
|
38
|
+
succeeded = true
|
39
|
+
tests_winpath = UnixCompatEnv.to_win_path(File.realpath("./test"))
|
40
|
+
lib_winpath = UnixCompatEnv.to_win_path(File.realpath("./lib"))
|
41
|
+
compat_envs.each do |env, path|
|
42
|
+
case env
|
43
|
+
when :wsl
|
44
|
+
convpath_win2compat = "wslpath"
|
45
|
+
when :msys, :cygwin
|
46
|
+
convpath_win2compat = "cygpath -u"
|
47
|
+
end
|
48
|
+
cd2test = "cd \"$(#{convpath_win2compat} '#{tests_winpath}')\"; "
|
49
|
+
rubylib = "RUBYLIB=\"$(#{convpath_win2compat} '#{lib_winpath}')\" "
|
50
|
+
prove = "prove -e /bin/bash -j4 test_*.bash; "
|
51
|
+
|
52
|
+
cmd_in_env = cd2test + rubylib + prove
|
53
|
+
if env == UnixCompatEnv.compat_env
|
54
|
+
cmd = "bash -lc #{unix_double_quote.call(cmd_in_env)}"
|
55
|
+
elsif [UnixCompatEnv.compat_env, env].include?(:wsl)
|
56
|
+
cmd = "#{path} -lc #{unix_double_quote.call(cmd_in_env)}"
|
57
|
+
else
|
58
|
+
# Cygwin and MSYS2 cannot launch each other's applications directly due to DLL conflict.
|
59
|
+
# Solve that by wrapping a command by cmd.exe.
|
60
|
+
cmd_env_launch = "#{UnixCompatEnv.to_win_path(path)} -lc #{cmd_double_quote.call(cmd_in_env)}"
|
61
|
+
cmd = "cmd.exe /C #{unix_double_quote.call(cmd_env_launch)}"
|
62
|
+
end
|
63
|
+
puts "\e[1m\e[33m===#{env_to_readablestr[env]}===\e[0m\e[22m"
|
64
|
+
sh cmd do |ok, _|
|
65
|
+
ok_results[env] = ok
|
66
|
+
succeeded &&= ok
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
puts "\e[1m\e[33m===Summary===\e[0m\e[22m"
|
71
|
+
compat_envs.each do |env, _|
|
72
|
+
puts "#{env_to_readablestr[env]}: #{ok_results[env] ? "PASSED" : "FAILED"}"
|
73
|
+
end
|
74
|
+
|
75
|
+
exit succeeded ? 0 : 1
|
76
|
+
end
|
data/bin/init_sw
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
puts <<BASH
|
4
|
+
#!/bin/bash
|
5
|
+
|
6
|
+
which winpty > /dev/null
|
7
|
+
if [[ `uname` != Linux && $? != 0 ]]; then
|
8
|
+
echo "winpty is required to run 'sw'. Please install winpty." >&2
|
9
|
+
echo "Please note that you have to build the latest winpty from the git repository since 4.3 has a fatal bug." >&2
|
10
|
+
else
|
11
|
+
sw () {
|
12
|
+
local arg
|
13
|
+
local tmp_env tmp_macro tmp_cwd
|
14
|
+
local exitstatus
|
15
|
+
|
16
|
+
for arg in "$@"; do
|
17
|
+
if [[ "$arg" = "--" ]]; then
|
18
|
+
shift 1
|
19
|
+
break
|
20
|
+
elif [[ "$arg" =~ ^-+.+ ]]; then
|
21
|
+
case "$arg" in
|
22
|
+
"--help" | "-h" )
|
23
|
+
cat <<EOS
|
24
|
+
sw, or SourceWinBat, is a utility to run Windows batch files from WSL /
|
25
|
+
MSYS2 / Cygwin and sync environment variables, and working directories
|
26
|
+
between batch files and their UNIX Bash shell.
|
27
|
+
|
28
|
+
Usage:
|
29
|
+
sw [ [sw_options] -- ] win_bat_file [args...]
|
30
|
+
|
31
|
+
Sw options:
|
32
|
+
-help --help Show this help message
|
33
|
+
|
34
|
+
Examples:
|
35
|
+
sw echo test
|
36
|
+
sw somebat.bat
|
37
|
+
|
38
|
+
EOS
|
39
|
+
return ;;
|
40
|
+
* )
|
41
|
+
echo "Unknown option '$1'" >&2
|
42
|
+
return 1 ;;
|
43
|
+
esac
|
44
|
+
shift 1
|
45
|
+
else
|
46
|
+
break
|
47
|
+
fi
|
48
|
+
done
|
49
|
+
|
50
|
+
tmp_env="$(mktemp)"
|
51
|
+
tmp_macro="$(mktemp)"
|
52
|
+
tmp_cwd="$(mktemp)"
|
53
|
+
ruby -W0 -e "require 'source_win_bat'; SourceWindowsBatch.new.main(ARGV)" "${tmp_env}" "${tmp_macro}" "${tmp_cwd}" "$@"
|
54
|
+
exitstatus=$?
|
55
|
+
if [[ -e "${tmp_env}" ]]; then
|
56
|
+
source "${tmp_env}"
|
57
|
+
fi
|
58
|
+
if [[ -e "${tmp_macro}" ]]; then
|
59
|
+
source "${tmp_macro}"
|
60
|
+
fi
|
61
|
+
if [[ -e "${tmp_cwd}" ]]; then
|
62
|
+
source "${tmp_cwd}"
|
63
|
+
fi
|
64
|
+
|
65
|
+
ruby -e "begin; File.delete('${tmp_env}', '${tmp_macro}', '${tmp_cwd}'); rescue Errno::ENOENT; end"
|
66
|
+
return $exitstatus
|
67
|
+
}
|
68
|
+
fi
|
69
|
+
|
70
|
+
BASH
|
@@ -0,0 +1,287 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require 'tmpdir'
|
5
|
+
require_relative 'unixcompatenv'
|
6
|
+
|
7
|
+
class SourceWindowsBatch
|
8
|
+
|
9
|
+
VERSION = "0.1.0"
|
10
|
+
|
11
|
+
def main(argv)
|
12
|
+
if argv.length < 4 || argv[3].chomp.empty?
|
13
|
+
STDERR.puts <<-EOS
|
14
|
+
Usage: sw windows_cmd_or_batch [options_for_the_cmd]
|
15
|
+
|
16
|
+
Internal Ruby command Usage:
|
17
|
+
#{File.basename(__FILE__)} env_out macro_out cwd_out windows_cmd_or_batch [options_for_the_cmd]
|
18
|
+
EOS
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
|
22
|
+
unless [:cygwin, :msys, :wsl].include? UnixCompatEnv.compat_env
|
23
|
+
raise "You're in an unsupported UNIX compatible environment"
|
24
|
+
end
|
25
|
+
|
26
|
+
env = prepare_env_vars
|
27
|
+
env_tmp_file_in = mk_tmpname(".env")
|
28
|
+
macro_tmp_file_in = mk_tmpname(".doskey")
|
29
|
+
cwd_tmp_file_in = mk_tmpname(".cwd")
|
30
|
+
win_cmd = argv[3..-1].map {|v| "#{v}"}.join(" ")
|
31
|
+
win_cmd += " & call set SW_EXITSTATUS=%^ERRORLEVEL% "
|
32
|
+
win_cmd = concat_envdump(win_cmd, env_tmp_file_in)
|
33
|
+
win_cmd = concat_macrodump(win_cmd, macro_tmp_file_in)
|
34
|
+
win_cmd = concat_cwddump(win_cmd, cwd_tmp_file_in)
|
35
|
+
win_cmd += " & call exit %^SW_EXITSTATUS%"
|
36
|
+
# puts win_cmd
|
37
|
+
Signal.trap(:INT, "SIG_IGN")
|
38
|
+
if UnixCompatEnv.compat_env == :wsl
|
39
|
+
# * Skip winpty, assuming the system's WSL supports ConPTY
|
40
|
+
# * Use an absolute path since EC overwrites PATH with Windows-style PATH in WSL
|
41
|
+
pid = Process.spawn(env,
|
42
|
+
UnixCompatEnv.to_compat_path('C:\\Windows\\System32\\cmd.exe'),
|
43
|
+
'/C', win_cmd, :in => 0, :out => 1, :err => 2)
|
44
|
+
elsif !STDOUT.isatty
|
45
|
+
pid = Process.spawn(env, 'cmd.exe', '/C', win_cmd, :in => 0, :out => 1, :err => 2)
|
46
|
+
else
|
47
|
+
pid = Process.spawn(env, 'winpty', '--', 'cmd.exe', '/C', win_cmd, :in => 0, :out => 1, :err => 2)
|
48
|
+
end
|
49
|
+
Signal.trap(:INT) do
|
50
|
+
Process.signal("-KILL", pid)
|
51
|
+
end
|
52
|
+
status = nil
|
53
|
+
loop do
|
54
|
+
_, status = Process.wait2(pid)
|
55
|
+
break if status.exited?
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
codepage = detect_ansi_codepage
|
60
|
+
conv_setenv_stmts(env_tmp_file_in, argv[0], :bash, codepage)
|
61
|
+
conv_doskey_stmts(macro_tmp_file_in, argv[1], :bash, codepage)
|
62
|
+
gen_chdir_cmds(cwd_tmp_file_in, argv[2], :bash, codepage)
|
63
|
+
[env_tmp_file_in, macro_tmp_file_in, cwd_tmp_file_in].each do |f|
|
64
|
+
File.delete f
|
65
|
+
end
|
66
|
+
rescue Errno::ENOENT
|
67
|
+
# ignore
|
68
|
+
end
|
69
|
+
|
70
|
+
exit(status.exitstatus)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def detect_ansi_codepage
|
76
|
+
if !STDOUT.isatty && UnixCompatEnv.compat_env == :wsl
|
77
|
+
# cmd.exe seems to use UTF-8 when Stdout is redirected in WSL. TODO: Is it always fixed?
|
78
|
+
return "65001" # CP65001 is UTF-8
|
79
|
+
end
|
80
|
+
|
81
|
+
posh_cmd = <<-EOS
|
82
|
+
Get-WinSystemLocale | Select-Object Name, DisplayName,
|
83
|
+
@{ n='OEMCP'; e={ $_.TextInfo.OemCodePage } },
|
84
|
+
@{ n='ACP'; e={ $_.TextInfo.AnsiCodePage } }
|
85
|
+
EOS
|
86
|
+
posh_res = `powershell.exe "#{posh_cmd.gsub("$", "\\$")}"`
|
87
|
+
locale = posh_res.lines.select {|line| !(line =~ /^\s*$/)}[-1].chomp
|
88
|
+
ansi_cp = locale.split(" ")[-1]
|
89
|
+
ansi_cp
|
90
|
+
end
|
91
|
+
|
92
|
+
def serialize_wslenvs(wslenvs)
|
93
|
+
wslenvs.map {|varname, opt| "#{varname}#{opt.empty? ? "" : "/#{opt}"}"}.join(":")
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse_wslenv(wslenv_str)
|
97
|
+
wslenvs = Hash[]
|
98
|
+
wslenv_str.split(":").each do |wslenvvar|
|
99
|
+
envvar_name, envvar_opt = wslenvvar.split('/')
|
100
|
+
wslenvs[envvar_name] = envvar_opt || ""
|
101
|
+
end
|
102
|
+
wslenvs
|
103
|
+
end
|
104
|
+
|
105
|
+
def prepare_env_vars
|
106
|
+
return {} if UnixCompatEnv.compat_env != :wsl
|
107
|
+
|
108
|
+
wslenvs = Hash[]
|
109
|
+
ENV.each do |envvar_name, _|
|
110
|
+
wslenvs[envvar_name] = ""
|
111
|
+
end
|
112
|
+
wslenvs.merge!(parse_wslenv(ENV['WSLENV']))
|
113
|
+
# We don't use '/l' option, but convert paths by ourselves instead.
|
114
|
+
# See the comment that starts with 'How PATH in WSLENV is handled'
|
115
|
+
wslenvs['PATH'] = ""
|
116
|
+
var_wslenv = serialize_wslenvs(wslenvs)
|
117
|
+
|
118
|
+
paths = []
|
119
|
+
ENV['PATH'].split(":").each do |path|
|
120
|
+
begin
|
121
|
+
rpath = File.realpath(path)
|
122
|
+
if rpath.start_with?(UnixCompatEnv.win_root_in_compat)
|
123
|
+
path = UnixCompatEnv.to_win_path(rpath)
|
124
|
+
end
|
125
|
+
rescue Errno::ENOENT
|
126
|
+
end
|
127
|
+
paths.push(path)
|
128
|
+
end
|
129
|
+
var_path = paths.join(';')
|
130
|
+
|
131
|
+
{"WSLENV" => var_wslenv, "PATH" => var_path}
|
132
|
+
end
|
133
|
+
|
134
|
+
def mk_tmpname(suffix)
|
135
|
+
"#{UnixCompatEnv.win_tmp_in_compat}#{SecureRandom.uuid + suffix}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def concat_envdump(cmd, tmpfile)
|
139
|
+
cmd + " & set > #{dq_win_path(UnixCompatEnv.to_win_path(tmpfile))}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def concat_macrodump(cmd, tmpfile)
|
143
|
+
#TODO: escape
|
144
|
+
cmd + " & doskey /macros > #{dq_win_path(UnixCompatEnv.to_win_path(tmpfile))}"
|
145
|
+
end
|
146
|
+
|
147
|
+
def concat_cwddump(cmd, tmpfile)
|
148
|
+
#TODO: escape
|
149
|
+
winpath = dq_win_path(UnixCompatEnv.to_win_path(tmpfile))
|
150
|
+
cmd + " & cd > #{winpath} & pushd >> #{winpath}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def dq_win_path(str)
|
154
|
+
str.gsub(/\//, '\\')
|
155
|
+
.split("\\")
|
156
|
+
.map {|dir| dir.include?(" ") ? "\"#{dir}\"" : dir}
|
157
|
+
.join("\\")
|
158
|
+
end
|
159
|
+
|
160
|
+
def escape_singlequote(str)
|
161
|
+
str.gsub(/'/, '"\'"')
|
162
|
+
end
|
163
|
+
|
164
|
+
def conv_to_host_cmds(in_file, out_file, conv_method, env)
|
165
|
+
unless File.exist?(in_file)
|
166
|
+
return
|
167
|
+
end
|
168
|
+
File.open(out_file, "w") do |out|
|
169
|
+
File.open(in_file) do |f|
|
170
|
+
f.each_line do |line|
|
171
|
+
line.force_encoding("ASCII-8BIT")
|
172
|
+
converted = conv_method.call(line, env)
|
173
|
+
out.puts converted if converted
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
def to_compat_pathlist(path, shell)
|
181
|
+
raise "Unsupporeted" unless shell == :bash
|
182
|
+
path.split(";")
|
183
|
+
.map {|p| UnixCompatEnv.to_compat_path(p)}
|
184
|
+
.join(":")
|
185
|
+
end
|
186
|
+
|
187
|
+
def to_env_stmt(set_stmt, shell)
|
188
|
+
raise "Unsupporeted" unless shell == :bash
|
189
|
+
var, val = /([^=]*)=(.*)$/.match(set_stmt)[1..2]
|
190
|
+
|
191
|
+
is_var_valid = /^[a-zA-Z_][_0-9a-zA-Z]*$/ =~ var
|
192
|
+
return nil unless is_var_valid
|
193
|
+
|
194
|
+
if var == "PATH" && UnixCompatEnv.compat_env != :wsl
|
195
|
+
val = to_compat_pathlist(val, :bash)
|
196
|
+
end
|
197
|
+
|
198
|
+
stmt = "export #{var}='#{escape_singlequote(val.chomp)}'"
|
199
|
+
return stmt if UnixCompatEnv.compat_env != :wsl
|
200
|
+
|
201
|
+
if var == "PATH"
|
202
|
+
stmt += "\nexport WSLENV=PATH/l:${WSLENV:-__EC_DUMMY_ENV}"
|
203
|
+
else
|
204
|
+
stmt += "\nexport WSLENV=#{var}:${WSLENV:-__EC_DUMMY_ENV}"
|
205
|
+
end
|
206
|
+
stmt
|
207
|
+
end
|
208
|
+
|
209
|
+
def conv_setenv_stmts(setenvfile, outfile, shell, codepage)
|
210
|
+
raise "Unsupporeted" if shell != :bash
|
211
|
+
|
212
|
+
File.open(outfile, "w") do |f_out|
|
213
|
+
envs = []
|
214
|
+
File.read(setenvfile, encoding: "CP#{codepage}:UTF-8").lines.each do |set_stmt|
|
215
|
+
var, val = /([^=]*)=(.*)$/.match(set_stmt)[1..2]
|
216
|
+
|
217
|
+
is_var_valid = /^[a-zA-Z_][_0-9a-zA-Z]*$/ =~ var
|
218
|
+
next if !is_var_valid
|
219
|
+
|
220
|
+
if var == "PATH"
|
221
|
+
val = to_compat_pathlist(val, shell)
|
222
|
+
end
|
223
|
+
|
224
|
+
envs.push(var)
|
225
|
+
f_out.puts("export #{var}='#{escape_singlequote(val.chomp)}'")
|
226
|
+
end
|
227
|
+
if UnixCompatEnv.compat_env == :wsl
|
228
|
+
# How PATH in WSLENV is handled:
|
229
|
+
# EC configures PATH's WSLENV flag as follows
|
230
|
+
# A. When EC internally launches a Windows bat file
|
231
|
+
# Set the PATH's flag to '' (nothing) since EC converts each Unix
|
232
|
+
# path to a corresponding Windows path.
|
233
|
+
# B. When EC syncs environment variables with the result of a bat file
|
234
|
+
# Leave the PATH's WSLENV flag as is
|
235
|
+
wslenvs = Hash[*envs.flat_map {|env| [env, ""]}]
|
236
|
+
wslenvs.delete('PATH')
|
237
|
+
wslenvs.merge!(parse_wslenv(ENV['WSLENV']))
|
238
|
+
|
239
|
+
if wslenvs.length > 0
|
240
|
+
f_out.puts("export WSLENV='#{serialize_wslenvs(wslenvs)}'")
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def conv_doskey_stmts(doskeyfile, outfile, shell, codepage)
|
247
|
+
raise "Unsupporeted" unless shell == :bash
|
248
|
+
|
249
|
+
File.open(outfile, "w") do |f_out|
|
250
|
+
File.open(doskeyfile, encoding: "CP#{codepage}:UTF-8") do |f_in|
|
251
|
+
f_in.each_line do |doskey_stmt|
|
252
|
+
key, body = /([^=]*)=(.*)$/.match(doskey_stmt)[1..2]
|
253
|
+
|
254
|
+
is_key_valid = /^[a-zA-Z][0-9a-zA-Z]*$/ =~ key
|
255
|
+
return nil unless is_key_valid
|
256
|
+
|
257
|
+
body_substituted = escape_singlequote(body.chomp)
|
258
|
+
.gsub(/(?<param>\$[1-9]|\$\$|\$\*)/, '\'"\k<param>"\'')
|
259
|
+
|
260
|
+
f_out.puts <<-"EOS"
|
261
|
+
#{key} () {
|
262
|
+
sw '#{body_substituted}'
|
263
|
+
}
|
264
|
+
EOS
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def gen_chdir_cmds(dirs, outfile, shell, codepage)
|
271
|
+
raise "Unsupporeted" unless shell == :bash
|
272
|
+
return unless File.exist?(dirs)
|
273
|
+
|
274
|
+
lines = File.read(dirs, encoding:"CP#{codepage}:UTF-8").lines.select {|line| !line.empty?}
|
275
|
+
cwd = lines[0]
|
276
|
+
dirs = lines[1..-1]
|
277
|
+
|
278
|
+
res = []
|
279
|
+
dirs.reverse.each do |dir|
|
280
|
+
res.push "cd '#{escape_singlequote(UnixCompatEnv.to_compat_path(dir.chomp))}'"
|
281
|
+
res.push "pushd . > /dev/null"
|
282
|
+
end
|
283
|
+
res.push "cd '#{escape_singlequote(UnixCompatEnv.to_compat_path(cwd.chomp))}'"
|
284
|
+
File.write(outfile, res.join("\n"))
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module UnixCompatEnv
|
4
|
+
|
5
|
+
@@compat_env = nil
|
6
|
+
@@compat_root = nil
|
7
|
+
@@win_root = nil
|
8
|
+
@@win_tmp = nil
|
9
|
+
|
10
|
+
def self.detect_installed_compat_envs
|
11
|
+
res = {}
|
12
|
+
|
13
|
+
default_paths = {
|
14
|
+
wsl: "/c/Windows/System32/bash.exe",
|
15
|
+
msys: "/c/tools/msys64/usr/bin/bash.exe",
|
16
|
+
cygwin: "/c/tools/cygwin/bin/bash.exe"
|
17
|
+
}
|
18
|
+
|
19
|
+
default_paths.each do |env, path|
|
20
|
+
path = win_root_in_compat[0..-2] + path
|
21
|
+
if File.exists?(path)
|
22
|
+
res[env] = path
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
res
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.compat_env
|
30
|
+
@@compat_env ||=
|
31
|
+
case RUBY_PLATFORM
|
32
|
+
when /msys/
|
33
|
+
:msys
|
34
|
+
when /linux/
|
35
|
+
:wsl
|
36
|
+
when /cygwin/
|
37
|
+
:cygwin
|
38
|
+
else
|
39
|
+
:win
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.compat_root_in_win
|
44
|
+
return @@compat_root if @@compat_root || compat_env == :wsl # @@compat_root should be nil for :wsl
|
45
|
+
case compat_env
|
46
|
+
when :msys, :cygwin
|
47
|
+
path = `cygpath -w /`.chomp
|
48
|
+
if !path.end_with?("\\")
|
49
|
+
path += "\\"
|
50
|
+
end
|
51
|
+
@@compat_root = path
|
52
|
+
when :wsl
|
53
|
+
@@compat_root = nil
|
54
|
+
end
|
55
|
+
@@compat_root
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.win_root_in_compat
|
59
|
+
return @@win_root if @@win_root
|
60
|
+
case compat_env
|
61
|
+
when :msys, :cygwin
|
62
|
+
root = `cygpath -u c:/`.chomp
|
63
|
+
when :wsl
|
64
|
+
root = `wslpath c:/`.chomp
|
65
|
+
end
|
66
|
+
raise "unexpected win root path" unless root.end_with?("c/")
|
67
|
+
@@win_root = root[0...-2]
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.win_tmp_in_compat
|
71
|
+
return @@win_tmp if @@win_tmp
|
72
|
+
case compat_env
|
73
|
+
when :wsl, :cygwin
|
74
|
+
@@win_tmp = to_compat_path(`cmd.exe /C "echo %TEMP%"`.chomp) + '/'
|
75
|
+
when :msys
|
76
|
+
@@win_tmp = to_compat_path(ENV['temp'])
|
77
|
+
end
|
78
|
+
@@win_tmp
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.to_win_path(path)
|
82
|
+
path = Pathname.new(path).cleanpath.to_s
|
83
|
+
raise "Abs path is expected: #{path}" if path[0] != "/"
|
84
|
+
|
85
|
+
if path.start_with?(win_root_in_compat)
|
86
|
+
drive = path[win_root_in_compat.length]
|
87
|
+
"#{drive.upcase}:\\" + (path[(win_root_in_compat.length + 2)..-1] || '').gsub('/', '\\')
|
88
|
+
elsif compat_env == :wsl
|
89
|
+
raise "A WSL path which cannot be accessed from Windows: #{path}"
|
90
|
+
else
|
91
|
+
# [0...-1] trims trailing '/'
|
92
|
+
compat_root_in_win[0...-1] + path.gsub('/', '\\')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.to_compat_path(path)
|
97
|
+
if !compat_root_in_win.nil? && path.start_with?(compat_root_in_win)
|
98
|
+
path = path[compat_root_in_win.length - 1 .. -1]
|
99
|
+
end
|
100
|
+
if /^[a-zA-Z]:/ =~ path
|
101
|
+
drive = path[0]
|
102
|
+
path = win_root_in_compat + drive.downcase + (path[2..-1] || "")
|
103
|
+
end
|
104
|
+
path.gsub('\\', '/')
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'source_win_bat'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.date = '2019-01-05'
|
5
|
+
s.summary = "'source' Windows bat files in your UNIX compatible shell in Windows"
|
6
|
+
s.description = <<EOS
|
7
|
+
sw, or SourceWinBat, is a utility to run Windows batch files from WSL /
|
8
|
+
MSYS2 / Cygwin and sync environment variables, doskeys, and working
|
9
|
+
directories between batch files and their UNIX shell environments.
|
10
|
+
EOS
|
11
|
+
s.authors = ["Takaya Saeki"]
|
12
|
+
s.email = 'abc.tkys+pub@gmail.com'
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.executables = ["init_sw"]
|
15
|
+
s.homepage = 'http://rubygems.org/gems/source_win_bat'
|
16
|
+
s.license = 'MIT'
|
17
|
+
end
|
data/test/exit.cmd
ADDED
data/test/setcwd.cmd
ADDED
data/test/setdoskey.cmd
ADDED
data/test/setenv.cmd
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
eval "$("$(dirname "$(realpath "${BASH_SOURCE:-0}")")/../bin/init_sw")"
|
4
|
+
source ./tap.bash
|
5
|
+
|
6
|
+
to_unix_path () {
|
7
|
+
if [[ $(uname) = Linux ]]; then
|
8
|
+
wslpath "$@"
|
9
|
+
else
|
10
|
+
cygpath "$@"
|
11
|
+
fi
|
12
|
+
}
|
13
|
+
|
14
|
+
to_win_path () {
|
15
|
+
if [[ $(uname) = Linux ]]; then
|
16
|
+
wslpath -w "$@"
|
17
|
+
else
|
18
|
+
cygpath -w "$@"
|
19
|
+
fi
|
20
|
+
}
|
21
|
+
|
22
|
+
strip () {
|
23
|
+
sed -e 's/[ \r]*$//'
|
24
|
+
}
|
data/test/tap.bash
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
tap_tests () {
|
2
|
+
echo 1..$1
|
3
|
+
}
|
4
|
+
|
5
|
+
declare -i tap_test_counter=0
|
6
|
+
_tap_echo_result () {
|
7
|
+
local mes
|
8
|
+
|
9
|
+
tap_test_counter=tap_test_counter+1
|
10
|
+
if [[ -n $2 ]]; then
|
11
|
+
mes=" - $2"
|
12
|
+
else
|
13
|
+
mes=""
|
14
|
+
fi
|
15
|
+
|
16
|
+
if [[ $1 == 0 ]]; then
|
17
|
+
echo ok $tap_test_counter$mes
|
18
|
+
else
|
19
|
+
echo not ok $tap_test_counter$mes
|
20
|
+
fi
|
21
|
+
|
22
|
+
}
|
23
|
+
|
24
|
+
tap_ok () {
|
25
|
+
_tap_echo_result 0 "$1"
|
26
|
+
}
|
27
|
+
|
28
|
+
tap_notok() {
|
29
|
+
_tap_echo_result 1 "$1"
|
30
|
+
}
|
31
|
+
|
32
|
+
tap_okif() {
|
33
|
+
_tap_echo_result $1 "$2"
|
34
|
+
}
|
35
|
+
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
source ./setup_test.bash
|
4
|
+
tap_tests 6
|
5
|
+
|
6
|
+
export jp=日本語
|
7
|
+
[[ $(sw echo %jp% | strip) = "日本語" ]]; tap_okif $?
|
8
|
+
sw echo %jp% > /dev/null # Re-import $jp from Windows
|
9
|
+
[[ $(echo $jp) = "日本語" ]]; tap_okif $? "Test a varible with Japanese value keeps its value after sw"
|
10
|
+
sw set jp2=あいうえお > /dev/null
|
11
|
+
[[ $(echo $jp2) = "あいうえお" ]]; tap_okif $?
|
12
|
+
|
13
|
+
|
14
|
+
tmp="$(to_unix_path "$(sw echo %TEMP% | strip)")/sw_日本語ディレクトリ"
|
15
|
+
rm -rf "$tmp"
|
16
|
+
mkdir "$tmp"
|
17
|
+
win_tmp="$(to_win_path "$tmp")"
|
18
|
+
sw cd "$win_tmp"
|
19
|
+
[[ `pwd` = "$tmp" ]]; tap_okif $?
|
20
|
+
[[ $(sw echo %jp% | strip) = "日本語" ]]; tap_okif $?
|
21
|
+
[[ $(sw cd | strip) = "$win_tmp" ]]; tap_okif $?
|
22
|
+
rm -rf "$tmp"
|
data/test/test_exit.bash
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
source ./setup_test.bash
|
4
|
+
tap_tests 3
|
5
|
+
|
6
|
+
sw exit 0
|
7
|
+
[[ $? = 0 ]]; tap_okif $? "Test exitcode is propagated 1"
|
8
|
+
|
9
|
+
sw exit 42
|
10
|
+
[[ $? = 42 ]]; tap_okif $? "Test exitcode is propagated 2"
|
11
|
+
|
12
|
+
sw exit.cmd
|
13
|
+
[[ $? = 42 ]]; tap_okif $? "Test exitcode is propagated 3"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
source ./setup_test.bash
|
4
|
+
tap_tests 2
|
5
|
+
|
6
|
+
sw setcwd.cmd
|
7
|
+
|
8
|
+
expected="[cC]/Windows/System32/drivers /.*[cC]/Windows .*/[cC]\$"
|
9
|
+
[[ $(dirs) =~ $expected ]]; tap_okif $? "test if pushd works"
|
10
|
+
[[ $(pwd) =~ [cC]/Windows/System32/drivers$ ]]; tap_okif $? "test if cd works"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
source ./setup_test.bash
|
4
|
+
tap_tests 4
|
5
|
+
|
6
|
+
sw setdoskey.cmd
|
7
|
+
|
8
|
+
expected="bar \r?"
|
9
|
+
[[ $(foo) =~ $expected ]]; tap_okif $?
|
10
|
+
expected="foo \r?"
|
11
|
+
[[ $(echo1stparam foo bar) =~ $expected ]]; tap_okif $?
|
12
|
+
expected="foo bar \r?"
|
13
|
+
[[ $(echoallparams foo bar) =~ $expected ]]; tap_okif $?
|
14
|
+
expected="Microsoft Windows"
|
15
|
+
[[ $(verver) =~ $expected ]]; tap_okif $?
|
16
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
source ./setup_test.bash
|
4
|
+
tap_tests 5
|
5
|
+
|
6
|
+
sw setenv.cmd
|
7
|
+
|
8
|
+
[[ "$WINENV1" = FOO ]]; tap_okif $? "Test a variable is imported from a bat file 1"
|
9
|
+
[[ "$WINENV2" = BAR ]]; tap_okif $? "Test a variable is imported from a bat file 2"
|
10
|
+
[[ "$PATH" =~ ^/[^:]*[cC]/?: ]]; tap_okif $? "Test PATH is imported from a bat file and converted to UNIX-style"
|
11
|
+
[[ $(sw echo %WINENV1%) =~ FOO ]]; tap_okif $? "Test a variable is exported to a bat file 1"
|
12
|
+
export UNIXENV1=BUZ
|
13
|
+
[[ $(sw echo %UNIXENV1%) =~ BUZ ]]; tap_okif $? "Test a variable is exported to a bat file 2"
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: source_win_bat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Takaya Saeki
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: "sw, or SourceWinBat, is a utility to run Windows batch files from WSL
|
14
|
+
/\nMSYS2 / Cygwin and sync environment variables, doskeys, and working \ndirectories
|
15
|
+
between batch files and their UNIX shell environments.\n"
|
16
|
+
email: abc.tkys+pub@gmail.com
|
17
|
+
executables:
|
18
|
+
- init_sw
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- bin/init_sw
|
26
|
+
- lib/source_win_bat.rb
|
27
|
+
- lib/unixcompatenv.rb
|
28
|
+
- source_win_bat.gemspec
|
29
|
+
- test/exit.cmd
|
30
|
+
- test/setcwd.cmd
|
31
|
+
- test/setdoskey.cmd
|
32
|
+
- test/setenv.cmd
|
33
|
+
- test/setup_test.bash
|
34
|
+
- test/tap.bash
|
35
|
+
- test/test_ansicpchars.bash
|
36
|
+
- test/test_exit.bash
|
37
|
+
- test/test_optionparsebug.bash
|
38
|
+
- test/test_setcwd.bash
|
39
|
+
- test/test_setdoskey.bash
|
40
|
+
- test/test_setenv.bash
|
41
|
+
homepage: http://rubygems.org/gems/source_win_bat
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.7.6
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: "'source' Windows bat files in your UNIX compatible shell in Windows"
|
65
|
+
test_files: []
|