Improved (after some years)

This commit is contained in:
Mattia Mascarello 2024-07-07 15:40:42 +02:00
parent 58ff0b92a7
commit a01f7a2379
4 changed files with 326 additions and 170 deletions

View File

@ -1,7 +1,20 @@
CC = cc
CFLAGS = -Wall -Wextra -Werror -pedantic -std=c99
UNAME := $(shell uname)
all: all:
echo "Compiling" @echo "Compiling"
gcc -o ropipe ropipe.c $(CC) $(CFLAGS) -o ropipe ropipe.c
@echo "Done"
install: install:
ifneq ($(UNAME), Linux)
@echo "Automatic installation is only supported on Linux"
@echo "Please copy the binary to a directory in your PATH"
@echo "and the manpage to your manpages directory"
@exit
endif
ifeq (,$(wildcard ./ropipe)) ifeq (,$(wildcard ./ropipe))
make make
endif endif
@ -9,20 +22,26 @@ ifneq ($(shell id -u), 0)
@echo "You must be root to install" @echo "You must be root to install"
else else
mkdir -p /usr/local/share/man/man1/ mkdir -p /usr/local/share/man/man1/
echo "Copying man file" @echo "Copying man file"
cp ropipe.1 /usr/local/share/man/man1/ropipe.1 cp ropipe.1 /usr/local/share/man/man1/ropipe.1
echo "Installing binary" @echo "Installing binary"
cp ropipe /usr/bin/ropipe cp ropipe /usr/bin/ropipe
endif endif
uninstall: uninstall:
ifneq ($(UNAME), Linux)
@echo "Automatic uninstalling is only supported on Linux"
@exit
endif
ifneq ($(shell id -u), 0) ifneq ($(shell id -u), 0)
@echo "You must be root to uninstall" @echo "You must be root to uninstall"
else else
echo "Removing man file" @echo "Removing man file"
rm -rf /usr/local/share/man/man1/ropipe.1 rm -rf /usr/local/share/man/man1/ropipe.1
echo "Uninstalling binary" @echo "Uninstalling binary"
rm -rf /usr/bin/ropipe rm -rf /usr/bin/ropipe
endif endif
update: update:
git pull git pull
make install make install

View File

@ -1,35 +1,63 @@
# Ropipe # Ropipe
![GitHub](https://img.shields.io/github/license/MatMasIt/ropipe) ![License](https://img.shields.io/github/license/MatMasIt/ropipe)
![c](https://img.shields.io/badge/language-C-blue)
> Convert Roman numbers to integers and vice-versa in pipes.
> Convert roman numbers to integers and vice-versa in pipes
## What is ropipe? ## What is ropipe?
Ropipe converts decimal integers to roman numbers and vice versa, reading from stdin and printing to stdout, it is meant to primarily be used in pipes.
Default is integer to roman.
## Options
* -r : Roman to integer. Integer to roman is the default.
## Installation
1. Clone this repository
2. `cd` into it
3. run
```sh
sudo make install
```
or
```sh
doas make install
```
(The program is compiled at this time, you may check the script and the program out beforehand) Ropipe converts base-10 integers to Roman numbers and vice versa, reading from stdin and printing to stdout. It is designed to be used primarily in pipes. By default, it converts integers to Roman numbers.
## Options
- `-r`: Roman to integer conversion. Integer to Roman is the default behavior.
- `-h`: Display help message and exit.
- `-s`: Fail silently, with no output on invalid input.
- `-q`: Accept quirky Roman numerals like IIII for IV.
- `-k`: Don't quit on invalid input; continue processing subsequent inputs.
You can use the options concatenated, like `-rsk` or individually, like `-r -s -k`.
## Installation
1. Clone this repository:
```sh
git clone https://github.com/MatMasIt/ropipe.git
cd ropipe
```
2. Compile and install:
```sh
sudo make install
```
or
```sh
doas make install
```
The program is compiled during installation. You may review the script and the program before proceeding.
## Examples ## Examples
Once installed, you can find sample files in the project dir.
Once installed, you can use `ropipe` in your shell commands. Below are some examples:
--- ---
Example 1:
**Example 1:**
```sh ```sh
ropipe < arabic.txt ropipe < arabic.txt
``` ```
Result:
**Result:**
``` ```
I I
X X
@ -38,26 +66,38 @@ CCCXXI
``` ```
--- ---
Example 2:
**Example 2:**
```sh ```sh
ropipe -r < roman.txt ropipe -r < roman.txt
``` ```
Result:
**Result:**
``` ```
4 4
5 5
7 7
1200 1200
``` ```
## Manpage ## Manpage
A manpage is installed alongside the program, try running `man ropipe` A manpage is installed alongside the program. You can access it by running:
```sh
man ropipe
```
## Updates ## Updates
There is no automatic update system as of yet, you are advised to regularly visit https://github.com/MatMasIt/ropipe, download and re-run the installer in order to get updates There is no automatic update system currently implemented. Please visit [ropipe GitHub repository](https://github.com/MatMasIt/ropipe) regularly to check for updates. To update, download the latest version and re-run the installer.
Nontheless, it's not a program that one would expect to change often, so manual updates should be rare.
## Make commands ## Make commands
* `make`: compile ropipe
* `make install`: install ropipe - `make`: compile ropipe
* `make uninstall`: uninstall ropipe - `make install`: install ropipe
- `make uninstall`: uninstall ropipe

View File

@ -1,15 +1,26 @@
.TH ROPIPE 1 "06 October 2021" .TH ROPIPE 1 "07 July 2024"
.SH NAME .SH NAME
ropipe - Convert roman numbers to integers and vice-versa in pipes ropipe - Convert Roman numerals to integers and vice-versa in pipes
.SH SYNOPSIS .SH SYNOPSIS
ropipe [ -r ] ropipe [ -rskqh ]
.SH DESCRIPTION .SH DESCRIPTION
Ropipe converts decimal integers to roman numbers and vice versa, reading from stdin and printing to stdout, it is meant to primarily be used in pipes. Ropipe converts decimal integers to Roman numerals and vice versa, reading from stdin and printing to stdout. It is meant to primarily be used in pipes. By default, it converts integers to Roman numerals.
Default is integer to roman.
.SS Options .SS Options
.TP .TP
-r -r
Roman to integer. Integer to roman is the default. Convert Roman numerals to integers. Integer to Roman is the default.
.TP
-s
Fails silently, with no output on invalid input.
.TP
-k
Don't quit on invalid input; continue processing subsequent inputs.
.TP
-q
Accept quirky Roman numerals like IIII for IV.
.TP
-h
Display this help message.
.SH FILES .SH FILES
.TP .TP
/usr/bin/ropipe /usr/bin/ropipe

338
ropipe.c
View File

@ -1,138 +1,224 @@
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#define RDIGITS 14 #include <stdint.h>
int validateN(char c) { #include <stdbool.h>
int n = (int)c; #include <errno.h>
return c > 47 && n < 58; #include <ctype.h>
}
/*
* Returns the value of a roman numeral digit
* @param {char} c - Roman digit
*/
static int roman_to_integer(char c) {
switch (c) {
case 'I':
return 1;
case 'V':
return 5;
case 'X':
return 10;
case 'L':
return 50;
case 'C':
return 100;
case 'D':
return 500;
case 'M':
return 1000;
default:
return 0;
}
}
/*
* Returns the interger value of a roman number
* @param {string} s - Roman number
*/
int roman_to_int(char *s) {
int i, int_num = roman_to_integer(s[0]);
for (i = 1; s[i] != '\0'; i++) { #define MAX_ROMAN_LEN 77 // 76 characters + 1 for the null terminator
int prev_num = roman_to_integer(s[i - 1]); #define MAX_INT_LEN 6 // 65535 (5 characters) + 1 for the null terminator
int cur_num = roman_to_integer(s[i]);
if (prev_num < cur_num) { #define NUM_SYMBOLS 13
int_num = int_num - prev_num + (cur_num - prev_num);
} else { const uint16_t values[NUM_SYMBOLS] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
int_num += cur_num; const char *symbols[NUM_SYMBOLS] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
} const uint8_t symbols_len[NUM_SYMBOLS] = {1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1};
}
return int_num; void print_usage(const char *program_name)
{
printf("Usage: %s [options] [input]\n", program_name);
printf("Options:\n");
printf(" -r: Convert a Roman numeral to an integer\n");
printf(" -s: Fails silently, with no output\n");
printf(" -k: Don't quit on invalid input\n");
printf(" -q: Accept quirky Roman numerals like IIII for IV (only valid with -r)\n");
printf(" -h: Display this help message\n");
} }
/*
* Map of roman digits symbols and their value void int_to_roman(uint16_t num, char result[MAX_ROMAN_LEN])
* @elem {string} sym - digit symbol {
* @elem {int} val - digit symbol if (num == 0)
*/ return;
typedef struct {
char *sym; char *result_ptr = result;
int val; for (uint8_t i = 0; i < NUM_SYMBOLS; i++)
} numeral;
/*
* Returns greatest numeral index
* @elem {numeral} nu - map of roman digits symbols and their value
* @elem {int} num - number
*/
int maxNume(numeral *nu, int num) {
int i, index;
for (i = 0; i < RDIGITS; i++) { // RDIGITS numerals in array
if (nu[i].val <= num)
index = i;
}
// gretest value numeral index, not greater than number
return index;
}
/*
* Prints roman number from integer
* @elem {numeral} nu - map of roman digits symbols and their value
* @elem {int} num - number
*/
void decToRoman(numeral *nu, int num) {
int max;
if (num != 0) {
max = maxNume(nu, num);
printf("%s", nu[max].sym);
num -= nu[max].val; // decrease number
decToRoman(nu, num); // recursively print numerals
}
}
/*
* Main
* @elem {int} argc - Argument count
* @elem {string[]} argv - Strings array
*/
int main(int argc, char *argv[]) {
int direction = 0;
/*
* 0: integer to roman (default)
* 1: roman to integer
*/
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "-r")) {
direction = 1;
} else {
direction = 0;
}
}
numeral nume[RDIGITS] = {{"I", 1}, {"IV", 4}, {"V", 5}, {"IX", 9},
{"X", 10}, {"XL", 40}, {"L", 50}, {"XC", 90},
{"C", 100}, {"CD", 400}, {"D", 500}, {"CM", 900},
{"M", 1000}, {"MMMM", 4000}};
char str[51];
int inputint = 0;
switch (direction) {
case 0:
while (scanf("%50s", str) &&
!feof(stdin)) // read integers and convert to roman
{ {
printf("%d\n", roman_to_int(str)); while (num >= values[i])
scanf("%*[^\n]"); // discard all until newline
}
break;
case 1:
while (scanf("%9s", str) &&
!feof(stdin)) // read roman and convert to integers
{ {
for (int i = 0; i < strlen(str); i++) { num -= values[i];
if (!validateN(str[i])) result_ptr += sprintf(result_ptr, "%s", symbols[i]);
break;
} }
sscanf(str, "%d", &inputint);
decToRoman(nume, inputint);
printf("\n");
scanf("%*[^\n]"); // discard all until newline
} }
break; }
void upper(char *str)
{
for (char *p = str; *p; p++)
{
*p = toupper(*p);
}
}
bool roman_to_int(char roman[MAX_ROMAN_LEN], uint16_t *result, bool allow_quirky)
{
*result = 0;
upper(roman);
const char *roman_ptr = roman;
int repetitions = 0;
char last_char = '\0';
for (uint8_t i = 0; i < NUM_SYMBOLS; i++)
{
while (strncmp(roman_ptr, symbols[i], symbols_len[i]) == 0)
{
// Validate repetition of the same numeral
if (!allow_quirky)
{
if (last_char == symbols[i][0])
{
repetitions++;
if ((symbols_len[i] == 1 && repetitions >= 3 && symbols[i][0] != 'M') ||
(symbols_len[i] == 2 && repetitions >= 1))
{
return false; // Invalid due to too many repetitions
}
}
else
{
repetitions = 0;
}
}
*result += values[i];
roman_ptr += symbols_len[i];
last_char = symbols[i][0];
}
}
return *roman_ptr == '\0';
}
typedef uint8_t flag;
#define ROMAN_TO_INT 0
#define INT_TO_ROMAN 1
#define SILENT 0
#define VERBOSE 1
#define QUIT 0
#define NO_QUIT 1
int handle_int_to_roman(flag verbosity, flag quit)
{
char line[MAX_INT_LEN];
while (fgets(line, MAX_INT_LEN, stdin) != NULL)
{
char *endptr;
errno = 0;
long num = strtol(line, &endptr, 10);
if (errno != 0 || (*endptr != '\n' && *endptr != '\0') || num <= 0 || num > UINT16_MAX)
{
if (verbosity == VERBOSE)
{
fprintf(stderr, "Error: input must be a non-zero positive integer less than or equal to %d\n", UINT16_MAX);
}
if (quit == QUIT)
{
return 1;
}
}
else
{
char result[MAX_ROMAN_LEN];
int_to_roman(num, result);
printf("%s\n", result);
}
} }
return 0; return 0;
} }
int handle_roman_to_int(flag verbosity, flag quit, bool allow_quirky)
{
char line[MAX_ROMAN_LEN];
while (fgets(line, MAX_ROMAN_LEN, stdin) != NULL)
{
line[strcspn(line, "\n")] = '\0';
uint16_t result;
if (!roman_to_int(line, &result, allow_quirky))
{
if (verbosity == VERBOSE)
{
fprintf(stderr, "Error: input must be a valid Roman numeral\n");
}
if (quit == QUIT)
{
return 1;
}
}
else
{
printf("%d\n", result);
}
}
return 0;
}
int main(int argc, char *argv[])
{
flag mode = INT_TO_ROMAN;
flag verbosity = VERBOSE;
flag quit = QUIT;
bool allow_quirky = false;
if (argc > 1)
{
for (int i = 1; i < argc; i++)
{
char *arg = argv[i];
if (arg[0] == '-')
{
for (int j = 1; arg[j] != '\0'; j++)
{
switch (arg[j])
{
case 'r':
mode = ROMAN_TO_INT;
break;
case 's':
verbosity = SILENT;
break;
case 'k':
quit = NO_QUIT;
break;
case 'q':
if (mode == ROMAN_TO_INT)
{
allow_quirky = true;
}
else
{
fprintf(stderr, "Error: Option -q is only valid with -r (Roman to integer conversion)\n");
return 1;
}
break;
case 'h':
print_usage(argv[0]);
return 0;
default:
fprintf(stderr, "Error: Unknown option '%c'\n", arg[j]);
return 1;
}
}
}
else
{
fprintf(stderr, "Error: Unknown argument '%s'\n", arg);
return 1;
}
}
}
switch (mode)
{
case INT_TO_ROMAN:
return handle_int_to_roman(verbosity, quit);
case ROMAN_TO_INT:
return handle_roman_to_int(verbosity, quit, allow_quirky);
default:
fprintf(stderr, "Invalid mode\n");
return 1;
}
}