Expand tilde to home directory - path

I have a program that accepts a destination folder where files will be created. My program should be able to handle absolute paths as well as relative paths. My problem is that I don't know how to expand ~ to the home directory.
My function to expand the destination looks like this. If the path given is absolute it does nothing otherwise it joins the relative path with the current working directory.
import "path"
import "os"
// var destination *String is the user input
func expandPath() {
if path.IsAbs(*destination) {
return
}
cwd, err := os.Getwd()
checkError(err)
*destination = path.Join(cwd, *destination)
}
Since path.Join doesn't expand ~ it doesn't work if the user passes something like ~/Downloads as the destination.
How should I solve this in a cross platform way?

Go provides the package os/user, which allows you to get the current user, and for any user, their home directory:
usr, _ := user.Current()
dir := usr.HomeDir
Then, use path/filepath to combine both strings to a valid path:
if path == "~" {
// In case of "~", which won't be caught by the "else if"
path = dir
} else if strings.HasPrefix(path, "~/") {
// Use strings.HasPrefix so we don't match paths like
// "/something/~/something/"
path = filepath.Join(dir, path[2:])
}
(Note that user.Current() is not implemented in the go playground (likely for security reasons), so I can't give an easily runnable example).

In general the ~ is expanded by your shell before it gets to your program. But there are some limitations.
In general is ill-advised to do it manually in Go.
I had the same problem in a program of mine and what I have understood is that if I use the flag format as --flag=~/myfile, it is not expanded. But if you run --flag ~/myfile it is expanded by the shell (the = is missing and the filename appears as a separate "word").

Normally, the ~ is expanded by the shell before your program sees it.
Adjust how your program acquires its arguments from the command line in a way compatible with the shell expansion mechanism.
One of the possible problems is using exec.Command like this:
cmd := exec.Command("some-binary", someArg) // say 'someArg' is "~/foo"
which will not get expanded. You can, for example use instead:
cmd := exec.Command("sh", "-c", fmt.Sprintf("'some-binary %q'", someArg))
which will get the standard ~ expansion from the shell.
EDIT: fixed the 'sh -c' example.

If you are expanding tilde '~' for use with exec.Command() you should use the users local shell for expansion.
// 'sh', 'bash' and 'zsh' all respect the '-c' argument
cmd := exec.Command(os.Getenv("SHELL"), "-c", "cat ~/.myrc")
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
}
However; when loading application config files such as ~./myrc this solution is not acceptable. The following has worked well for me across multiple platforms
import "os/user"
import "path/filepath"
func expand(path string) (string, error) {
if len(path) == 0 || path[0] != '~' {
return path, nil
}
usr, err := user.Current()
if err != nil {
return "", err
}
return filepath.Join(usr.HomeDir, path[1:]), nil
}
NOTE: usr.HomeDir does not respect $HOME instead determines the home directory by reading the /etc/passwd file via the getpwuid_r syscall on (osx/linux). On windows it uses the OpenCurrentProcessToken syscall to determine the users home directory.

I know this is an old question but there is another option now. You can use go-homedir to expand the tidle to the user's homedir:
myPath := "~/.ssh"
fmt.Printf("path: %s; with expansion: %s", myPath, homedir.Expand(myPath))

This works on go >= 1.12:
if strings.HasPrefix(path, "~/") {
home, _ := os.UserHomeDir()
path = filepath.Join(home, path[2:])
}

Related

How evaluate external command to a Nix value?

I want to parse a file to a Nix list value inside flake.nix.
I have a shell script which does that
perl -007 -nE 'say for m{[(]use-package \s* ([a-z-0-9]+) \s* (?!:nodep)}xsgm' init.el
How can I execute external command while evaluating flake.nix?
programs.emacs = {
enable = true;
extraConfig = builtins.readFile ./init.el;
extraPackages = elpa: (shellCommandToParseFile ./init.el); # Runs shell script
};
You can run ./init.el by the same way you perform any other impure step in Nix: With a derivation.
This might look something vaguely like:
programs.emacs = {
enable = true;
extraConfig = ../init.el;
extraPackages = elpa:
let
packageListNix =
pkgs.runCommand "init-packages.nix" { input = ../init.el; } ''
${pkgs.perl}/bin/perl -007 -nE '
BEGIN {
say "{elpa, ...}: with elpa; [";
say "use-package";
};
END { say "]" };
while (m{[(]use-package \s* ([a-z-0-9]+) \s* (;\S+)?}xsgm) {
next if $2 eq ";builtin";
say $1;
}' "$input" >"$out"
'';
in (import "${packageListNix}" { inherit elpa; });
};
...assuming that, given the contents of your ./init.el, the contents of your resulting el-pkgs.nix is actually valid nix source code.
That said, note that like any other derivation (that isn't either fixed-output or explicitly impure), this happens inside a sandbox with no network access. If the goal of init.el is to connect to a network resource, you should be committing its output to your repository. A major design goal of flakes is to remove impurities; they're not suitable for impure derivations.

Passing strings to .wasm module

I've been stuck on this for a while now and I cannot seem to find good resources to my problem. I am coming from and "only C" background, so most of the web dev stuff is completely new for me.
I wrote a C function float editDistance(char *str1, char *str2) that returns the edit distance of 2 char arrays. Right now the goal is to successfully call this function from a JS environment.
After ensuring that the code works with the recommended Emscipten ccall method, I decided to move on. Now
I use Emscripten to compile the C code with flags -O3, -s WASM=1, -s EXPORTED_FUNCTIONS="['_editDistance']", and -s SIDE_MODULE=1 -s to Wasm. The JS code I'm trying to wrap around my WebAssembly is:
// Allocate memory for the wasm module to run in. (65536*256 bit)
let wasmMemory = new WebAssembly.Memory({
initial: 256
});
let info = {
env: {
abort: function() {},
memoryBase: 0,
tableBase: 0,
memory: wasmMemory,
table: new WebAssembly.Table({initial: 2, element: 'anyfunc'}),
}
}
// Define the strings
let str1 = "abcd";
let str2 = "abcd";
// Allocate memory on the wasm partition for the HEAPU8
let HEAPU8 = new Uint8Array(wasmMemory.buffer);
// Create the char arrays on the heap from the strings
let stackPtr = 0;
let str1Ptr = stackPtr;
stackPtr = stringToASCIIArray(str1, HEAPU8, stackPtr);
let str2Ptr = stackPtr;
stackPtr = stringToASCIIArray(str2, HEAPU8, stackPtr);
// Read the wasm file and instantiate it with the above environment setup. Then
// call the exported function with the string pointers.
let wasmBinaryFile = 'bin/edit_distanceW.wasm';
fetch(wasmBinaryFile, {credentials:"same-origin"})
.then((response) => response.arrayBuffer())
.then((binary) => WebAssembly.instantiate(binary,info))
.then((wa) => alert(wa.instance.exports._editDistance(str1Ptr, str2Ptr)));
// Converts a string to an ASCII byte array on the specified memory
function stringToASCIIArray(str, outU8Array, idx){
let length = str.length + 1;
let i;
for(i=0; i<length; i++){
outU8Array[idx+i] = str.charCodeAt(i);
}
outU8Array[idx+i]=0;
return (idx + length);
}
The generated wasm file when converted to wat demands these imports:
(import "env" "abort" (func (;0;) (type 0)))
(import "env" "memoryBase" (global (;0;) i32))
(import "env" "tableBase" (global (;1;) i32))
(import "env" "memory" (memory (;0;) 256))
(import "env" "table" (table (;0;) 2 anyfunc))
.. and exports these:
(export "__post_instantiate" (func 7))
(export "_editDistance" (func 9))
(export "runPostSets" (func 6))
(elem (;0;) (get_global 1) 8 1))
Now, when I test the code the strings are passed to the C module without a problem. A few function calls are even made on them (strLen) before things go south. In the C function there is this nasty nested loop that does the main computation, iterating thru a 2D array while reading the characters from the strings (C code just been ported from a paper with an ugly pseudo code, so pardon me the variable names):
do{
for(p=0; p<editDistance; p++){
// Do stuff
}
// Do more stuff
editDistance++;
} while(fkp[len2*2-len1][editDistance] != len1);
Before the function enters the for() loop, the module still has the strings on memory str1Ptr=0x00 and str2Ptr=0x05 with the correct length and content. On the contrary, immediately after entering the for() loop the memory gets overwritten by garbage (mostly 0s), corrupting the end result. I suspect some stack saving and restoration problems on the scope change, as the exact same code compiled to my PC using gcc works like a charm.
Any idea what setup I'm missing that hinders the correct completion of the C function?
If you are starting out you probably want to use the emscripten-generated JS glue. That is, don't use SIDE_MODULE=1 and instead output to a files calle .js. The emscripten compiler will then generate both a .js and a .wasm file. You can then include the .js file in your project and it will handle all the loading and setup for you.
If you try to load the wasm file yourself, you will need to do a lot of work to replicate the emscripten environment, which will require a lot of internal details of emscripten. Also, those internal details of subject to change when you update to the new version of emscripten so you are creating more work for yourself.

if condition always returning False

I am writing a powershell-v2.0 script. The script contains a function to set environment variable. The function is as follows:
function SetSvnEnvironmentVariable($path)
{
# Get the Path environment variable
$pathVar = [Environment]::GetEnvironmentVariable("Path","User")
# Check if path environment variable already contains the path to be added
if(-Not ($pathVar -like $path))
{
$pathVar += $path
[Environment]::SetEnvironmentVariable("Path",$pathVar, [System.EnvironmentVariableTarget]::User)
}
}
The $path variable contains the path to be added to the PATH environment variable.
The problem I am facing is, even though $path is a substring of $pathVar, the if condition always returns False.
Following are the values of $pathVar and $path.
$pathVar:
C:\Program Files (x86)\MATLAB\R2009b\bin\win32;C:\Program Files\TortoiseSVN\bin
$path:
;C:\Program Files\TortoiseSVN\bin
Using wildcard or regular expression matches for this kind of check bears the risk of getting false positives if the path already contains a subfolder of the folder you want to add. It's better to split the value of the environment variable at semicolons and use the -contains/-notcontains operator to check if the resulting array does or doesn't contain the item you want to add:
function SetSvnEnvironmentVariable($path)
{
$pathVar = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($pathVar.Split(';') -notcontains $path))
{
$pathVar += ";$path"
[Environment]::SetEnvironmentVariable('Path', $pathVar, [EnvironmentVariableTarget]::User)
}
}
Note that $path should not be specified with a leading (or trailing) semicolon.
Using a wildcard fixed my problem. -like operator without a wildcard compared the strings if they were equal and hence always returned False.
By changing the if condition as follows, the if condition returns True:
if(-Not ($pathVar -like "*$path*"))
{
....
}

Line break support in parser (TCL)

So, I have a parser, written in TCL. There are many commands in the parsing file. Now, I need to add support for line breaks.
For ex.
my_command \
arg1 \
arg2 \
arg3
I have something like this.
while { ! [eof $currentFileDescriptor] } {
set line [gets $currentFileDescriptor]
set lst [lindex [regexp -all -inline {^(\s*(\S*)\s*)*(\{(.*)\})?(\s*(\S*)\s*)*$} $line] 0]
set tok [string toupper [lindex $lst 0]]
switch -glob $tok {
"\#*" { }
"MY_COMMAND_1" { parseMyCommand1 $handler $lst }
.....#other commands }
}
incr lnum
}
I am looking for an optimal and effective solution.
It looks like you have defined a domain specific language (DSL) with the parsing implemented in Tcl. You may as well use the Tcl parsing itself to deal with things like line continuation and quote handling. The method to do this is to create a safe interpreter and in the safe interpreter only provide the commands required for your DSL. You then interpret your config file in the safe child interpreter. The wiki page has some examples.
The advantage of this method is that the parsing is handled by the normal Tcl parser. However you can be in complete control of what commands are exposed in the safe interpreter. You can also control the amount of resources it can use (stack and memory) and limit it's visibility of the filesystem or network.
If you don't want to get into this then you just need to implement recognition of backslashed newlines and buffer such lines until you have a complete line. Something like the following (untested):
set linenum 0
set buffer ""
while {[gets $input line] != -1} {
incr linenum
if {[regexp {\\$} $line]} {
append buffer [string range $line 0 end-1]
continue
} else {
append buffer $line
}
ParseCompleteLine $linenum $buffer
set buffer ""
}

How do I read an environment variable in Verilog/System Verilog?

How do I read an environment variable in Verilog ? (Running on a VCS simulator)
I am trying to accomplish
File=$fopen("$PATH/FileName","r");
$PATH is an environment variable.
You can simply use SystemVerilog DPI for getting environment.
And because getenv is a standard C library for every POSIX platform, so you do not need to implement your own getenv() equivalent function for the function definition again.
Example code in SV.
import "DPI-C" function string getenv(input string env_name);
module top;
initial begin
$write("env = %s\n", {getenv("HOME"), "/FileName"});
end
endmodule
Running
ncverilog -sv dpi.v
or
vcs -sverilog dpi.v
It will show
env = /home/user/FileName
And one more issue in your original question, PATH is a environment for executable search path and concatenate with ":" character. I think it should be an example here, not really "PATH" environment. Otherwise, your fopen file name could be "/bin:/usr/bin:/usr/local/bin/FileName", which is wrong.
You can use a simple PLI application to read an environment variable. Here's a sample, without any error checks:
#include <stdlib.h>
#include <string.h>
#include "vpi_user.h"
PLI_INT32 pli_getenv (PLI_BYTE8 * arg) {
vpiHandle tf_obj = vpi_handle (vpiSysTfCall, NULL);
vpiHandle arg_iter = vpi_iterate (vpiArgument, tf_obj);
vpiHandle arg1, arg2;
arg1 = vpi_scan (arg_iter);
arg2 = vpi_scan (arg_iter);
s_vpi_value vi, vo;
vi.format = vpiStringVal;
vpi_get_value (arg2, &vi);
vo.format = vpiStringVal;
vo.value.str = strdup (getenv (vi.value.str));
vpi_put_value (arg1, &vo, NULL, vpiNoDelay);
return 0;
}
The VCS documentation should explain how to link this into the simulator.
It is often simpler to use the Verilog preprocessor
File = $fopen(`PATH_FILENAME, "r");
Then invoke the simulator from your Makefile/shell script the specifying value to be substituted
$(SIM) -DPATH_FILENAME=\"$PATH/FileName\" blah.v ...
I use this with Icarus' iverilog often, vsim and friends probably support similar.
Quotes are escaped so that they are included in the substituted value, since the preprocessor will not substitute inside a literal value. For instance this combination does not work:
File = $fopen("`PATH_FILENAME", "r");
...
`$(SIM) -DPATH_FILENAME=$PATH/FileName blah.v ...`
Here I can see all answers, either they are using some DPI Or need some command line arguments. So I am sharing my answer with only SystemVerilog syntax. Answer is not specific to any simulator. But surely it is for Linux environment; for other OS we need to change $system commands.
We need to set this "logPath" system variable using some pre
processing script or by simulation script before we start our
simulation.
string myPath;
initial begin
//Writing System Variable To A File
$system("echo ${logPath} > logPath.txt");
//Opening that file and reading to a string variable
fh = $fopen ("./logPath.txt", "r");
void'($fscanf(fh,"%s",myPath));
//Appending File Name To That Path
myPath = {myPath,"/note.txt"};
//Closed and remove this temporary file
$fclose(fh);
$system("rm -rf logPath.txt");
//Open a file at the path that you have extracted from System Variable
//Do whatever you want now
fh = $fopen (myPath, "w");
repeat(10) begin
$fdisplay (fh, "%t %M: Write Line Number =|%0d| ", $time, i);
i++;
end
$fclose(fh);
end

Resources