diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cf67bea --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +all: unicon + +WARNINGS = -Wall +DEBUG = -ggdb -fno-omit-frame-pointer +OPTIMIZE = -O2 +OPTS = -lm + +unicon: Makefile unicon.c + $(CC) -o $@ $(WARNINGS) $(DEBUG) $(OPTIMIZE) $(OPTS) unicon.c + +clean: + rm -f unicon + +install: + echo "Installing is not supported" + +run: + ./unicon + diff --git a/README.md b/README.md index 9162eb1..8c1568f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,81 @@ # unicon -A simple and efficient unit conversion command line tool written in C + +## Table of Contents + +- [Introduction](#introduction) +- [Features](#features) +- [Bulding](#building) +- [Usage](#usage) +- [Options](#options) +- [Examples](#examples) + +## Introduction +**unicon** is a command-line unit conversion tool that allows you to easily +convert values between various units such as temperature, length, time, mass, +and digital storage. This tool is designed to be user-friendly and flexible, +allowing you to convert units with precision. + +## Features +- Convert between a wide range of unit types including temperature, length, + time, mass, and digital storage. +- Easily specify the number of decimal places for rounding the result. + +## Building + +This tool is provided as source code and can be built using `make`. By just +typing make in the terminal within the code's directory: + + ```bash + make + ``` + +You should now have an executable named `unicon`. You can copy it to a location +in your PATH for easy access. + +## Usage + +The general usage format for the **unicon** tool is as follows: + +```bash +unicon [OPTIONS] VALUE from to +``` + +- `OPTIONS`: Optional command-line options (see [Options](#options)). +- `VALUE`: The numeric value you want to convert. +- `from`: Keyword to specify the source unit. +- ``: The source unit you want to convert from. +- `to`: Keyword to specify the target unit. +- ``: The target unit you want to convert to. + +## Options + +- `-r, --round=PLACES`: Round the result to the specified number of decimal + places. +- `-h, --help`: Display the help message and exit. +- `-v, --version`: Display version information and exit. + +## Examples + +1. Convert 100 degrees Celsius to Fahrenheit, rounding to 2 decimal places: + + ```bash + unicon -r 2 100 from celsius to fahrenheit + ``` + +2. Convert 5 kilometers to miles, rounding to 3 decimal places: + + ```bash + unicon -r 3 5 from kilometers to miles + ``` + +3. Display the help message: + + ```bash + unicon -h + ``` + +4. Display version: + + ```bash + unicon -v + ``` diff --git a/unicon.c b/unicon.c new file mode 100644 index 0000000..fca68fb --- /dev/null +++ b/unicon.c @@ -0,0 +1,359 @@ +/* + * unicon.c + * + * Copyright 2024 Clay Gomera + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include + +#define VERSION 0.1 + +// Enumeration for each unit type +typedef enum { + TEMPERATURE, + LENGTH, + TIME, + MASS, + DIGITAL +} UnitType; + +// Enumeration for each unit +typedef enum { + // Temperature units + CELSIUS, FAHRENHEIT, KELVIN, + // Length units + METERS, CENTIMETERS, DECIMETERS, DECAMETERS, HECTOMETERS, KILOMETERS, MILLIMETERS, MILE, INCHES, FEET, + // Time units + SECONDS, MILLISECONDS, MINUTES, HOURS, DAYS, MONTHS, YEARS, + // Mass units + GRAMS, CENTIGRAMS, DECIGRAMS, DECAGRAMS, HECTOGRAMS, MILLIGRAMS, KILOGRAMS, POUNDS, OUNCES, + // Digital storage units + BYTES, KILOBYTES, MEGABYTES, GIGABYTES, TERABYTES, PETABYTES, EXABYTES +} Unit; + +// Struct for the units table +typedef struct _UnitTable { + UnitType type; + Unit unit; + const char *name; + double conversion_factor; +} UnitTable; + +// Units table with conversion factors +UnitTable unit_table[] = { + // Temperature units + {TEMPERATURE, CELSIUS, "celsius", 0}, + {TEMPERATURE, FAHRENHEIT, "fahrenheit", 0}, + {TEMPERATURE, KELVIN, "kelvin", 0}, + // Length units + {LENGTH, METERS, "meters", 1.0}, + {LENGTH, CENTIMETERS, "centimeters", 100.0}, + {LENGTH, DECIMETERS, "decimeters", 10.0}, + {LENGTH, DECAMETERS, "decameters", 0.1}, + {LENGTH, HECTOMETERS, "hectometers", 0.01}, + {LENGTH, KILOMETERS, "kilometers", 0.001}, + {LENGTH, MILLIMETERS, "millimeters", 1000.0}, + {LENGTH, MILE, "miles", 0.000621371}, + {LENGTH, INCHES, "inches", 39.3701}, + {LENGTH, FEET, "feet", 3.28084}, + // Time units + {TIME, SECONDS, "seconds", 1.0}, + {TIME, MILLISECONDS, "milliseconds", 1000.0}, + {TIME, MINUTES, "minutes", 1.0 / 60.0}, + {TIME, HOURS, "hours", 1.0 / 3600.0}, + {TIME, DAYS, "days", 1.0 / 86400.0}, + {TIME, MONTHS, "months", 1.0 / 2592000.0}, + {TIME, YEARS, "years", 1.0 / 31536000.0}, + // Mass units + {MASS, GRAMS, "grams", 1.0}, + {MASS, CENTIGRAMS, "centigrams", 100.0}, + {MASS, DECIGRAMS, "decigrams", 10.0}, + {MASS, DECAGRAMS, "decagrams", 0.1}, + {MASS, HECTOGRAMS, "hectograms", 0.01}, + {MASS, MILLIGRAMS, "milligrams", 1000.0}, + {MASS, KILOGRAMS, "kilograms", 0.001}, + {MASS, POUNDS, "pounds", 0.00220462}, + {MASS, OUNCES, "ounces", 0.03527396}, + // Digital storage units + {DIGITAL, BYTES, "bytes", 1.0}, + {DIGITAL, KILOBYTES, "kilobytes", 1.0 / 1024.0}, + {DIGITAL, MEGABYTES, "megabytes", 1.0 / 1048576.0}, + {DIGITAL, GIGABYTES, "gigabytes", 1.0 / 1073741824.0}, + {DIGITAL, TERABYTES, "terabytes", 1.0 / 1099511627776.0}, + {DIGITAL, PETABYTES, "petabytes", 1.0 / 1125899906842624.0}, + {DIGITAL, EXABYTES, "exabytes", 1.0 / 1152921504606846976.0}, +}; + +// Function prototypes +bool isNumeric(const char *str); +double convertUnit(double value, Unit from, Unit to); +Unit matchArgument(const char *arg); +void displayHelp(); +void displayVersion(); +void displayUnits(); + +int main(int argc, char **argv) { + int opt; + int round_places = -1; // Default value for rounding places + + // Check if there are no command-line arguments + if (argc == 1) { + displayHelp(); + return 0; + } + + static const char* const short_options = "r:shv"; + static struct option long_options[] = { + {"round", required_argument, 0, 'r'}, + {"show", no_argument, 0, 's'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {NULL, 0, NULL, 0} + }; + + // Command line option parsing + while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { + switch (opt) { + case 'r': + round_places = atoi(optarg); + break; + case 'h': + displayHelp(); + return 0; + case 'v': + displayVersion(); + return 0; + case 's': + displayUnits(); + return 0; + default: + fprintf(stderr, "Use '-h, --help' for help.\n"); + return 1; + } + } + + // Check if there are enough arguments + if (optind + 5 != argc) { + printf("Invalid command format. Please provide the correct number of arguments.\n"); + displayHelp(); + return 1; + } + + // Extract the value to convert + if (!isNumeric(argv[optind])) { + printf("Invalid value provided. Please provide a valid numeric value.\n"); + return 1; + } + + double value = atof(argv[optind]); + + // Find the positions of "from" and "to" keywords + int fromPos = -1; + int toPos = -1; + for (int i = optind + 1; i < argc - 1; i++) { + if (strcasecmp(argv[i], "from") == 0) { + fromPos = i + 1; + } else if (strcasecmp(argv[i], "to") == 0) { + toPos = i + 1; + } + } + + // Check if both "from" and "to" keywords were found + if (fromPos == -1 || toPos == -1) { + printf("Invalid command format. Please provide both 'from' and 'to' units.\n"); + displayHelp(); + return 1; + } + + // Find the matching units + Unit from = matchArgument(argv[fromPos]); + Unit to = matchArgument(argv[toPos]); + + // Check if both units are valid + if (from == -1 || to == -1) { + printf("Invalid units provided. Please provide valid units.\n"); + displayHelp(); + return 1; + } + + // Convert the value + double result; + if (from < to) { + result = convertUnit(value, from, to); + } else { + result = convertUnit(value, to, from); + } + + // Round the result if round_places is set + if (round_places >= 0) { + result = round(result * pow(10, round_places)) / pow(10, round_places); + } + + // Determine the number of decimal places for formatting + int decimal_places = (round_places >= 0) ? round_places : 2; + + // Display the result with the appropriate decimal places + printf("%.*f %s = %.*f %s\n", decimal_places, value, unit_table[from].name, decimal_places, result, unit_table[to].name); + + return 0; +} + +// Function to check if a string is a valid number +bool isNumeric(const char *str) { + if (str == NULL || str[0] == '\0') { + return false; + } + int i = 0; + if (str[0] == '-' || str[0] == '+') { + i++; + } + int dotCount = 0; + int len = strlen(str); + for (; i < len; i++) { + if (!isdigit(str[i])) { + if (str[i] == '.' && dotCount == 0) { + dotCount++; + } else { + return false; + } + } + } + return true; +} + +// Function to convert a value from one unit to another +double convertUnit(double value, Unit from, Unit to) { + if (from == to) { + return value; + } + + // Temperatures need to be handled by formula + switch (from) { + case CELSIUS: + switch (to) { + case FAHRENHEIT: + return (value * 9/5) + 32; + case KELVIN: + return value + 273.15; + default: + break; + } + case FAHRENHEIT: + switch (to) { + case CELSIUS: + return (value - 32) * 5/9; + case KELVIN: + return (value - 32) * 5/9 + 273.15; + default: + break; + } + case KELVIN: + switch (to) { + case CELSIUS: + return value - 273.15; + case FAHRENHEIT: + return (value - 273.15) * 9/5 + 32; + default: + break; + } + default: + if (unit_table[from].type != unit_table[to].type) { + printf("Cannot convert between different unit types.\n"); + exit(1); + } + double factor = unit_table[from].conversion_factor / unit_table[to].conversion_factor; + return value * factor; + } +} + +// Function to match an argument to a unit +Unit matchArgument(const char *arg) { + for (int i = 0; i < sizeof(unit_table) / sizeof(unit_table[0]); i++) { + if (strcasecmp(arg, unit_table[i].name) == 0) { + return unit_table[i].unit; + } + } + return -1; +} + +// Function to display the help message +void displayHelp() { + printf("Usage: unicon [OPTIONS] VALUE from to \n"); + printf("Convert between various units.\n\n"); + printf("Options:\n"); + printf("\t-r, --round=PLACES Round the result to the specified number of decimal places.\n"); + printf("\t-s, --show Show the full table of supported units.\n"); + printf("\t-h, --help Display this help message and exit.\n"); + printf("\t-v, --version Display version information and exit.\n"); +} + +void displayUnits() { + printf("Supported units:\n"); + printf("TEMPERATURE:\n"); + printf("\t- Celsius\n"); + printf("\t- Fahrenheit\n"); + printf("\t- Kelvin\n"); + printf("LENGTH:\n"); + printf("\t- Millimeters\n"); + printf("\t- Centimeters\n"); + printf("\t- Decimeters\n"); + printf("\t- Meters\n"); + printf("\t- Decameters\n"); + printf("\t- Hectometers\n"); + printf("\t- Kilometers\n"); + printf("\t- Miles\n"); + printf("\t- Inches\n"); + printf("\t- Feet\n"); + printf("TIME:\n"); + printf("\t- Milliseconds\n"); + printf("\t- Seconds\n"); + printf("\t- Minutes\n"); + printf("\t- Hours\n"); + printf("\t- Days\n"); + printf("\t- Months\n"); + printf("\t- Years\n"); + printf("MASS:\n"); + printf("\t- Milligrams\n"); + printf("\t- Centigrams\n"); + printf("\t- Decigrams\n"); + printf("\t- Grams\n"); + printf("\t- Decagrams\n"); + printf("\t- Hectograms\n"); + printf("\t- Kilograms\n"); + printf("\t- Pounds\n"); + printf("\t- Ounces\n"); + printf("DIGITAL STORAGE:\n"); + printf("\t- Bytes\n"); + printf("\t- Kilobytes\n"); + printf("\t- Megabytes\n"); + printf("\t- Gigabytes\n"); + printf("\t- Terabytes\n"); + printf("\t- Petabytes\n"); + printf("\t- Exabytes\n"); +} + +// Function to display the version information +void displayVersion() { + printf("unicon v%.1f\n", VERSION); +}