How to manage module dependencies in jq - module

Since version 1.5 the jq data processing language has a library module system. A module consists of optional metadata and a set of functions. For instance
module { name: "util", version: "1.0.0" };
def digitsum: tostring|split("")|map(tonumber)|add;
stored as file util.jq can be used like this:
$ echo '789' | jq -L. 'include "util"; digitsum'
24
Modules can use other modules and the dependencies are tracked by the modulemeta directive but how to express and check for a minimum version of a module number? For instance:
module {
name: "math",
version: "0.1.0",
};
include "util"; # TODO: require at least version 1.0.0!
def digitroot:
(.|digitsum) as $sum |
if $sum<10 then $sum else $sum|digitroot end;

The support for modules in jq is currently (June 2019) still very minimal, though on github there is a module management system for jq: https://github.com/joelpurra/jqnpm
Without using such an external module management system, what can be done in jq itself? Extending the given example, the following illustrates one approach to supporting version requirements. Notice the additional key named dependencies in the metadata of the math module. (Currently, this key cannot be named deps as jq overwrites it.)
Files
dependencies.jq
# Recursively check specified version constraints
module { name: "dependencies", version: "0.0.2" };
# parents of a module as defined by its .deps
def parents:
. as $in
| if type == "array" then map(parents) | add
else modulemeta | .deps | map(.relpath)
end ;
# ancestors of a single module or an array of modules.
# The array of "ancestors" of a module includes itself.
def ancestors:
# input and $visited should be arrays of strings
def ancestors($visited):
. as $in
| ($in - $visited) as $new
| if $new == [] then $visited
else $new | parents | ancestors($visited + $new | unique)
end;
if type == "array" then . else [.] end
| ancestors([]) ;
def versionsort:
def parse:
sub("(?<a>(alpha|beta|gamma))"; "\(.a).")
| [splits("[-.]")]
| map(tonumber? // .) ;
sort_by(parse);
# Input: a module name
# Emit empty if the constraints for the given module are satisfied, otherwise raise an error
def dependencies($version):
def le($y): (. == $y) or ([.,$y] | . == versionsort);
modulemeta
| .version as $mv
| if (($mv == null) or ($version | le($mv))) then empty
else ("module \(.name) dependencies version \($version) vs \($mv)" | error)
end ;
# Input: a module name or array of module names
# Check the module-version dependencies in .dependencies, proceeding up the chain as defined by .deps
def dependencies:
def check:
modulemeta
| select(has("dependencies"))
| all( .dependencies | to_entries[];
.key as $m | .value as $v | ($m | dependencies($v) ))
| empty;
ancestors[] | check;
util.jq
module { name: "util", version: "1.0.0" };
def digitsum: tostring|split("")|map(tonumber)|add;
math.jq
module {
name: "math",
version: "0.1.0",
dependencies: {"util": "1.0.0"} };
include "util" ;
def digitroot:
digitsum as $sum
| if $sum<10 then $sum
else $sum|digitroot
end;
Invocation
jq -n -L . '
include "dependencies";
include "math";
"math" | dependencies,
(123|digitroot) '

Related

Nextflow name collision

I have files with identical names but in different folders. Nextflow stages these files into the same work directory resulting in name collisions. My question is how to deal with that without renaming the files. Example:
# Example data
mkdir folder1 folder2
echo 1 > folder1/file.txt
echo 2 > folder2/file.txt
# We read from samplesheet
$ cat samplesheet.csv
sample,file
sample1,/home/atpoint/foo/folder1/file.txt
sample1,/home/atpoint/foo/folder2/file.txt
# Nextflow main.nf
#! /usr/bin/env nextflow
nextflow.enable.dsl=2
// Read samplesheet and group files by sample (first column)
samplesheet = Channel
.fromPath(params.samplesheet)
.splitCsv(header:true)
.map {
sample = it['sample']
file = it['file']
tuple(sample, file)
}
ch_samplesheet = samplesheet.groupTuple(by:0)
// That creates a tuple like:
// [sample1, [/home/atpoint/foo/folder1/file.txt, /home/atpoint/foo/folder2/file.txt]]
// Dummy process that stages both files into the same work directory folder
process PRO {
input:
tuple val(samplename), path(files)
output:
path("out.txt")
script:
"""
echo $samplename with files $files > out.txt
"""
}
workflow { PRO(ch_samplesheet) }
# Run it
NXF_VER=21.10.6 nextflow run main.nf --samplesheet $(realpath samplesheet.csv)
...obviously resulting in:
N E X T F L O W ~ version 21.10.6
Launching `main.nf` [adoring_jennings] - revision: 87f26fa90b
[- ] process > PRO -
Error executing process > 'PRO (1)'
Caused by:
Process `PRO` input file name collision -- There are multiple input files for each of the following file names: file.txt
So, what now? The real world application here is sequencing replicates of the same fastq file, which then have the same name, but are in different folders, and I want to feed them into a process that merges them. I am aware of this section in the docs but cannot say that any of it was helpful or that I understand it properly.
You can use stageAs option in your process definition.
#! /usr/bin/env nextflow
nextflow.enable.dsl=2
samplesheet = Channel
.fromPath(params.samplesheet)
.splitCsv(header:true)
.map {
sample = it['sample']
file = it['file']
tuple(sample, file)
}
.groupTuple()
.set { ch_samplesheet }
// [sample1, [/path/to/folder1/file.txt, /path/to/folder2/file.txt]]
process PRO {
input:
tuple val(samplename), path(files, stageAs: "?/*")
output:
path("out.txt")
shell:
def input_str = files instanceof List ? files.join(" ") : files
"""
cat ${input_str} > out.txt
"""
}
workflow { PRO(ch_samplesheet) }
See an example from nf-core and the path input type docs

Can't find package from application written in Tcl

Thanks to the comments, better understand the problem a bit. The variables:
thufir#dur:~/tcl/packages$
thufir#dur:~/tcl/packages$ echo 'puts $auto_path' | tclsh
/usr/share/tcltk/tcl8.6 /usr/share/tcltk /usr/lib /usr/local/lib/tcltk /usr/local/share/tcltk /usr/lib/tcltk/x86_64-linux-gnu /usr/lib/tcltk /usr/lib/tcltk/tcl8.6
thufir#dur:~/tcl/packages$
thufir#dur:~/tcl/packages$ echo 'puts $tcl_pkgPath' | tclsh
/usr/local/lib/tcltk /usr/local/share/tcltk /usr/lib/tcltk/x86_64-linux-gnu /usr/lib/tcltk /usr/share/tcltk /usr/lib/tcltk/tcl8.6 /usr/lib
thufir#dur:~/tcl/packages$
code:
thufir#dur:~/tcl/packages$
thufir#dur:~/tcl/packages$ ll
total 16
drwxrwxr-x 2 thufir thufir 4096 May 4 02:22 ./
drwxrwxr-x 6 thufir thufir 4096 May 4 02:22 ../
-rw-rw-r-- 1 thufir thufir 215 May 4 02:21 foo.tcl
-rw-rw-r-- 1 thufir thufir 1207 May 4 02:20 tutstack.tcl
thufir#dur:~/tcl/packages$
thufir#dur:~/tcl/packages$ cat foo.tcl
package require tutstack 1.0
set stack [tutstack::create]
foreach num {1 2 3 4 5} { tutstack::push $stack $num }
while { ![tutstack::empty $stack] } {
puts "[tutstack::pop $stack]"
}
tutstack::destroy $stack
thufir#dur:~/tcl/packages$
thufir#dur:~/tcl/packages$ cat tutstack.tcl
# Register the package
package provide tutstack 1.0
package require Tcl 8.5
# Create the namespace
namespace eval ::tutstack {
# Export commands
namespace export create destroy push pop peek empty
# Set up state
variable stack
variable id 0
}
# Create a new stack
proc ::tutstack::create {} {
variable stack
variable id
set token "stack[incr id]"
set stack($token) [list]
return $token
}
# Destroy a stack
proc ::tutstack::destroy {token} {
variable stack
unset stack($token)
}
# Push an element onto a stack
proc ::tutstack::push {token elem} {
variable stack
lappend stack($token) $elem
}
# Check if stack is empty
proc ::tutstack::empty {token} {
variable stack
set num [llength $stack($token)]
return [expr {$num == 0}]
}
# See what is on top of the stack without removing it
proc ::tutstack::peek {token} {
variable stack
if {[empty $token]} {
error "stack empty"
}
return [lindex $stack($token) end]
}
# Remove an element from the top of the stack
proc ::tutstack::pop {token} {
variable stack
set ret [peek $token]
set stack($token) [lrange $stack($token) 0 end-1]
return $ret
}
thufir#dur:~/tcl/packages$
thufir#dur:~/tcl/packages$ tclsh foo.tcl
can't find package tutstack 1.0
while executing
"package require tutstack 1.0"
(file "foo.tcl" line 1)
thufir#dur:~/tcl/packages$
to my understanding, I need to compile a list or map of where packages are.
The problem is that Tcl is not finding the index file (which should be called pkgIndex.tcl) for your package. If you had implemented the weather 1.0 package as a file weather.tcl, then you'd probably be looking to have an index file something like this in the same directory:
package ifneeded weather 1.0 [list source [file join $dir weather.tcl]]
That says “to load version 1.0 of the weather package, run this script” where the script is generated at runtime and binds $dir in (which is a variable always defined in the context where package index loader runs package ifneeded).
Once that's there, you need to allow Tcl to find the index file. This can be done by putting that directory or its immediate parent on the Tcl global auto_path list; either do that inside your script before you load any packages (very useful for applications that have internal packages) or you can initialise that from outside of Tcl too by setting the TCLLIBPATH environment variable. Note that the value of that variable is a Tcl list of directories, not a system path like env(PATH). This matters if you have backslashes or spaces in directory names, or if you want to have multiple elements on the list. Fortunately, you can usually avoid all of these issues in the case of adding a single directory as an environment variable, even on Windows, by using / instead of \ and by following usual installation practice and not putting a space in names. When adding a path during application launch it's easier: you just use lappend, perhaps like this (very early in your main script):
lappend auto_path [file join [file dirname [info script]] my_app_pacakges]
# If the script is in foo/bar.tcl then packages are in or below foo/my_app_packages
result which runs:
thufir#dur:~/tcl/foo$
thufir#dur:~/tcl/foo$ tree
.
├── api
│   ├── pkgIndex.tcl
│   └── tutstack.tcl
└── main.tcl
1 directory, 3 files
thufir#dur:~/tcl/foo$
thufir#dur:~/tcl/foo$ cat main.tcl
lappend auto_path /home/thufir/tcl/foo/api
package require tutstack 1.0
set stack [tutstack::create]
foreach num {1 2 3 4 5} { tutstack::push $stack $num }
while { ![tutstack::empty $stack] } {
puts "[tutstack::pop $stack]"
}
tutstack::destroy $stack
thufir#dur:~/tcl/foo$
thufir#dur:~/tcl/foo$ cat api/pkgIndex.tcl
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script. It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
package ifneeded tutstack 1.0 [list source [file join $dir tutstack.tcl]]
thufir#dur:~/tcl/foo$
thufir#dur:~/tcl/foo$ cat api/tutstack.tcl
# Register the package
package provide tutstack 1.0
package require Tcl 8.5
# Create the namespace
namespace eval ::tutstack {
# Export commands
namespace export create destroy push pop peek empty
# Set up state
variable stack
variable id 0
}
# Create a new stack
proc ::tutstack::create {} {
variable stack
variable id
set token "stack[incr id]"
set stack($token) [list]
return $token
}
# Destroy a stack
proc ::tutstack::destroy {token} {
variable stack
unset stack($token)
}
# Push an element onto a stack
proc ::tutstack::push {token elem} {
variable stack
lappend stack($token) $elem
}
# Check if stack is empty
proc ::tutstack::empty {token} {
variable stack
set num [llength $stack($token)]
return [expr {$num == 0}]
}
# See what is on top of the stack without removing it
proc ::tutstack::peek {token} {
variable stack
if {[empty $token]} {
error "stack empty"
}
return [lindex $stack($token) end]
}
# Remove an element from the top of the stack
proc ::tutstack::pop {token} {
variable stack
set ret [peek $token]
set stack($token) [lrange $stack($token) 0 end-1]
return $ret
}
thufir#dur:~/tcl/foo$
thufir#dur:~/tcl/foo$ tclsh main.tcl
5
4
3
2
1
thufir#dur:~/tcl/foo$
generating the config file:
thufir#dur:~/tcl/foo/api$
thufir#dur:~/tcl/foo/api$ tclsh
%
%
% pkg_mkIndex . *.tcl
%
% exit
thufir#dur:~/tcl/foo/api$

Powershell Add Spaces to Registry Data Output

I'm trying to create a small script, that can easily display some valid information to the standard user in regards of getting IT assistance from ServiceDesk.
Current output
So to improve this i was trying to figure out if i could add spaces to the teamviewer result.
This is an example of the current team viewer ID outcome:
1483547869
But i would like if the outcome could be:
1 483 547 869
This is a small thing but it will make it a lot easier to read for the standard user.
This is my code:
Add-Type -AssemblyName System.Windows.Forms
$ip=get-WmiObject Win32_NetworkAdapterConfiguration|Where {$_.Ipaddress.length -gt 1}
$ipaddress = $ip.ipaddress[0]
$hostname = [System.Net.Dns]::GetHostName()
$TeamViewerVersions = #('10','11','12','13','14','')
If([IntPtr]::Size -eq 4) {
$RegPath='HKLM:\SOFTWARE\TeamViewer'
} else {
$RegPath='HKLM:\SOFTWARE\Wow6432Node\TeamViewer'
}
$ErrorActionPreference= 'silentlycontinue'
foreach ($TeamViewerVersion in $TeamViewerVersions) {
If ((Get-Item -Path $RegPath$TeamViewerVersion).GetValue('ClientID') -ne $null) {
$TeamViewerID=(Get-Item -Path $RegPath$TeamViewerVersion).GetValue('ClientID')
}
}
$msgBoxInput = [System.Windows.Forms.MessageBox]::Show("Computer Name: $hostname`nIP Address: $ipaddress`nTeamViewer ID: $TeamviewerID`n`nWould you like to open Self Service Portal?", 'Quick Support','YesNo','Information')
If ($msgBoxInput -eq 'Yes' ){
start https://www.google.com/
Else
}
Stop-Process -Id $PID
Here's a really simple solution to format a number:
$TeamViewerDisplayID = $TeamViewerID.toString("### ### ### ###")
This will display 1483547869 as 1 483 547 869. Note: if your number will have 9 characters for example, the above line of code will add a space to the beginning. Example: "483547869" becomes "_483 547 869". So if you want, you can add another if statement there that checks how long the number is and formats it accordingly:
if ($TeamViewerID.length -gt 9) {
$TeamViewerID.toString("### ### ### ###")
} else {
$TeamViewerID.toString("### ### ###")
}

Exclude list of Vms from script deleting snapshots in vmware

I have a powercli script which is scheduled to delete the VMs older than X days, recently we got a list of VMs which are supposed to be excluded from the snapshot deletion as these are critical Snapshots.
How do I introduce the parameter exclude VM in my script to compare the VMS with a snapshot in Vcenter to the list I provide and list and delete only VMS which meets the criteria of not older than X days and not part of the Exclude VM list.
I am relatively new and using below code to fetch snapshots older than 10 Days and delete them.
# vCenter Server configuration
$vcenter = "Vcenter Name"
$vcenteruser = "Domain\Userid"
$vcenterpw = "Password"
#Connect to the vCenter server defined above. Ignore certificate errors
Connect to vcenter Server connect-viserver $vcenter -User $vcenteruser -Password $vcenterpw"
Add-PSSnapin VMware.VimAutomation.Core -ErrorAction 'SilentlyContinue'
Clear-Host
$old_snapshots = Get-VM | Get-Snapshot |? { ([DateTime]::Now - $_.Created).TotalDays -gt 7 } | Remove-Snapshot: $old_snapshots | Remove-Snapshot -RunAsync -Confirm:$false
I need to figure out what if i have a list of 'vms' having snapshots older than 10 days which shouldn't be deleted. I want to exclude those 'Vms' but i am not sure how to do that.
So I tried Using the logic by # I.T Delinquent . I put a value in '$VmToIgnore' and compare it with the list of VMs received in 'Get-Vm'. If it is true do nothing, If it s false, get snapshot and other attribute of that VM and export it to CSV.
$vmsToIgnore ="Vm1"
$e = Get-VM
Foreach-Object {
if ($vmsToIgnore -Contains $_.Name){
#Do nothing as VM name is in the vmsToIgnore list
}else{
$f = $e |get-Snapshot| Select-object
vm,VMId,name,Description,SizeGB,created
$f| Export-Csv -Path "\\%path%\snapshot.csv"
}
}
This is still returning list of all 'VM' Snapshots including the one n '$VMToIgnore'.
I must be making some mistake here as it should not print the '$VmToIgnore' in Excel.
Could you build upon something like this:
$vmsToIgnore = "VM1","VM2"
$old_Snapshots = Get-VM | Foreach-Object {
if ($vmsToIgnore -Contains $_.Name){
#Do nothing as VM name is in the vmsToIgnore list
}else{
#Perform tasks as vm isn't in vmsToIgnore list
}
}
I'm not really sure what Get-VM will return so please you will likely have to edit my example. Here is the basis of it though assuming that Get-VM returns a nice list of VMs:
$vmsToIgnore = "VM1","VM2"
$outputFromGetVM = "VM1","VM1.2","VM2","VM3"
foreach ($vm in $outputFromGetVM){
if ($vmsToIgnore -contains $vm){
Write-Host "Ignoring"
}else{
Write-Host "performing"
}
}
Let me know if you have any questions
UPDATE
I think this could be used:
$vmsToIgnore = "Vm1"
Get-VM | ForEach-Object {
if ($vmsToIgnore -contains $_.Name){
#Do nothing as VM name is in the vmsToIgnore list
}else{
Get-Snapshot $_ | Select-Object vm, VmId, Name, Description, SizeGB, Created | Export-Csv -Path "\\path\to\file.csv"
}
}
Sounds like you have the VM names as a list already in which case exclude them
$list = Get-VM | where {"name" -ne "mypreciousvm"}
You will need to play with -notmatch or -notcontains I can't remember which one you will need. Then pass the exclusion list below.
$old_snapshots = Get-VM -name $list | Get-Snapshot |? { ([DateTime]::Now - $_.Created).TotalDays -gt 7 } | Remove-Snapshot: $old_snapshots | Remove-Snapshot -RunAsync -Confirm:$false
If Vms come and go a lot then try to do it in one go otherwise you may get an error as you reget the vm name
$old_snapshots = Get-VM | where {$.name -notcontains $list}| Get-Snapshot |? { ([DateTime]::Now - $.Created).TotalDays -gt 7 } | Remove-Snapshot: $old_snapshots | Remove-Snapshot -RunAsync -Confirm:$false
Delete Snapshots that are older than 3 days
Get-VM | Get-Snapshot | ? {$_.created -lt (Get-Date).AddDays(-3)} | Remove-Snapshot -Confirm:$false -RunAsync

awk set variable to line after if statement match

I have the following text...
BIOS Information
Manufacturer : Dell Inc.
Version : 2.5.2
Release Date : 01/28/2015
Firmware Information
Name : iDRAC7
Version : 2.21.21 (Build 12)
Firmware Information
Name : Lifecycle Controller 2
Version : 2.21.21.21
... which is piped into the following awk statement...
awk '{ if ($1" "$2 == "BIOS Information") var=$1} END { print var }'
This will output 'BIOS' in this case.
I want to look for 'BIOS Information' and then set the third field, two lines down, so in this case 'var' would equal '2.5.2'. Is there a way to do this with awk?
EDIT:
I tried the following:
awk ' BEGIN {
FS="[ \t]*:[ \t]*";
}
NF==1 {
sectname=$0;
}
NF==2 && $1 == "Version" && sectname="BIOS Information" {
bios_version=$2;
}
END {
print bios_version;
}'
Which gives me '2.21.21.21' with the above text. Can this be modified to give me the first 'Version" following "BIOS Information"?
Following script may be an overkill but it is robust in cases if you have multiple section names and/or order of fields is changed.
BEGIN {
FS="[ \t]*:[ \t]*";
}
NF==1 {
sectname=$0;
}
NF==2 && $1 == "Version" && sectname=="BIOS Information" {
bios_version=$2;
}
END {
print bios_version;
}
First, we set input field separator so that words are not separated into different fields. Next, we check whether current line is section name or a key-value pair. If it is section name, set sectname to section name. If it is a key-value pair and current section name is "BIOS Information" and key is "Version" then we set bios_version.
To answer the question as asked:
awk -v RS= '
/^BIOS Information\n/ {
for(i=1;i<=NF;++i) { if ($i=="Version") { var=$(i+2); exit } }
}
END { print var }
' file
-v RS= puts awk in paragraph mode, so that each run of non-empty lines becomes a single record.
/^BIOS Information\n/ then only matches a record (paragraph) whose first line equals "BIOS Information".
Each paragraph is internally still split into fields by any run of whitespace (awk's default behavior), so the for loop loops over all fields until it finds literal Version, assigns the 2nd field after it to a variable (because : is parsed as a separate field) and exits, at which point the variable value is printed in the END block.
Note: A more robust and complete way to extract the version number can be found in the update below (the field-looping approach here could yield false positives and also only ever reports the first (whitespace-separated) token of the version field).
Update, based on requirements that emerged later:
To act on each paragraph's version number and create individual variables:
awk -v RS= '
# Helper function that that returns the value of the specified field.
function getFieldValue(name) {
# Split the record into everything before and after "...\n<name> : "
# and the following \n; the 2nd element of the array thus created
# then contains the desired value.
split($0, tokens, "^.*\n" name "[[:blank:]]+:[[:blank:]]+|\n")
return tokens[2]
}
/^BIOS Information\n/ {
biosVer=getFieldValue("Version")
print "BIOS version = " biosVer
}
/^Firmware Information\n/ {
firmVer=getFieldValue("Version")
print "Firmware version (" getFieldValue("Name") ") = " firmVer
}
' file
With the sample input, this yields:
BIOS version = 2.5.2
Firmware version (iDRAC7) = 2.21.21 (Build 12)
Firmware version (Lifecycle Controller 2) = 2.21.21.21
Given:
$ echo "$txt"
BIOS Information
Manufacturer : Dell Inc.
Version : 2.5.2
Release Date : 01/28/2015
Firmware Information
Name : iDRAC7
Version : 2.21.21 (Build 12)
Firmware Information
Name : Lifecycle Controller 2
Version : 2.21.21.21
You can do:
$ echo "$txt" | awk '/^BIOS Information/{f=1; printf($0)} /^Version/ && f{f=0; printf(":%s\n", $3)}'
BIOS Information:2.5.2