Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 2.1

jobs:
build:
docker:
- image: cimg/elixir:1.18.2
environment:
MIX_ENV: test
steps:
- checkout
- run:
name: Install dependencies
command: mix deps.get
- run:
name: Run tests
command: mix test
36 changes: 36 additions & 0 deletions .github/workflows/autobuild.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Autobuild CI

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:
name: Build and test
runs-on: ubuntu-latest
env:
MIX_ENV: test

steps:
- uses: actions/checkout@v4

- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: '1.18.2'
otp-version: '27.2'

- name: Restore dependencies cache
uses: actions/cache@v4
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-

- name: Install dependencies
run: mix deps.get

- name: Run tests
run: mix test
10 changes: 5 additions & 5 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Elixir
uses: actions/setup-elixir@v1
uses: erlef/setup-beam@v1
with:
elixir-version: '1.10.3' # Define the elixir version [required]
otp-version: '22.3' # Define the OTP version [required]
elixir-version: '1.18.2' # Define the elixir version [required]
otp-version: '27.2' # Define the OTP version [required]
- name: Restore dependencies cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
Expand Down
4 changes: 2 additions & 2 deletions .tools-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
erlang 18.0
elixir 1.3.4
erlang 27.2
elixir 1.18.2
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ language: elixir
env:
- MIX_ENV=test
elixir:
- 1.5.0
- 1.18.2
otp_release:
- 17.5
- 18.0
- 27.2
after_script:
- mix deps.get --only docs
- MIX_ENV=docs mix inch.report
Expand Down
68 changes: 68 additions & 0 deletions EXPLANATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Radpath Repository Explanation

Radpath is an Elixir library designed for path manipulation, largely inspired by Python's `pathlib`. It provides a more idiomatic and convenient way to handle file system paths in Elixir compared to the standard `Path` and `File` modules alone.

## Project Structure

The codebase is organized into a main facade module and several specialized submodules:

* **`Radpath` (`lib/radpath.ex`)**: The main entry point that aggregates functionalities from submodules and provides additional utilities like zipping, hashing, and symlink management.
* **`Radpath.Dirs` (`lib/Radpath/directory.ex`)**: Focuses on directory-related operations, such as listing directories with regex filtering.
* **`Radpath.Files` (`lib/Radpath/files.ex`)**: Handles file-specific operations, including listing files with extension-based filtering.
* **`Radpath.Tempfs` (`lib/Radpath/tempfs.ex`)**: Provides utilities for creating temporary files and directories.

## Key Features and Functionalities

### 1. File and Directory Listing
* `Radpath.files(path, ext \\ nil)`: Lists files in a given path, optionally filtered by extension.
* `Radpath.dirs(path, regex_dir \\ ".+")`: Lists directories in a given path, optionally filtered by a regex pattern.

### 2. Archiving (Zip/Unzip)
* `Radpath.zip(paths, archive_name)`: Creates a zip archive from a list of paths or a single path.
* `Radpath.unzip(zip_file, unzip_dir \\ File.cwd!)`: Extracts a zip archive to a specified directory.

### 3. File Hashing
* `Radpath.md5sum(path)`: Calculates the MD5 hash of a file.
* `Radpath.sha1sum(path)`: Calculates the SHA1 hash of a file.

### 4. Path Utilities
* `Radpath.relative_path(base, file)`: Returns the relative path of a file with respect to a base directory.
* `Radpath.parent_path(path)`: Retrieves the parent directory of a given path.
* `Radpath.ensure(path, is_file \\ false)`: Ensures a directory or file exists (creates it if it doesn't).
* `Radpath.erusne(path)`: The opposite of `ensure`; removes a file or directory if it exists.

### 5. Symlinks
* `Radpath.symlink(source, destination)`: Creates a symbolic link.
* `Radpath.islink?(path)`: Checks if a given path is a symbolic link.

### 6. Temporary Filesystem
* `Radpath.mktempfile(ext \\ ".tmp", path)`: Creates a temporary file.
* `Radpath.mktempdir(path \\ "/tmp")`: Creates a temporary directory.

## Usage Examples

Based on the project's tests and documentation:

```elixir
# List all PDF files in a directory
Radpath.files("/path/to/docs", "pdf")

# Create a zip archive
Radpath.zip(["dir1", "file1.txt"], "my_archive.zip")

# Get MD5 checksum of a file
hash = Radpath.md5sum("mix.exs")

# Ensure a directory exists
Radpath.ensure("/tmp/my_new_dir")

# Create a temporary log file
{status, fd, filepath} = Radpath.mktempfile(".log", "/tmp")
```

## Development and Testing

* **Build Tool**: Mix
* **Dependencies**: Includes `ex_doc`, `finder`, `erlware_commons`, `pattern_tap`, and `temp`.
* **Testing**: The project uses `ExUnit`. Tests can be run via `make test` or `mix test` (requires Elixir and Erlang to be installed).
* **CI/CD**: The project uses GitHub Actions for automated building and testing (see `.github/workflows/autobuild.yml`).
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ VENDORED_ELIXIR=${PWD}/vendor/elixir/bin/elixir
VENDORED_MIX=${PWD}/vendor/elixir/bin/mix
RUN_VENDORED_MIX=${VENDORED_ELIXIR} ${VENDORED_MIX}
#VERSION := $(strip $(shell cat VERSION))
STABLE_ELIXIR_VERSION = 1.1.1
STABLE_ELIXIR_VERSION = 1.18.2

.PHONY: all test

Expand Down
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
# Radpath

[![Build Status](https://travis-ci.org/lowks/Radpath.png?branch=master)](https://travis-ci.org/lowks/Radpath)
[![Build Status](https://drone.io/github.com/lowks/Radpath/status.png)](https://drone.io/github.com/lowks/Radpath/latest)
[![wercker status](https://app.wercker.com/status/10f2bf7288af1be5c4e39f25367bb3b7/s/master "wercker status")](https://app.wercker.com/project/byKey/10f2bf7288af1be5c4e39f25367bb3b7)
[![Circle CI](https://circleci.com/gh/lowks/Radpath/tree/master.png?style=badge)](https://circleci.com/gh/lowks/Radpath/tree/master)
[![CI](https://github.com/lowks/Radpath/actions/workflows/autobuild.yml/badge.svg)](https://github.com/lowks/Radpath/actions/workflows/autobuild.yml)
[![Inline docs](http://inch-ci.org/github/lowks/Radpath.svg?branch=master&style=flat)](http://inch-ci.org/github/lowks/Radpath)
[![Build Status](https://snap-ci.com/lowks/Radpath/branch/master/build_image)](https://snap-ci.com/lowks/Radpath/branch/master/build_image)
[![Coverage Status](https://coveralls.io/repos/lowks/Radpath/badge.png?branch=master)](https://coveralls.io/r/lowks/Radpath?branch=master)

A library for dealing with paths in Elixir largely inspired by Python's pathlib.

Expand Down
36 changes: 0 additions & 36 deletions circle.yml

This file was deleted.

14 changes: 7 additions & 7 deletions lib/Radpath/directory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ defmodule Radpath.Dirs do
iex(3)> Radpath.dirs(["/home/lowks/src/elixir/radpath/lib", "/home/lowks/src/elixir/radpath/_build"], regex_dir)

"""
@spec dirs(bitstring, bitstring) :: list
@spec dirs(bitstring | list, bitstring) :: list
def dirs(path, regex_dir \\ ".+") when (is_bitstring(path) or is_list(path)) do
path
|> normalize_path
|> Radpath.Util.normalize_path()
|> do_dirs([], regex_dir)
end
defp do_dirs([], result, regex_dir) do
defp do_dirs([], result, _regex_dir) do
result
end
defp do_dirs(paths ,result, regex_dir) do
Expand All @@ -40,10 +40,10 @@ defmodule Radpath.Dirs do
end

defp dirs_list(path, regex_dir) when is_bitstring(path) do
Finder.new()
|> Finder.with_directory_regex(Regex.compile!(regex_dir))
|> Finder.only_directories()
|> Finder.find(Path.expand(path))
Radpath.Finder.new()
|> Radpath.Finder.with_directory_regex(Regex.compile!(regex_dir))
|> Radpath.Finder.only_directories()
|> Radpath.Finder.find(Path.expand(path))
|> Enum.to_list
|> Enum.sort
end
Expand Down
59 changes: 13 additions & 46 deletions lib/Radpath/files.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,13 @@ defmodule Radpath.Files do

"""

@spec files(bitstring, bitstring) :: list
def files(path, ext) when is_bitstring(path) and is_bitstring(ext) do
file_ext = case String.valid? ext do
true -> [ext]
false -> ext
end
expanded_path = Path.expand(path)
case File.exists? expanded_path do
true -> Finder.new()
|> Finder.only_files()
|> Finder.with_file_endings(file_ext)
|> Finder.find(expanded_path)
|> Enum.to_list
false -> []
end
@spec files(bitstring | list, bitstring | list) :: list
def files(path, ext) when (is_bitstring(path) or is_list(path)) and (is_bitstring(ext) or is_list(ext)) do
file_ext = if is_bitstring(ext), do: [ext], else: ext

path
|> Radpath.Util.normalize_path()
|> do_ext_files([], file_ext)
end

@doc """
Expand All @@ -77,37 +69,12 @@ Listing down all files in the "ci" folder without filtering:

"""

def files(path) when is_bitstring(path) do
expanded_path = Path.expand(path)
case File.exists? expanded_path do
true -> Finder.new()
|> Finder.only_files()
|> Finder.find(expanded_path)
|> Enum.to_list
false -> []
end
end

# def files(path, file_ext) when is_bitstring(path) and is_list(file_ext) do
# file_ext = normalize_path(file_ext)
# path
# |> normalize_path
# |> do_ext_files([], file_ext)
# end

def files(path) when is_list(path) do
def files(path) when is_bitstring(path) or is_list(path) do
path
|> normalize_path
|> Radpath.Util.normalize_path()
|> do_files([])
end

def files(path, file_ext) when is_bitstring(path) or is_list(path) and is_list(file_ext) do
file_ext = normalize_path(file_ext)
path
|> normalize_path
|> do_ext_files([], file_ext)
end

# def files(path, file_ext) when is_list(path) and is_list(file_ext) do
# file_ext = normalize_path(file_ext)
# path
Expand Down Expand Up @@ -136,10 +103,10 @@ Listing down all files in the "ci" folder without filtering:
defp ext_file_list(path, file_ext \\ []) do
expanded_path = Path.expand(path)
case File.exists? expanded_path do
true -> Finder.new()
|> Finder.only_files()
|> Finder.with_file_endings(file_ext)
|> Finder.find(expanded_path)
true -> Radpath.Finder.new()
|> Radpath.Finder.only_files()
|> Radpath.Finder.with_file_endings(file_ext)
|> Radpath.Finder.find(expanded_path)
|> Enum.to_list
false -> []
end
Expand Down
42 changes: 42 additions & 0 deletions lib/Radpath/finder.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule Radpath.Finder do
defstruct only_files: false, only_directories: false, file_endings: [], directory_regex: nil

def new(), do: %Radpath.Finder{}

def only_files(finder), do: %{finder | only_files: true}
def only_directories(finder), do: %{finder | only_directories: true}
def with_file_endings(finder, endings), do: %{finder | file_endings: endings}
def with_directory_regex(finder, regex), do: %{finder | directory_regex: regex}

def find(finder, path) do
# Finder.find usually returns an Enumerable.
# We will use Stream to be safe if there are many files.

normalized_endings = Enum.map(finder.file_endings, &String.trim_leading(&1, "."))

# We'll use File.ls and recursion to mimic a deep find.
find_recursive(path)
|> Stream.filter(fn p ->
cond do
finder.only_files and not File.regular?(p) -> false
finder.only_directories and not File.dir?(p) -> false
finder.file_endings != [] and not (String.trim_leading(Path.extname(p), ".") in normalized_endings) -> false
finder.directory_regex and not (Path.basename(p) =~ finder.directory_regex) -> false
true -> true
end
end)
end

defp find_recursive(path) do
case File.ls(path) do
{:ok, files} ->
files
|> Stream.flat_map(fn file ->
full_path = Path.join(path, file)
[full_path | find_recursive(full_path)]
end)
_ ->
[]
end
end
end
Loading
Loading