Skip to content

Commit

Permalink
Feature: semver find (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
chriskilding authored May 30, 2019
1 parent 44ed274 commit 060cbba
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 16 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Semantic Versioning utility.

## Overview

The `semver` command line utility generates, modifies, parses, sorts, and validates [Semantic Version](https://semver.org/) strings.
The `semver` command line utility finds, generates, modifies, parses, sorts, and validates [Semantic Version](https://semver.org/) strings.

The Semantic Versioning format is:

Expand Down Expand Up @@ -37,6 +37,7 @@ Coming soon.
## Usage

```bash
semver find <path> [expression]
semver grep [-coq] -
semver printf <format> <version>
semver sort [-r] -
Expand All @@ -51,7 +52,7 @@ man semver

## Examples

Find the latest Git tag:
See the latest Git tag:

```bash
git tag | semver grep -o | semver sort -r | head -n 1
Expand Down Expand Up @@ -85,3 +86,9 @@ semver printf '%major %minor %patch' '1.2.3-alpha+1' | awk '{ print ++$1 "." 0 "
semver printf '%major %minor %patch' '1.2.3-alpha+1' | awk '{ print $1 "." ++$2 "." 0 }' # => 1.3.0
semver printf '%major %minor %patch' '1.2.3-alpha+1' | awk '{ print $1 "." $2 "." ++$3 }' # => 1.2.4
```

Find filenames containing Semantic Versions inside a directory:

```bash
semver find . -type f
```
70 changes: 63 additions & 7 deletions semver
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ use warnings;
use feature qw(say switch);
use Scalar::Util qw(looks_like_number);
use List::Util qw(max);
use File::Find;
use Getopt::Long qw(GetOptionsFromArray);
no if $] >= 5.018, warnings => qw(experimental::smartmatch);

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name = *File::Find::name;
*dir = *File::Find::dir;
*prune = *File::Find::prune;

# Regex from semver.org: https://github.com/semver/semver/pull/460
my $semver_regex = '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$';
my $semver_regex = '(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?';
my $semver_precedence_regex_head = '^([0-9a-zA-Z-]*)';
my $semver_precedence_regex_tail = '\.(0|[1-9a-zA-Z-][0-9a-zA-Z-]*)';

Expand All @@ -23,6 +30,7 @@ sub usage() {
say STDERR "Semantic Versioning utility.";
say STDERR "";
say STDERR "Usage:";
say STDERR " $program find <path> [expression]";
say STDERR " $program grep [-coq] -";
say STDERR " $program printf <format> <version>";
say STDERR " $program sort [-r] -";
Expand All @@ -37,7 +45,7 @@ sub usage() {
sub semver_get {
my ($str) = @_;

my @matches = $str =~ $semver_regex;
my @matches = $str =~ /^$semver_regex$/;

if ((defined $matches[0]) && (defined $matches[1]) && (defined $matches[2])) {
return @matches;
Expand Down Expand Up @@ -183,12 +191,44 @@ sub semver::printf {
printf $format;
}

sub semver::find {
my ($dir, $depth, $type) = @_;

*wanted = sub {
my ($dev, $ino, $mode);

my $is_match = /$semver_regex/s && (($dev, $ino, $mode) = lstat($_));

if ($type) {
if ($type eq "d") {
$is_match = $is_match && -d _;
} elsif ($type eq "f") {
$is_match = $is_match && -f _;
} elsif ($type eq "l") {
$is_match = $is_match && -l _;
} elsif ($type eq "p") {
$is_match = $is_match && -p _;
}
}

if ($is_match) {
say "$name";
}
};

if ($depth) {
finddepth({ wanted => \&wanted }, $dir);
} else {
find({ wanted => \&wanted }, $dir);
}
}

sub semver::grep_count {
my $num_matches = 0;

while (my $line = <STDIN>) {
my @words = split(/\s+/, $line);
if (grep(/$semver_regex/, @words)) {
if (grep(/^$semver_regex$/, @words)) {
$num_matches++;
}
}
Expand All @@ -203,7 +243,7 @@ sub semver::grep_count {
sub semver::grep_quiet {
while (my $line = <STDIN>) {
my @words = split(/\s+/, $line);
if (grep(/$semver_regex/, @words)) {
if (grep(/^$semver_regex$/, @words)) {
exit 0;
}
}
Expand All @@ -220,7 +260,7 @@ sub semver::grep {
my @words = split(/\s+/, $line);
if ($only_matching) {
foreach (@words) {
if (/$semver_regex/) {
if (/^$semver_regex$/) {
$num_matches++;
print "$1.$2.$3";
print "-$4" if (length $4 // '');
Expand All @@ -229,7 +269,7 @@ sub semver::grep {
}
}
} else {
if (grep(/$semver_regex/, @words)) {
if (grep(/^$semver_regex$/, @words)) {
$num_matches++;
print $line;
}
Expand All @@ -249,7 +289,7 @@ sub semver::sort(&@) {
chomp(@lines);

# Extra validation if just 1 element, because Perl does not sort unless there are at least 2 elements.
if ((scalar(@lines) eq 1) && ($lines[0] !~ /$semver_regex/)) {
if ((scalar(@lines) eq 1) && ($lines[0] !~ /^$semver_regex$/)) {
exit 1;
}

Expand Down Expand Up @@ -278,6 +318,22 @@ sub main {
my $subcommand = shift @args // '';

for ($subcommand) {
when (/^find$/) {
my $depth, my $type, my $help;

GetOptionsFromArray(
\@args,
"depth" => \$depth,
"type=s" => \$type,
"h" => \$help,
) or exit 1;

usage() if $help or (scalar(@args) < 1);

my $dir = $args[0];

semver::find($dir, $depth, $type);
}
when (/^grep$/) {
my $count, my $only_matching, my $quiet, my $help;

Expand Down
77 changes: 70 additions & 7 deletions semver.1
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
.Nd Semantic Versioning utility
.Sh SYNOPSIS
.Nm
find
.Ar path
.Op Ar expression
.Nm
grep
.Op Fl coq
.Fl
Expand All @@ -22,7 +26,55 @@ sort
.Sh DESCRIPTION
The
.Nm
utility generates, modifies, parses, sorts, and validates Semantic Version strings.
utility finds, generates, modifies, parses, sorts, and validates Semantic Version strings.
.Ss find
The
.Nm
utility can recursively descend the directory hierarchy specified by
.Ar path ,
select any paths where (a) the basename (file name) contains one or more Semantic Version strings and (b) the file matches the optional boolean
.Op Ar expression ,
and print them to the standard output, in the style of
.Xr find 1
with
.Ic -name .
.Pp
The
.Op Ar expression
is made of one or more operands as described below.
.Pp
Operands:
.Bl -tag -width indent -offset indent
.It Ic -depth
Causes descent of the directory hierarchy to be done so that all entries in a directory are acted on BEFORE the directory itself. (If a
.Ic -depth
primary is not specified, all entries in a directory shall be acted on AFTER the directory itself.)
.It Ic -type Ar t
True if the file is of the specified type. Possible file types are as follows:
.Pp
.Bl -tag -width indent -compact
.It Cm d
directory
.It Cm f
regular file
.It Cm l
symbolic link
.It Cm p
FIFO pipe
.El
.El
.Pp
The primaries are combined with an implicit conjunction; the AND operator is implied by the juxtaposition of two primaries. Other operators from
.Xr find 1
including negations are not supported. The primaries shall evaluate their respective arguments only once.
.Pp
Exit status:
.Bl -tag -width Ds -offset indent -compact
.It 0
Success.
.It >0
An error occurred, or an invalid option or operand was specified.
.El
.Ss grep
The
.Nm
Expand Down Expand Up @@ -62,8 +114,8 @@ Exit status:
.Bl -tag -width Ds -offset indent -compact
.It 0
One or more lines were selected (i.e. there was at least one valid Semantic Version).
.It 1
No lines were selected (i.e. there were no valid Semantic Versions), or an invalid option was specified.
.It >0
No lines were selected (i.e. there were no valid Semantic Versions), or an invalid option was specified, or an error occurred.
.El
.Ss printf
The
Expand Down Expand Up @@ -146,12 +198,12 @@ Exit status:
.Bl -tag -width Ds -offset indent -compact
.It 0
Success.
.It 1
.It >0
The
.Ar format
string contained invalid specifiers, or
.Ar version
was invalid.
was invalid, or an error occurred.
.El
.Ss sort
The
Expand All @@ -173,8 +225,8 @@ Exit status:
.Bl -tag -width Ds -offset indent -compact
.It 0
Success.
.It 1
An invalid option was specified, or the input was invalid (i.e. it contained something besides Semantic Versions and line delimiter characters).
.It >0
An invalid option was specified, or the input was invalid (i.e. it contained something besides Semantic Versions and line delimiter characters), or an error occurred.
.El
.Sh OPTIONS
.Pp
Expand All @@ -186,6 +238,16 @@ utility understands the following command-line options:
Display the usage screen.
.El
.Sh EXAMPLES
.Ss Find
.Pp
Find only regular file names containing Semantic Version strings:
.Pp
.Bd -literal -offset indent -compact
$ semver find . -type f
foo-1.2.3
bar-4.5.6
7.8.9
.Ed
.Ss Grep
Given a line-separated text stream:
.Bd -literal -offset indent
Expand Down Expand Up @@ -262,6 +324,7 @@ The Semantic Versioning standard does not define an ordering for two versions th
.Nm
utility applies an additional natural sort on top of the Semantic Version precedence sort. This additional sort is IMPLEMENTATION-SPECIFIC and SUBJECT TO CHANGE between releases, so its algorithm is deliberately left undocumented. You should not rely on it.
.Sh SEE ALSO
.Xr find 1 ,
.Xr grep 1 ,
.Xr printf 1 ,
.Xr sort 1
Expand Down
Loading

0 comments on commit 060cbba

Please sign in to comment.