diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0bff4b4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Environnement virtuel +/venv/ + +# Fichiers de cache +__pycache__/ +*.pyc + +# Fichiers temporaires +*.tmp + +# Fichiers d'environnement +.env + +# Fichiers de logs +/logs/ diff --git a/README.md b/README.md index af845681..d4d2a115 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,218 @@ -
+# Building Your Own Cat-Tool +This challenge is to build your own version of the Unix Command line tool cat. -# Shared Solutions to Coding Challenges +I use for this challenge **argparse module** to parse my cat command line. -Publicly shared solutions to the [Coding Challenges](https://codingchallenges.fyi/). +## Table of Contents + +- [Progress](#progress) +- [Get and Install my Cat Tool](#get-and-install-my-cat-tool) +- [Ready to use my cat-tool commands](#ready-to-use-my-cat-tool-commands) + - [Command to know the different possible commands of my cat-tool](#command-to-know-the-different-possible-commands-of-my-cat-tool) + - [Command to display content of one file](#command-to-display-content-of-one-file) + - [Command to display content of many files](#command-to-display-content-of-many-files) + - [Command to read input from standard in](#command-to-read-input-from-standard-in) + - [Command to number all the lines as they’re printed out](#command-to-number-all-the-lines-as-theyre-printed-out) + - [Command to number the lines as they’re printed out except the blank-lines](#command-to-number-the-lines-as-theyre-printed-out-except-the-blank-lines) + +## Progress +0. Test files received to test solution with. +1. Open specific file on the command line and write its contents to standard out. +2. Reads input from standard in +3. Can concatenate files +4. Number the lines as they’re printed out +5. Number lines but exclude blank lines from being numbered in output + +## Get and Install my Cat Tool +### 1. Clone the Repo : +``` +gh repo clone oussy96/coding-challenges +``` +### 2. Move to the Cat-Tool Project : +``` +cd coding-challenges/cat-tool +``` +### 3. Create a Vitual Environment (Recommended) : +``` +python -m venv venv +``` + +### 4. Activate the Virtual Environment: +``` +source venv/bin/activate +``` + +## Ready to use my cat-tool commands +### Command to know the different possible commands of my cat-tool +``` +% python cccat.py --help +``` +Output : +``` +usage: cccat [-h] [-] [-n] [-b] [filename ...] + +Process cat file. + +positional arguments: + filename The name of file to display his content + +options: + -h, --help show this help message and exit + -, --read Read the input from standard in + -n, --number Number the lines printed out including non-blank lines + -b, --bnumber Number the lines printed out excluding non-blank lines +``` -Join the [Discord Community](https://discord.gg/zv4RKDcEKV). +### Command to display content of one file +``` +python cccat.py test.txt +``` +Output : +``` +"Your heart is the size of an ocean. Go find yourself in its hidden depths." +"The Bay of Bengal is hit frequently by cyclones. The months of November and May, in particular, are dangerous in this regard." +"Thinking is the capital, Enterprise is the way, Hard Work is the solution." +"If You Can'T Make It Good, At Least Make It Look Good." +"Heart be brave. If you cannot be brave, just go. Love's glory is not a small thing." +"It is bad for a young man to sin; but it is worse for an old man to sin." +"If You Are Out To Describe The Truth, Leave Elegance To The Tailor." +"O man you are busy working for the world, and the world is busy trying to turn you out." +"While children are struggling to be unique, the world around them is trying all means to make them look like everybody else." +"These Capitalists Generally Act Harmoniously And In Concert, To Fleece The People." +``` -### How To Add Your Solution -To add your solution follow the process for [making a pull request to an open-source project](https://github.com/gabrieldemarmiesse/getting_started_open_source). +### Command to display content of many files +``` +python cccat.py test.txt test2.txt +``` +Output : +``` +"Your heart is the size of an ocean. Go find yourself in its hidden depths." +"The Bay of Bengal is hit frequently by cyclones. The months of November and May, in particular, are dangerous in this regard." +"Thinking is the capital, Enterprise is the way, Hard Work is the solution." +"If You Can'T Make It Good, At Least Make It Look Good." +"Heart be brave. If you cannot be brave, just go. Love's glory is not a small thing." +"It is bad for a young man to sin; but it is worse for an old man to sin." +"If You Are Out To Describe The Truth, Leave Elegance To The Tailor." +"O man you are busy working for the world, and the world is busy trying to turn you out." +"While children are struggling to be unique, the world around them is trying all means to make them look like everybody else." +"These Capitalists Generally Act Harmoniously And In Concert, To Fleece The People." +"I Don'T Believe In Failure. It Is Not Failure If You Enjoyed The Process." +"Do not get elated at any victory, for all such victory is subject to the will of God." +"Wear gratitude like a cloak and it will feed every corner of your life." +"If you even dream of beating me you'd better wake up and apologize." +"I Will Praise Any Man That Will Praise Me." +"One Of The Greatest Diseases Is To Be Nobody To Anybody." +"I'm so fast that last night I turned off the light switch in my hotel room and was in bed before the room was dark." +"People Must Learn To Hate And If They Can Learn To Hate, They Can Be Taught To Love." +"Everyone has been made for some particular work, and the desire for that work has been put in every heart." +"The less of the World, the freer you live." +``` -Essentially: -1. Fork this repo and clone it. -2. Create a branch and make your change. -3. Push your branch to your fork. -4. Open a PR against this repo. +### Command to read input from standard in +``` +head -n2 test2.txt | python cccat.py - +``` +OR +``` +head -n2 test2.txt | python cccat.py +``` +Output : +``` +"I Don'T Believe In Failure. It Is Not Failure If You Enjoyed The Process." +"Do not get elated at any victory, for all such victory is subject to the will of God." +``` + +### Command to number all the lines as they’re printed out +``` +% head -n3 test.txt | python cccat.py -n +``` +Output : +``` +1 "Your heart is the size of an ocean. Go find yourself in its hidden depths." +2 "The Bay of Bengal is hit frequently by cyclones. The months of November and May, in particular, are dangerous in this regard." +3 "Thinking is the capital, Enterprise is the way, Hard Work is the solution." +``` +Also the blank-lines : +``` +sed G test.txt | python cccat.py -n | head -n4 +``` +Output : +``` +1 "Your heart is the size of an ocean. Go find yourself in its hidden depths." +2 +3 "The Bay of Bengal is hit frequently by cyclones. The months of November and May, in particular, are dangerous in this regard." +4 +``` + +### Command to number the lines as they’re printed out except the blank-lines +``` +sed G test.txt | python cccat.py -b | head -n5 +``` +Output : +``` +1 "Your heart is the size of an ocean. Go find yourself in its hidden depths." + +2 "The Bay of Bengal is hit frequently by cyclones. The months of November and May, in particular, are dangerous in this regard." + +3 "Thinking is the capital, Enterprise is the way, Hard Work is the solution." +``` + +## Test our project using the TestCccat unittest class + +The `TestCccat` class contains unit tests for the `cccat` module, which is responsible for processing and displaying content from files or standard input. ## Table of Contents -- [Build your own wc Tool](Solutions/challenge-wc.md) -- [Build your own JSON Parser](Solutions/challenge-json-parser.md) -- [Build your own Compression Tool](Solutions/challenge-huffman.md) -- [Build your own cut Tool](Solutions/challenge-cut.md) -- [Build your own Load Balancer](Solutions/challenge-load-balancer.md) -- [Build your own Sort Tool](Solutions/challenge-sort.md) -- [Build your own Calculator](Solutions/challenge-calculator.md) -- [Build your own Redis Server](Solutions/challenge-redis.md) -- [Build your own grep](Solutions/challenge-grep.md) -- [Build your own uniq](Solutions/challenge-uniq.md) -- [Build your own Web Server](Solutions/challenge-webserver.md) -- [Build your own URL Shortener](Solutions/challenge-url-shortener.md) -- [Build your own diff](Solutions/challenge-diff.md) -- [Build your own Shell](Solutions/challenge-shell.md) -- [Build your own cat Tool](Solutions/challenge-cat.md) -- [Build your own IRC Client](Solutions/challenge-irc.md) -- [Build your own Memcached Server](Solutions/challenge-memcached.md) -- [Build your own Spotify Client](Solutions/challenge-spotify.md) -- [Build your own Discord Bot](Solutions/challenge-discord.md) -- [Build your own LinkedIn Carousel Generator](Solutions/challenge-licg.md) -- [Build your own sed](Solutions/challenge-sed.md) -- [Build your own DNS Resolver](Solutions/challenge-dns-resolver.md) -- [Build your own Traceroute](Solutions/challenge-traceroute.md) -- [Build your own Realtime Chat Client and Server](Solutions/challenge-realtime-chat.md) -- [Build your own NATS Message Broker](Solutions/challenge-nats.md) -- [Build your own Git Client](Solutions/challenge-git.md) -- [Build your own Rate Limiter](Solutions/challenge-rate-limiter.md) -- [Build your own Scheduling Automation App](Solutions/challenge-scheduleing-automation.md) -- [Build your own Lisp Interpreter](Solutions/challenge-lisp.md) -- [Build your own Tetris](Solutions/challenge-tetris.md) - -- [Build your own QR Code Generator](Solutions/challenge-qr-generator.md) -- [Build your own CronTab Tool](Solutions/challenge-crontab.md) -- [Build your own head](Solutions/challenge-head.md) -- [Build your own jq](Solutions/challenge-jq.md) -- [Build your own Pong](Solutions/challenge-pong.md) -- [Build your own curl](Solutions/challenge-curl.md) -- [Build your own HTTP(S) Load Tester](Solutions/challenge-load-tester.md) -- [Build Your Own Data Privacy Vault](Solutions/challenge-data-privacy-vault.md) -- [Build Your Own Password Cracker](Solutions/challenge-password-cracker.md) -- [Build Your own Spell Checker using Bloom Filter](Solutions/challenge-bloom-filter-spell-checker.md) - - +- [Introduction](#introduction) +- [Setup](#setup) +- [Tests](#tests) + - [parse_arguments Function](#parse_arguments-function) + - [read_file_content Function](#read_file_content-function) + - [cc_cat Function](#cc_cat-function) + - [read_input Function](#read_input-function) + - [read_input_numbered_lines Function](#read_input_numbered_lines-function) + - [read_input_bnumbered_lines Function](#read_input_bnumbered_lines-function) +- [Conclusion](#conclusion) + +### Introduction + +This test suite verifies the functionality of various functions within the `cccat` module. It utilizes Python's `unittest` framework to define test cases covering different scenarios and edge cases. + +### Setup + +To run the tests, ensure that you have Python installed on your system. The tests can be executed using the following command: + +```bash +python -m unittest -v tests/test_cccat.py +``` + +### Tests + +#### parse_arguments Function + +- Tests the `parse_arguments` function with different command-line arguments to ensure correct parsing and handling of options. + +#### read_file_content Function + +- Tests the `read_file_content` function to verify that it correctly reads and returns the content of a file. + +#### cc_cat Function + +- Tests the `cc_cat` function with various scenarios, including reading content from multiple files, standard input, and applying line numbering options. + +#### read_input Function + +- Tests the `read_input` function to ensure that it correctly reads and returns input from standard input. + +#### read_input_numbered_lines Function + +- Tests the `read_input_numbered_lines` function to verify that it correctly numbers lines from standard input. + +#### read_input_bnumbered_lines Function + +- Tests the `read_input_bnumbered_lines` function to ensure that it correctly numbers lines from standard input, excluding blank lines. + +### Conclusion + +The `TestCccat` test class provides comprehensive coverage of the `cccat` module's functionality, ensuring its reliability and correctness across various scenarios. \ No newline at end of file diff --git a/cccat.py b/cccat.py new file mode 100755 index 00000000..6d176e67 --- /dev/null +++ b/cccat.py @@ -0,0 +1,115 @@ +import os +import argparse +import sys + + +def parse_arguments(): + """ + Parse command line arguments using ArgumentParser. + + Returns: + argparse.Namespace: Parsed arguments. + """ + # Create ArgumentParser object + parser = argparse.ArgumentParser(prog='cccat', description='Process cat file or input.') + + # Add arguments + parser.add_argument('filename', type=str, nargs='*', help='The name of file to display its content') + parser.add_argument('-', '--read', action='store_true', help='Read the input from standard in') + parser.add_argument('-n', '--number', action='store_true', help='Number the lines printed out including non-blank lines') + parser.add_argument('-b', '--bnumber', action='store_true', help='Number the lines printed out excluding non-blank lines') + + return parser.parse_args() + + +def get_filepath(filename): + """ + Get the absolute file path. + + Args: + filename (str): Name of the file. + + Returns: + str: Absolute file path. + """ + return os.path.join(os.getcwd(), filename) + + +def read_file_content(filename): + """ + Read and return the content of a file. + + Args: + filename (str): Name of the file. + + Returns: + str: Content of the file. + """ + try: + with open(filename, 'r') as file: + return file.read() + except FileNotFoundError: + print(f"Error: {filename} not found") + + +def read_input(): + """ + Read and return input from standard input. + + Returns: + str: Input from standard input. + """ + return sys.stdin.read() + + +def read_input_numbered_lines(): + """ + Read input from standard input and number the lines. + + Returns: + str: Input from standard input with numbered lines. + """ + return ''.join((f'{index+1} {line}' for index, line in enumerate(sys.stdin))) + + +def read_input_bnumbered_lines(): + """ + Read input from standard input and number the lines excluding non-blank lines. + + Returns: + str: Input from standard input with numbered lines excluding non-blank lines. + """ + num_line = 1 + result = '' + for line in sys.stdin: + if line.strip(): + result += f'{num_line} {line}' + num_line += 1 + else: + result += line + return result + + +def cc_cat(args): + """ + Execute the 'cccat' command based on the provided arguments. + + Args: + args (argparse.Namespace): Parsed command line arguments. + + Returns: + str: Output of the 'cccat' command. + """ + if args.filename: + return ''.join(read_file_content(filename) for filename in args.filename) + elif args.read or not any(vars(args).values()): # If no arguments are provided, execute the 'read' command by default + return read_input() + elif args.number: + return read_input_numbered_lines() + elif args.bnumber: + return read_input_bnumbered_lines() + + +if __name__ == "__main__": + args = parse_arguments() + print(cc_cat(args)) diff --git a/test.txt b/test.txt new file mode 100644 index 00000000..5b499d0e --- /dev/null +++ b/test.txt @@ -0,0 +1,10 @@ +"Your heart is the size of an ocean. Go find yourself in its hidden depths." +"The Bay of Bengal is hit frequently by cyclones. The months of November and May, in particular, are dangerous in this regard." +"Thinking is the capital, Enterprise is the way, Hard Work is the solution." +"If You Can'T Make It Good, At Least Make It Look Good." +"Heart be brave. If you cannot be brave, just go. Love's glory is not a small thing." +"It is bad for a young man to sin; but it is worse for an old man to sin." +"If You Are Out To Describe The Truth, Leave Elegance To The Tailor." +"O man you are busy working for the world, and the world is busy trying to turn you out." +"While children are struggling to be unique, the world around them is trying all means to make them look like everybody else." +"These Capitalists Generally Act Harmoniously And In Concert, To Fleece The People." diff --git a/test2.txt b/test2.txt new file mode 100644 index 00000000..60587092 --- /dev/null +++ b/test2.txt @@ -0,0 +1,10 @@ +"I Don'T Believe In Failure. It Is Not Failure If You Enjoyed The Process." +"Do not get elated at any victory, for all such victory is subject to the will of God." +"Wear gratitude like a cloak and it will feed every corner of your life." +"If you even dream of beating me you'd better wake up and apologize." +"I Will Praise Any Man That Will Praise Me." +"One Of The Greatest Diseases Is To Be Nobody To Anybody." +"I'm so fast that last night I turned off the light switch in my hotel room and was in bed before the room was dark." +"People Must Learn To Hate And If They Can Learn To Hate, They Can Be Taught To Love." +"Everyone has been made for some particular work, and the desire for that work has been put in every heart." +"The less of the World, the freer you live." diff --git a/tests/test_cccat.py b/tests/test_cccat.py new file mode 100644 index 00000000..c9144922 --- /dev/null +++ b/tests/test_cccat.py @@ -0,0 +1,134 @@ +import unittest +from unittest.mock import patch +from io import StringIO +import os +import sys +import argparse + +# Import the functions from my script cccat +from cccat import parse_arguments, read_file_content, read_input, read_input_numbered_lines, read_input_bnumbered_lines, cc_cat + +class TestCccat(unittest.TestCase): + + # Test parse_arguments function with arguments patched + @patch('sys.argv', ['cccat', 'file1.txt']) + def test_parse_arguments_with_file(self): + args = parse_arguments() + self.assertEqual(args.filename, ['file1.txt']) + self.assertFalse(args.read) + self.assertFalse(args.number) + self.assertFalse(args.bnumber) + + @patch('sys.argv', ['cccat', '-']) + def test_parse_arguments_with_standard_input(self): + args = parse_arguments() + self.assertEqual(args.filename, []) + self.assertTrue(args.read) + self.assertFalse(args.number) + self.assertFalse(args.bnumber) + + @patch('sys.argv', ['cccat']) + def test_parse_arguments_with_no_arguments_passed(self): + # Redirect stdin to provide input + with patch('sys.stdin', StringIO("Test standard input")): + args = parse_arguments() + # Get the output directly from cc_cat + output = cc_cat(args) + # Check if no filename is provided + self.assertEqual(args.filename, []) + + # Check if other flags are not set + self.assertFalse(args.read) + self.assertFalse(args.number) + self.assertFalse(args.bnumber) + + # Check if the 'read' command is executed correctly + self.assertEqual(output, "Test standard input") + + @patch('sys.argv', ['cccat', '-n']) + def test_parse_arguments_with_numbered_lines(self): + args = parse_arguments() + self.assertEqual(args.filename, []) + self.assertFalse(args.read) + self.assertTrue(args.number) + self.assertFalse(args.bnumber) + + @patch('sys.argv', ['cccat', '-b']) + def test_parse_arguments_with_bnumbered_lines(self): + args = parse_arguments() + self.assertEqual(args.filename, []) + self.assertFalse(args.read) + self.assertFalse(args.number) + self.assertTrue(args.bnumber) + + # Test read_file_content function + def test_read_file_content(self): + # Create a temporary file + with open('test_file.txt', 'w') as f: + f.write('Test content') + + content = read_file_content('test_file.txt') + self.assertEqual(content, 'Test content') + + # Clean up + os.remove('test_file.txt') + + # Test reading content from multiple files + def test_cc_cat_multiple_files(self): + # Create temporary files with content + with open('file1.txt', 'w') as f1, open('file2.txt', 'w') as f2: + f1.write('Content from file 1\n') + f2.write('Content from file 2\n') + + # Call cc_cat with file names as arguments + args = argparse.Namespace(filename=['file1.txt', 'file2.txt'], read=False, number=False, bnumber=False) + output = cc_cat(args) + + # Verify that the concatenated content of both files is returned correctly + expected_output = 'Content from file 1\nContent from file 2\n' + self.assertEqual(output, expected_output) + + # Clean up temporary files + os.remove('file1.txt') + os.remove('file2.txt') + + # Test read_input function + @patch('sys.stdin', StringIO('Test input')) + def test_read_input(self): + input_ = read_input() + self.assertEqual(input_, 'Test input') + + # Test read_input_numbered_lines function + @patch('sys.stdin', StringIO('Line 1\n\nLine 2\nLine 3\n')) + def test_read_input_numbered_lines(self): + numbered_lines = read_input_numbered_lines() + self.assertEqual(numbered_lines, '1 Line 1\n2 \n3 Line 2\n4 Line 3\n') + + # Test read_input_bnumbered_lines function + @patch('sys.stdin', StringIO('Line 1\n\nLine 2\n\nLine 3\n')) + def test_read_input_bnumbered_lines(self): + bnumbered_lines = read_input_bnumbered_lines() + self.assertEqual(bnumbered_lines, '1 Line 1\n\n2 Line 2\n\n3 Line 3\n') + + # Test cc_cat function + @patch('sys.stdin', StringIO('Line 1\nLine 2\nLine 3\n')) + def test_cc_cat_read_input(self): + args = argparse.Namespace(filename=[], read=True, number=False, bnumber=False) + output = cc_cat(args) + self.assertEqual(output, 'Line 1\nLine 2\nLine 3\n') + + @patch('sys.stdin', StringIO('Line 1\nLine 2\nLine 3\n')) + def test_cc_cat_read_input_numbered_lines(self): + args = argparse.Namespace(filename=[], read=False, number=True, bnumber=False) + output = cc_cat(args) + self.assertEqual(output, '1 Line 1\n2 Line 2\n3 Line 3\n' ) + + @patch('sys.stdin', StringIO('Line 1\n\nLine 2\n\nLine 3\n')) + def test_cc_cat_read_input_bnumbered_lines(self): + args = argparse.Namespace(filename=[], read=False, number=False, bnumber=True) + output = cc_cat(args) + self.assertEqual(output, '1 Line 1\n\n2 Line 2\n\n3 Line 3\n') + + +if __name__ == '__main__': + unittest.main()