Giter Site home page Giter Site logo

fs's Introduction

Babashka

CircleCI project chat Financial Contributors on Open Collective Clojars Project twitter docs

Life's too short to remember how to write Bash code. I feel liberated.

@laheadle on Clojurians Slack

Introduction

Babashka is a native Clojure interpreter for scripting with fast startup. Its main goal is to leverage Clojure in places where you would be using bash otherwise.

As one user described it:

I’m quite at home in Bash most of the time, but there’s a substantial grey area of things that are too complicated to be simple in bash, but too simple to be worth writing a clj/s script for. Babashka really seems to hit the sweet spot for those cases.

Goals

  • Fast starting Clojure scripting alternative for JVM Clojure
  • Easy installation: grab the self-contained binary and run. No JVM needed.
  • Familiar: targeted at JVM Clojure users
  • Cross-platform: supports linux, macOS and Windows
  • Interop with commonly used classes (System, File, java.time.*, java.nio.*)
  • Multi-threading support (pmap, future)
  • Batteries included (tools.cli, cheshire, ...)

Non-goals

  • Provide a mixed Clojure/Bash DSL (see portability).
  • Replace existing shells. Babashka is a tool you can use inside existing shells like bash and it is designed to play well with them. It does not aim to replace them.

Quickstart

For installation options check Installation. For quick installation use:

$ bash < <(curl -s https://raw.githubusercontent.com/babashka/babashka/master/install)

or grab a binary from Github releases yourself and place it anywhere on the path.

Then you're ready to go:

$ ls | bb -i '(filter fs/directory? *input*)'
("doc" "resources" "sci" "script" "src" "target" "test")
bb took 4ms.

Support ❤️

You can support this project via Github Sponsors, OpenCollective, Ko-fi or indirectly via Clojurists Together.

Top sponsors

Babashka users

See companies for a list of companies using babashka.

Are you using babashka in your company or personal projects? Let us know here.

Setting expectations

Babashka uses SCI for interpreting Clojure. SCI implements a substantial subset of Clojure. Interpreting code is in general not as performant as executing compiled code. If your script takes more than a few seconds to run or has lots of loops, Clojure on the JVM may be a better fit as the performance on JVM is going to outweigh its startup time penalty. Read more about the differences with Clojure here.

Status

Functionality regarding clojure.core and java.lang can be considered stable and is unlikely to change. Changes may happen in other parts of babashka, although we will try our best to prevent them. Always check the release notes or CHANGELOG.md before upgrading.

Talk

To get an overview of babashka, you can watch this talk (slides):

Babashka at ClojureD 2020

Babashka book

The babashka book contains detailed information about how to get the most out of babashka scripting.

There is also the book Babashka Babooka, by Daniel Higginbotham, who has also helped a lot of people learn Clojure with Clojure for the Brave and True.

Examples

Read the output from a shell command as a lazy seq of strings:

$ ls | bb -i '(take 2 *input*)'
("CHANGES.md" "Dockerfile")

Read EDN from stdin and write the result to stdout:

$ bb '(vec (dedupe *input*))' <<< '[1 1 1 1 2]'
[1 2]

Read more about *input* and in- and output flags here.

Execute a script. E.g. print the current time in California using the java.time API:

File pst.clj:

#!/usr/bin/env bb

(def now (java.time.ZonedDateTime/now))
(def LA-timezone (java.time.ZoneId/of "America/Los_Angeles"))
(def LA-time (.withZoneSameInstant now LA-timezone))
(def pattern (java.time.format.DateTimeFormatter/ofPattern "HH:mm"))
(println (.format LA-time pattern))
$ bb pst.clj
05:17

More examples can be found here.

Try online

You can try babashka online with Nextjournal's babashka notebook environment.

Installation

Brew

Linux and macOS binaries are provided via brew.

Install:

brew install borkdude/brew/babashka

Upgrade:

brew upgrade babashka

Nix

Linux and macOS (including ARM Macs) binaries are provided via nix (see the installation instructions for nix here).

Install:

# Adding `nixpkgs-unstable` channel for more up-to-date binaries, skip this if you already have `nixpkgs-unstable` in your channel list
nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable
nix-channel --update
nix-env -iA nixpkgs-unstable.babashka

Upgrade:

nix-channel --update
nix-env -iA nixpkgs-unstable.babashka

You can find more documentation on how to use babashka with nix here.

Alpine

On Alpine it's recommended to download the binary manually from Github Releases and use the static linux binary.

Arch (Linux)

babashka is available in the Arch User Repository. It can be installed using your favorite AUR helper such as yay, yaourt, apacman and pacaur. Here is an example using yay:

yay -S babashka-bin

asdf

asdf is an extendable version manager for linux and macOS. Note that asdf will add significant startup time to any babashka script, consider using mise instead.

Babashka can be installed using a plugin as follows:

asdf plugin add babashka https://github.com/pitch-io/asdf-babashka
asdf install babashka latest

mise

mise is a development environment setup tool for linux and macOS.

Install:

mise use --global babashka@latest

Upgrade:

mise upgrade babashka

Windows

Scoop

On Windows you can install using scoop and the scoop-clojure bucket.

Or just follow these concrete steps:

# Note: if you get an error you might need to change the execution policy (i.e. enable Powershell) with
# Set-ExecutionPolicy RemoteSigned -scope CurrentUser
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')

scoop bucket add scoop-clojure https://github.com/littleli/scoop-clojure
scoop bucket add extras
scoop install babashka

Manual

If scoop does not work for you, then you can also just download the bb.exe binary from Github releases and place it on your path manually.

WSL1

Note: WSL1 users might experience a BSOD, please use the --static install option when installing

$ curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install
$ chmod +x install
$ ./install --static

Installer script

Install via the installer script for linux and macOS:

$ curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install
$ chmod +x install
$ ./install

By default this will install into /usr/local/bin (you may need sudo for this). To change this, provide the directory name:

$ ./install --dir .

To install a specific version, the script also supports --version:

$ ./install --dir . --version 0.4.1

To force the download of the zip archive to a different directory than /tmp use the --download-dir argument:

$ ./install --dir . --version 0.4.1 --download-dir .

On Linux, if you want to install the static binary version:

$ ./install --dir . --version 0.4.1 --download-dir . --static

In case you want to check the download, you can use the --checksum option. This maybe useful for unattended installations:

$ sha256sum babashka-0.4.1-linux-amd64-static.tar.gz
ab70fb39fdbb5206c0a2faab178ffb54dd9597991a4bc13c65df2564e8f174f6  babashka-0.4.1-linux-amd64-static.tar.g
$ ./install --dir /tmp --checksum ab70fb39fdbb5206c0a2faab178ffb54dd9597991a4bc13c65df2564e8f174f6 --static --version 0.4.1

Note that the --checksum option only works when --version option is also provided. This is to avoid breakage when a new version of Babashka is released.

Github releases

You may also download a binary from Github. For linux there is a static binary available which can be used on Alpine.

CI

  • On Github Actions it's recommended to use setup-clojure with bb: latest.
  • You can use the installer script on any non-Windows CI system. CircleCI requires sudo.
  • On Appveyor + Windows you can use a bit of Powershell.

Docker

Check out the image on Docker hub.

Check out the news page to keep track of babashka-related news items.

Go here to see the full list of built-in namespaces.

A list of projects (scripts, libraries, pods and tools) known to work with babashka.

Badges

bb compatible

The babashka compatible badge indicates that a library can be used as babashka dependency.

If this is the case for your library, we encourage you to proudly display this badge.

Markdown
[![bb compatible](https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg)](https://babashka.org)
AsciiDoc
https://babashka.org[image:https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg[bb compatible]]
HTML
<a href="https://babashka.org" rel="nofollow"><img src="https://github.com/babashka/babashka/raw/master/logo/badge.svg" alt="bb compatible" style="max-width: 100%;"></a>

bb built-in

The babashka built-in badge means that a library has been built directly into babashka and requires no extra dependencies to use it.

If this rare honor belongs to your library, you should display this badge.

Markdown
[![bb built-in](https://raw.githubusercontent.com/babashka/babashka/master/logo/built-in-badge.svg)](https://babashka.org)
AsciiDoc
https://babashka.org[image:https://raw.githubusercontent.com/babashka/babashka/master/logo/built-in-badge.svg[bb built-in]]
HTML
<a href="https://babashka.org" rel="nofollow"><img src="https://github.com/babashka/babashka/raw/master/logo/built-in-badge.svg" alt="bb built-in" style="max-width: 100%;"></a>

Swag

Pods are programs that can be used as a Clojure library by babashka. Documentation is available in the pod library repo.

A list of available pods can be found in the pod registry.

Differences with Clojure

Babashka is implemented using the Small Clojure Interpreter. This means that a snippet or script is not compiled to JVM bytecode, but executed form by form by a runtime which implements a substantial subset of Clojure. Babashka is compiled to a native binary using GraalVM. It comes with a selection of built-in namespaces and functions from Clojure and other useful libraries. The data types (numbers, strings, persistent collections) are the same. Multi-threading is supported (pmap, future).

Differences with Clojure:

  • A pre-selected set of Java classes are supported. You cannot add Java classes at runtime.

  • Interpretation comes with overhead. Therefore loops are slower than in Clojure on the JVM. In general interpretation yields slower programs than compiled programs.

  • No deftype, definterface and unboxed math.

  • defprotocol and defrecord are implemented using multimethods and regular maps. Ostensibly they work the same, but under the hood there are no Java classes that correspond to them.

  • Currently reify works only for one class at a time

  • The clojure.core.async/go macro is not (yet) supported. For compatibility it currently maps to clojure.core.async/thread. More info here.

Package babashka script as a AWS Lambda

AWS Lambda runtime doesn't support signals, therefore babashka has to disable handling of SIGINT and SIGPIPE. This can be done by setting BABASHKA_DISABLE_SIGNAL_HANDLERS to true.

Articles, podcasts and videos

Including new libraries or classes

Before new libraries or classes go into the standardly distributed babashka binary, these evaluation criteria are considered:

  • The library or class is useful for general purpose scripting.
  • Adding the library or class would make babashka more compatible with Clojure libraries relevant to scripting.
  • The library cannot be interpreted by with babashka using --classpath.
  • The functionality can't be met by shelling out to another CLI or can't be written as a small layer over an existing CLI (like babashka.curl) instead.
  • The library cannot be implemented as a pod.

If not all of the criteria are met, but adding a feature is still useful to a particular company or niche, adding it behind a feature flag is still a possibility. This is currently the case for next.jdbc and the PostgresQL and HSQLDB database drivers. Companies interested in these features can compile an instance of babashka for their internal use. Companies are also free to make forks of babashka and include their own internal libraries. If their customized babashka is interesting to share with the world, they are free to distribute it using a different binary name (like bb-sql, bb-docker, bb-yourcompany, etc.). See the feature flag documentation and the implementation of the existing feature flags (example commit).

Related projects

Contributors

Thanks to all the people that contributed to babashka:

License

Copyright © 2019-2020 Michiel Borkent

Distributed under the EPL License. See LICENSE.

This project contains code from:

  • Clojure, which is licensed under the same EPL License.

fs's People

Contributors

benjamin-asdf avatar bobisageek avatar borkdude avatar corasaurus-hex avatar duzunov avatar eamonnsullivan avatar eval avatar holyjak avatar hugoduncan avatar ikappaki avatar jjttjj avatar jlesquembre avatar kineolyan avatar kiramclean avatar lread avatar pavlosmelissinos avatar retrogradeorbit avatar sohalt avatar tekacs avatar teodorlu avatar thenonameguy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

fs's Issues

`babashka.fs/glob` crashes when encountering permission errors

On my Mac, I get the following behavior:

user=> (babashka.fs/glob "/private" "**/deps.edn")
java.lang.Exception: Visiting /private/etc/cups/certs failed [at <repl>:6:1]

I use babashka.fs/glob to find all deps.edn files in a subfolder in neil-quickadd. In my case, I'd prefer ignoring files and folders with permission errors.

https://github.com/teodorlu/neil-quickadd/blob/7bb1629670966d4e40adf458bc157f08eb2dc8f1/src/teodorlu/neil_quickadd.clj#L31

The glob folder traversal logic appears to be in babashka.fs/match:

https://github.com/babashka/fs/blob/master/src/babashka/fs.cljc#L250-L314

expand-home

Should we add an expand-home function that expands a leading ~ into (System/getProperty "user.home")?

Questions:

  • On Windows this doesn't mean anything. Should we also read ~/foo/bar as C:\Users\borkdude\foo\bar on Windows
  • Do we need to expand more than tilde?
  • Do we need to expand tilde is if's not the leading character?

Allow for more declarative use of `xdg-*-home`

(fs/xdg-config-home) is mostly never useful as-is as it's typically used to get the config-folder for a specific application: (fs/path (fs/xdg-config-home) "some-app").

How about we add a 1-arity option for the fs/xdg-*-home functions?

(fs/xdg-config-home "clj-kondo")
;; => #object[sun.nio.fs.UnixPath 0x37548587 "/Users/gert/.config/clj-kondo"]`

Add test case for attributes

 (-> (fs/set-attribute "/tmp/foo.clj" "basic:lastModifiedTime" (fs/millis->file-time 100)) (fs/read-attributes "*") :lastModifiedTime fs/file-time->millis) ;;=> 100

Explicitly support `:win-exts` on `which` function

Currently

@borkdude has a comment on :win-exts being unsupported for the which function:

fs/src/babashka/fs.cljc

Lines 780 to 787 in 5a63586

(defn which
"Locates a program in (exec-paths) similar to the which Unix command.
On Windows it tries to resolve in the order of: .com, .exe, .bat,
.cmd."
([program] (which program nil))
([program opts]
;; :win-exts is unsupported, if you read and use
;; this, let me know, it may break.

But...

I actually made use of :win-exts on Etaoin to help me locate .ps1 files.

So...

As requested in the comment, I reached out to borkdude on Slack.
To which he responded:

I'm fine with removing the comment and support :win-exts

Next Action

I'll follow up with a PR.
Only comment and docstring should change.

move-tree

Do we need this? When moving a file on the same partition, moving an entire dir should already work.
Workaround: copy-tree + delete-tree on the original.

exists? should never throw

On Windows:

user=> (fs/exists? "<")
Execution error (InvalidPathException) at sun.nio.fs.WindowsPathParser/normalize (WindowsPathParser.java:182).
Illegal char <<> at index 0: <

expand-home is not working as expected on Windows

Small example:

(require '[babashka.fs :as fs])
(fs/expand-home "~/hello-world.bat")

prints

#object[sun.nio.fs.WindowsPath 0x42897f2f "C:\\Users\\hello-world.bat"]

But in my case the string part should be like:

C:\\Users\\alesn\\hello-world.bat

This means that actual user directory is omitted from the expansion.

issue with glob matching at the last part of path

I've run into a problem when trying to use fs/glob to match a path which matches the end of the pattern. For instance
(fs/glob "/Users/derek/tmp/file1" "file1") => [] or
(fs/glob "/Users/derek/tmp/file1" "**file1") => [] both return nothing

but pointing to its parent dir works
(fs/glob "/Users/derek/tmp" "file1") => [#object[sun.nio.fs.UnixPath 0x62a73d2b "/Users/derek/tmp/file1"]]

The above is counter-intuitive to me. Also I can match in this scenario using the standard Java FileSystems matcher and get the expected behavior:
(def jfs (FileSystems/getDefault))
(def match3 (.getPathMatcher jfs "glob:**file1"))
(def f1 (io/file "/Users/derek/tmp/file1"))
(.matches match3 (fs/canonicalize f1)) => true

Maybe I'm misunderstanding something about how you intended glob to be used, but this sounds like a bug to me.

Thanks for taking a look at this

Derek

copy-tree doesn't create nested destination directories

As discussed here: https://clojurians.slack.com/archives/CLX41ASCS/p1641396190148600
Tested with bb v0.7.0 and v0.7.3

Repro steps:

➜  ~ mkdir foo
➜  ~ touch foo/bar1
➜  ~ touch foo/bar2
➜  ~ bb -e '(fs/copy-tree "foo/" "foo2/foo")'
----- Error --------------------------------------------------------------------
Type:     java.nio.file.NoSuchFileException
Message:  /Users/dimitaruzunov/foo2/foo
Location: <expr>:1:1

Expected result: to create the foo2 directory

Or to change the docstring:
Copies entire file tree from src to dest. Creates dest if needed using create-dirs, passing it the :posix-file-permissions option. Supports same options as copy.
https://babashka.org/fs/babashka.fs.html#var-copy-tree

support `java.nio.file.Files/write`

I've been wanting to use babashka.fs for writing files using nio

Might supporting those be a sensible addition? Would also require an implementation ->standard-open-option analogous to ->copy-opts.

`which` searches $PATH for relative paths (i.e. different from *nix which command)

Probably a low priority, as it's a somewhat esoteric situation.
I'm reporting this here because in my opinion fs/which is sort of the root cause, but there are several layers here, and maybe a couple of options as to where to fix (if we agree it's an issue).

presenting symptom/problem statement
When running native babashka tests in my Windows environment, I found that the tests end up using my installed babashka (on my PATH) rather than the freshly-built babashka in the current directory. I believe this has been the case since a fix for babashka/babashka#1078 went in. The native tests use babashka.process and execute "./bb", but despite the explicit relative path, $PATH is still being searched.

The desired outcome (from my standpoint) would be that calling process with a relative path (not just a command/file name) would respect the relative path, to more closely mimic the behavior of the command line. With that said, the Windows PATHEXT problem (needing the tack on the exe/com/cmd extension) would still exist in that case, so maybe there's a case for separating determining candidate directory paths and checking the candidate extensions.

babashka.process
On Windows, p/process searches $PATH if program is a relative path:
https://github.com/babashka/process/blob/8d72f2097a1dc49b81a52af6733b8beb836efbdc/src/babashka/process.cljc#L167-L168

Given a situation where the current directory contains an executable with the same name as an executable on $PATH, calling process on ./executable-name will use the one from $PATH instead of the one from the current directory:

C:\Users\user\code\clojure\babashka>.\bb --version
babashka v0.7.9-SNAPSHOT

C:\Users\user\code\clojure\babashka>bb
user=> (require '[babashka.process :as p])
user=> (-> (p/process ["./bb" "--version"] {:out :string})
         deref
         (select-keys [:out :cmd]))
{:out "babashka v0.7.8\r\n", :cmd ["C:\\Users\\user\\scoop\\shims\\.\\bb.exe" "--version"]}

... note that the process invocation uses the bb executable from $PATH. In my non-windows environment, both the shell command and the process invocation use the bb from the current directory.

fs/which
fs/which always searches $PATH, whereas the which unix util appears to only search $PATH if the argument is exactly a file name (i.e. not a path that specifies any directories)

# /home/bob/code is on my $PATH
$ echo $PATH | awk -F: '{print $NF}'
/home/bob/code

# /home/bob/code/bin/bb exists
$ ls /home/bob/code/bin/bb
/home/bob/code/bin/bb

# which doesn't find 'bin/bb' on $PATH from an arbitrary directory
$ pwd
/home/bob/code/clojure
$ which bin/bb
$

# but which does find 'bin/bb' if it's relative to the current directory
$ pwd
/home/bob/code
$ which bin/bb
bin/bb

but fs/which always looks through $PATH:

$ pwd
/home/bob/code/clojure
# this which fails at the command line
$ bb "(require '[babashka.fs :as fs]) (fs/which \"bin/bb\")"
#object[sun.nio.fs.UnixPath 0x1dff8382 "/home/bob/code/bin/bb"]

Possible resolution
If the aim would be to make fs/which behave more like unix which (while still accounting for Windows extensions), then I imagine it might work something like this:

; figure out candidate paths
(let [prog-path (path program)]
   (if (= prog-path (.getFileName prog-path)) ; if the whole path is just the file name, search $PATH, otherwise treat path as relative
       (exec-paths)
       ["."]))

... and then the existing search (with windows extensions on Windows) through the newly-determined candidate paths. One possible problem with the .getFileName approach is that it would also see a directory name as just a file name, but that seems to also be true currently.

That's just an idea - I figure logic that's getting complicated like this might warrant some design discussion before acting, so I just wanted to lay out the problem statement and get some feedback.

support for google storage file attribute metadata

Hello, I'm trying to use google cloud storage's nio interface with babashka.fs. While a lot of it is just working (:tada:), I'm having issues accessing file metadata attributes.

The direct java invocation would look like:

(Files/readAttributes 
 (Paths/get (URI/create "gs://bucket/filename"))
 com.google.cloud.storage.contrib.nio.CloudStorageFileAttributes 
 (into-array [LinkOption/NOFOLLOW_LINKS]))

but when I try to use babashka.fs/read-attributes as follows I encounter two failures

(babashka.fs/read-attributes 
 (Paths/get (URI/create "gs://bucket/filename")) 
 CloudStorageFileAttributes 
 {:nofollow-links true})

failure A

; eval (current-form): (babashka.fs/read-attributes path CloudStorageFileAttributes {:nofollow-links true})
; (err) Execution error (ClassCastException) at babashka.fs/read-attributes (fs.cljc:596).
; (err) class java.lang.Class cannot be cast to class java.lang.String (java.lang.Class and java.lang.String are in module java.base of loader 'bootstrap')

failure B

#error {
 :cause "Don't know how to create ISeq from: com.google.cloud.storage.contrib.nio.CloudStorageObjectAttributes"
 :via
 [{:type java.lang.IllegalArgumentException
   :message "Don't know how to create ISeq from: com.google.cloud.storage.contrib.nio.CloudStorageObjectAttributes"
   :at [clojure.lang.RT seqFrom "RT.java" 557]}]
 :trace
 [[clojure.lang.RT seqFrom "RT.java" 557]
  [clojure.lang.RT seq "RT.java" 537]
  [clojure.core$seq__5467 invokeStatic "core.clj" 139]
  [clojure.core.protocols$seq_reduce invokeStatic "protocols.clj" 24]
  [clojure.core.protocols$fn__8226 invokeStatic "protocols.clj" 75]
  [clojure.core.protocols$fn__8226 invoke "protocols.clj" 75]
  [clojure.core.protocols$fn__8178$G__8173__8191 invoke "protocols.clj" 13]
  [clojure.core$reduce invokeStatic "core.clj" 6886]
  [clojure.core$into invokeStatic "core.clj" 6958]
  [clojure.core$into invoke "core.clj" 6950]
  [babashka.fs$read_attributes invokeStatic "NO_SOURCE_FILE" 585]
  [babashka.fs$read_attributes invoke "NO_SOURCE_FILE" 577]
...
}

For quick context, I marked the failure points:

(ns babashka.fs)

(defn read-attributes
  "Reads attributes via Files/readAttributes. Keywordizes string keys into keywords. This can be changed by passing a :key-fn in the options map."
  ([path attributes]
   (read-attributes path attributes nil))
  ([path ^String attributes {:keys [:nofollow-links :key-fn]}] ;; failure A due to ^String annotation
   (->>  (Files/readAttributes (as-path path)
                               attributes
                               (->link-opts {:nofollow-links nofollow-links}))
         (into {}) ;; failure B
         (keyize (or key-fn keyword)))))

Thanks!

glob: not recursive by default?

It seems a lot of environments do not have recursive globbing by default. We should revisit this.

borkdude@MBP2019 ~/Dropbox/dev/clojure/babashka (master) $ ls **/*.md
CHANGELOG.md                                   babashka.nrepl/CHANGELOG.md                    depstar/README.md                              doc/news.md                                    pods/examples/pod-lispyclouds-sqlite/README.md
CONTRIBUTING.md                                babashka.nrepl/README.md                       doc/build.md                                   doc/projects.md                                process/README.md
README.md                                      babashka.nrepl/doc/intro.md                    doc/dev.md                                     doc/surveys/2020-11.md                         sci/CHANGELOG.md
babashka.curl/CHANGES.md                       deps.clj/README.md                             doc/examples.md                                examples/README.md                             sci/README.md
babashka.curl/README.md                        depstar/CHANGELOG.md                           doc/libraries.md                               pods/README.md                                 sci/doc/libsci.md
borkdude@MBP2019 ~/Dropbox/dev/clojure/babashka (master) $ bash
bash-3.2$ ls **/*.md
babashka.curl/CHANGES.md	babashka.nrepl/README.md	depstar/README.md		doc/examples.md			doc/projects.md			process/README.md
babashka.curl/README.md		deps.clj/README.md		doc/build.md			doc/libraries.md		examples/README.md		sci/CHANGELOG.md
babashka.nrepl/CHANGELOG.md	depstar/CHANGELOG.md		doc/dev.md			doc/news.md			pods/README.md			sci/README.md

Ideas for more functions

I came across f.el and some of these functions look really useful for when you're messing around with the filesystem.

Consider allowing non-relative paths for fs/which

Background

On Windows babashka.fs/which will automatically find foo.exe (and other :win-exts) for foo.

This is very helpful. We often don't really care about the Windows executable extension.

Problem

which currently throws on non-relative paths.

For example, let's say I want to find the executable for java on Windows.
Instead of finding the java executable on the PATH, I want to use JAVA_HOME.

From a Windows 10 in PowerShell:

PS C:\Users\lee> clj --version
Clojure CLI version (deps.clj) 1.11.1.1252
PS C:\Users\lee> clj -Sdeps "{:deps {babashka/fs {:mvn/version \`"0.3.17\`"}}}"
Clojure 1.11.1
user=> (require '[babashka.fs :as fs])
nil

Finding java on the path works great:

user=> (fs/which "java")
#object[sun.nio.fs.WindowsPath 0x3e3861d7 "C:\\Users\\lee\\scoop\\apps\\graalvm22-jdk17\\current\\bin\\java.exe"]

But maybe we want to find it via JAVA_HOME.
First, let's show that java.exe exists through JAVA_HOME:

user=> (fs/exists? (fs/file (System/getenv "JAVA_HOME") "bin" "java.exe"))
true

Ok, now let's try to find java.exe through JAVA_HOME using which:

user=> (fs/which (fs/file (System/getenv "JAVA_HOME") "bin" "java"))
Execution error (IllegalArgumentException) at babashka.fs/path (fs.cljc:57).
C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin\java.com is not a relative path

This does not work because which does not currently work on non-relative paths.

But if I contrive a relative path I can get which to find the exe:

user=> (System/getProperty "user.dir")
"C:\\Users\\lee"
user=> (fs/which (fs/file (fs/relativize (System/getProperty "user.dir") (System/getenv "JAVA_HOME")) "bin" "java"))
#object[sun.nio.fs.WindowsPath 0x61af1510 "scoop\\apps\\graalvm22-jdk17\\current\\bin\\java.exe"]

Proposal

On Windows, which could resolve the executable extension even for non-relative paths.

Additional Idea

Perhaps which should allow non-relative paths in general?
If the passed in program already resolves to an executable, which could return it.

Given a foobar.bat in the current directory, we see this behaviour when looking up programs that specify a relative path:

user=> (fs/which "./foobar")
#object[sun.nio.fs.WindowsPath 0x11b455e5 ".\\foobar.bat"]
user=> (fs/which "./foobar.bat")
#object[sun.nio.fs.WindowsPath 0x6d025d1d ".\\foobar.bat"]

(Tangent: I wonder though, should we be returning the absolute or canonical path here?)

Operating on absolute paths seems to match the which zsh/bash behaviour on macOS/Linux:

$ which java
/home/lee/.sdkman/candidates/java/current/bin/java
❯ which /home/lee/.sdkman/candidates/java/current/bin/java
/home/lee/.sdkman/candidates/java/current/bin/java

And the get-command behaviour on Windows PowerShell

PS C:\Users\lee> get-command java

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     java.exe                                           17.0.7.0   C:\Users\lee\scoop\apps\graalvm22-jdk1...


PS C:\Users\lee> get-command C:\\Users\\lee\\scoop\\apps\\graalvm22-jdk17\\current\\bin\\java.exe

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     java.exe                                           17.0.7.0   C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin\java.exe

And I guess kinda sorta the where behaviour Windows CMD shell:

C:\Users\lee>where java
C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin\java.exe
C:\Users\lee\scoop\apps\temurin19-jdk\current\bin\java.exe
C:\Users\lee\scoop\apps\temurin8-jdk\current\bin\java.exe
C:\Users\lee\scoop\apps\temurin11-jdk\current\bin\java.exe

C:\Users\lee>where "C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin:java.exe"
C:\Users\lee\scoop\apps\graalvm22-jdk17\current\bin\java.exe

Next Steps

Lemme know if I've missed some alternatives/nuances,etc that you see.

If this idea makes sense to you, happy to help out with a PR.

Consider adding cljdoc config

Because it has cljc files, cljdoc assumes that babashka/fs is a Clojure and ClojureScript project. Explicitly telling cljdoc that this is a Clojure only project will allow docs to build successfully on cljdoc.

I you are ok with adding this config, I am very happy to follow up with a PR.

Add dir-name

dirname(1) is a common command in shell scripts. It would be convenient to have its direct equivalent in babashka.fs. It would be also nice to have something that functioned like corasaurus_hex.fs/split-path. I don't think they are redundant, since a dirname function would handle a path with no parent directory specially (returning "."). Joker, for example, has joker.filepath/dir as well as split and split-list.

Add common XDG-paths

The XDG spec defines various base paths to look for config/data/cache files.
Having written some scripts with this logic, I thought it might be convenient to have this in babashka/fs.

Looking at what paths are commonly used, it seems adding the following fns would suffice:

  • xdg-config-home
  • xdg-cache-home
  • xdg-data-home
  • xdg-state-home

.zip functionality

Hi,

I wonder if you've considered adding .zip/.jar functionality to this lib. I would be able to contribute PRs.

Some needs/features that come to mind:

  • zip-seq, akin to file-seq
  • uncompressing a file into the filesystem
  • uncompressing a file into memory

WDYT?

Cheers - V

clarify docs for create-dir and create-dirs

javahippie For create-dir and create-dirs, the docs just reference the Files docs, maybe it’s helpful to point out, what the difference there is directly in the docs? In some API, this behavior is controlled with a flag, so it might not be clear to some users (edited)

<1m
borkdude 👍 I'll do that

split-ext/extension fail on files with trailing space on windows

It seems that this works on linux but on windows fails.

(spit "test .txt" "hi")
(fs/extension "test .txt")

The exception thrown is

InvalidPathException Trailing char < > at index 4: test 
	sun.nio.fs.WindowsPathParser.normalize (WindowsPathParser.java:191)
	sun.nio.fs.WindowsPathParser.parse (WindowsPathParser.java:153)
	sun.nio.fs.WindowsPathParser.parse (WindowsPathParser.java:77)
	sun.nio.fs.WindowsPath.parse (WindowsPath.java:92)
	sun.nio.fs.WindowsFileSystem.getPath (WindowsFileSystem.java:232)
	java.io.File.toPath (File.java:2396)
	babashka.fs/as-path (fs.cljc:39)
	babashka.fs/as-path (fs.cljc:34)
	babashka.fs/path (fs.cljc:53)
	babashka.fs/path (fs.cljc:47)
	babashka.fs/split-ext (fs.cljc:728)
	babashka.fs/split-ext (fs.cljc:720)

This is due to the call to path on something with a trailing space, ie "test ", here, this is an invalid path in windows, so it makes sense that calling path on that should throw on windows. "test .txt" as a path is valid on windows or at least it works with the rest of api.

I'm not quite sure what the correct behavior here for extension should be though. Figured I would report it and see what you think.

read-all-lines fails when encoding not UTF-8

The function read-all-lines fails when the encoding is not UTF-8

With the file attached, iso-8859.txt, the function produces this error:

(fs/read-all-lines (fs/file "iso-8859.txt"))
: Input length = 1 user

If you enclose in a try-catch, the exception is java.nio.charset.MalformedInputException

(tested with babashka v0.8.2)

Ideally, the function should provide an encoding optional param to deal with these cases.

Handle google storage uri strings without need for additional wrapping

babashka.fs works well with google storage paths with a little bit of additional treatment

(require '[babashka.fs])
(import [java.nio.file Paths] [java.net URI] [java.nio.file.spi FileSystemProvider])

;; quickly check that `gs` is registered correctly
(->> (FileSystemProvider/installedProviders)
     (map (fn [p] (.getScheme p)))
     (into #{})
     println)
;; => #{"jrt" "gs" "jar" "file"}

(def uri-str "gs://bucket/filename")
(def uri (URI/create uri-str))
(def path (Paths/get uri))
(babashka.fs/exists? path) ;=> true

but I'm wondering if it would be possible to have the following two cases also work, where the argument is a URI or a string representing a uri:

(babashka.fs/exists? uri) ;=> currently fails with "unknown protocol: gs"
(babashka.fs/exists? uri-str) ;=> currently results in `false`

we spoke offline and you tracked the issue down to

user=> (babashka.fs/path uri)
Execution error (MalformedURLException) at java.net.URL/<init> (URL.java:652).
unknown protocol: gs
;; then
user=> (clojure.java.io/as-url uri)
Execution error (MalformedURLException) at java.net.URL/<init> (URL.java:652).
unknown protocol: gs
;; and lastly
user=> (.toURL uri)
Execution error (MalformedURLException) at java.net.URL/<init> (URL.java:652).
unknown protocol: gs

your work in 47f5642 helps the following work

(babashka.fs/exists? uri) ;=> true

but this still fails:

(babashka.fs/exists? uri-str) ;=> false

Feature request: Extension utils

This seems to most up-to-date JVM Clojure filesystem library, but I noticed there are couple of differences from some of the other ones. I was wondering what you think of adding extension utils to this library, like split-ext or ext, like ones from e.g. the clj-commons lib.

They're really simple so maybe it doesn't make sense to include here. Or maybe you want to stick to mirroring the java file APIs. Anyway, I can see why they might not belong here, but if this is something you'd consider I'd be happy to help implement it.

Thanks for yet another useful library!

Add fs/zip

We can look at the tools build implementation for this as an example.

Wrap fs/create-dir with fs/exists?

Hey, I really like the babshka.fs namespace, thanks for creating.

In my scripts I tend to wrap create-dir like this

(defn create-dir [path]
  (when-not (fs/exists? path)
    (fs/create-dir path)))

Just wanted to check if you think this would be a useful addition to the babshka.fs namespace?

`expand-home` is wrong on windows when passed a string with a forward slash

(fs/expand-home "~/my-file") ;; "C:\\Users\\my-file" incorrect, missing username directory when using forward slash in the argument
(fs/expand-home "~\\my-file") ;; "C:\\Users\\justin\\my-file" correct
(fs/expand-home (fs/path "~/my-file")) ;; "C:\\Users\\justin\\my-file" correct

This is because the current expand-home implementation just looks for the index of the path separator in the input. Calling as-path on the input within expand-home should fix this.

I will provide a PR for this today or tomorrow

Get the filename without an extension/suffix

Linux includes a handy function called basename which lets you get a file name from a path and optionally remove a suffix at the same time. Is this something babashka.fs should include?

I have already opened #28 for this but it should be considered a WIP when/if it's decided what should be included.

Some possible options:

  1. Include it as part of the file-name function just as was done in raynes.fs.
  2. Add a new function for doing just the removal part as the PR does.
  3. Add a function that strips the extension or a suffix from a path and returns the entire path, not just the file-name.
  4. Not include this at all?

Looking for input and thoughts from the community, thanks!!

Async-capabilities for fs/glob and fs/match

It would be useful if the library provided async-capabilities for finding files, such as a callback-based API for fs/glob and fs/match. That way we could traverse large file-trees in the background without blocking the main thread.

create-temp-file

I find myself needing temp-file just to create a temporary file somewhere.

Clarify posix-file-permissions option

heow Today at 3:42 PM
anyone know the permissions format for (fs/create-dir "foo") ? The docs just reference Files which is a bit opaque

3 replies
borkdude:clj-kondo: 3 minutes ago
The docs could be improved here. In another function I see:
The :posix-file-permissions option is a string like "rwx------"
👍
1

borkdude:clj-kondo: 1 minute ago
Please post an issue about this.
borkdude:clj-kondo: < 1 minute ago
Or I will do this myself now, actually ;).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.