diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index f1d8b91..5360538 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,26 +1,26 @@ -# Contributor Covenant Code of Conduct +# Code of Conduct ## Our Pledge -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct that could reasonably be considered inappropriate in a professional setting ## Our Responsibilities @@ -30,17 +30,16 @@ Project maintainers have the right and responsibility to remove, edit, or reject ## Scope -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at aceisace63@yahoo.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [this email](inkycal@aceinnolab.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f9b084b..c4b9122 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,31 +1,49 @@ -# Inkycal Contribution Policy +# Contributing to Inkycal -Thanks for willing to contribute to Inkycal -We welcome all sort of contributions, for example: -* giving support via the Discord server -* submitting hotfixes for existing bugs -* giving ideas for new features -* financial contributions (while Inkycal is still dependent on them. These go towards new hardware, displays and a bit of coffee) +Welcome to Inkycal! We are excited that you are considering contributing to our project. Before you get started, please take a moment to read through our contribution guidelines. -# Third party modules -So you had a great idea for an inkycal-module? Awesome! In fact, there is already a repo sepcfifically created for that purpose: [inkycal-modules-template](https://github.com/aceisace/inkycal-modules-template). Just fork that repo, add your module and give me a shout via Discord, Github or Email. If it is really unique and convincing, chances are, if you agree, that it will be available as default module in a future release. Please do not attempt to have it merged straight into main. We try not to touch main except for new releases to keep things consistent, stable and easy-to-maintain. +## Code of Conduct -# Code contributions (PRs, hotfixes, Critical improvements) -So you found a bug in Inkycal and tested out a bugfix? Kudos! Please fork the Inkycal repo, add your changes in there and create a PR targeting main. For all other PRs, please target a different branch. +This project and everyone participating in it are governed by our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report any unacceptable behavior. -Don't forget to add your name in the file `CONTRIBUTORS.md` of the corresponding branch. Thank You! +## How Can I Contribute? -# Submitting Issues +### Reporting Bugs -Please only submit reproducible issues with clear instructions on how to reproduce them. +Before submitting a bug report, check if the issue is already reported in the [Issues](https://github.com/aceinnolab/Inkycal/issues) section. If not, please open a new issue with a detailed description of the problem, including steps to reproduce it. -When you are submitting a new issue, please supply the following information: +### Suggesting Enhancements -### Release version -* are you using main or a different branch. In most cases, this is main +We welcome suggestions for new features or enhancements. Use the [Issues](https://github.com/aceinnolab/Inkycal/issues) section to submit your ideas, and provide as much detail as possible. -### Expected behavior and actual behavior -* what were you expecting to happen and what did really happen? +### Third party modules +So you had a great idea for an inkycal-module? Awesome! In fact, there is already a repo sepcfifically created for that purpose: [inkycal-modules-template](https://github.com/aceisace/inkycal-modules-template). Just fork that repo, add your module and give me a shout via Discord, Github or Email. + + +### Pull Requests + +1. Fork the repository and create a new branch for your feature or bug fix. +2. Make your changes and test thoroughly. +3. Ensure your code follows our coding standards. +4. Update the documentation if necessary. +5. Add your name in the file `CONTRIBUTORS.md`. +6. Open a pull request, referencing any related issues. + +## Code Standards + +Follow our coding standards to maintain consistency across the project. Check the existing codebase to understand the style and conventions. + +## Testing + +Ensure that your changes are thoroughly tested. If applicable, provide test cases to cover your code. + +## License + +By contributing, you agree that your contributions will be licensed under the [LICENSE](https://github.com/aceinnolab/Inkycal/blob/main/LICENSE) file of this project. + +## Thank You + +Thank you for considering contributing to Inkycal! Your help is invaluable, and we appreciate your time and effort. + +Happy coding! -### Steps to reproduce the behavior -* How can the devs re-create the same problem you were having? diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..45a3586 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,29 @@ +# Pull Request + +## Description +_Briefly describe the purpose of this pull request_ + +## Changes Made +_Describe the changes you made in this PR_ + +## Related Issues +_Reference any related issues here. Use the format "Fixes #" if this PR fixes an issue._ + +## How to Test +_Provide step-by-step instructions or commands on how to test your changes_ + +## Screenshots (if applicable) +_Include screenshots or GIFs that demonstrate the changes (if applicable)_ + +## Checklist +_Place an 'x' in the checkboxes that apply. +If you're unsure about any of them, don't hesitate to ask._ + +- [ ] I have read the [contribution guidelines](https://github.com/aceinnolab/Inkycal/blob/main/.github/CONTRIBUTING.md) +- [ ] My code follows the project's coding standards +- [ ] I have tested my changes +- [ ] I have updated the documentation +- [ ] My changes do not introduce new warnings or errors + +## Additional Notes +_Any additional information or context you want to provide_ diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py index a092007..ff3f16e 100644 --- a/inkycal/custom/functions.py +++ b/inkycal/custom/functions.py @@ -277,60 +277,88 @@ def internet_available(): return False -def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)): - """Draws a border at given coordinates. +from PIL import Image, ImageDraw + +def draw_dotted_line(draw, start, end, colour, thickness): + """Draws a dotted line between start and end points using dots.""" + delta_x = end[0] - start[0] + delta_y = end[1] - start[1] + distance = ((delta_x ** 2 + delta_y ** 2) ** 0.5) + dot_spacing = 6 # Distance between dots + + for i in range(0, int(distance / dot_spacing), 1): + dot_position = (start[0] + (i * dot_spacing * delta_x / distance), + start[1] + (i * dot_spacing * delta_y / distance)) + # Drawing a circle at each dot position to create a dotted effect + draw.ellipse([(dot_position[0] - thickness, dot_position[1] - thickness), + (dot_position[0] + thickness, dot_position[1] + thickness)], + fill=colour) + +def draw_dashed_line(draw, start, end, colour, thickness): + """Draws a dashed line between start and end points.""" + delta_x = end[0] - start[0] + delta_y = end[1] - start[1] + distance = ((delta_x ** 2 + delta_y ** 2) ** 0.5) + step_size = 10 + gap_size = 5 + + for i in range(0, int(distance / (step_size + gap_size)), 1): + segment_start = (start[0] + (i * (step_size + gap_size) * delta_x / distance), + start[1] + (i * (step_size + gap_size) * delta_y / distance)) + segment_end = (segment_start[0] + (step_size * delta_x / distance), + segment_start[1] + (step_size * delta_y / distance)) + draw.line((segment_start, segment_end), fill=colour, width=thickness) + +def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1), style='solid'): + """ + Draws a border at given coordinates with specified styles (solid, dotted, dashed). Args: - - image: The image on which the border should be drawn (usually im_black or - im_colour. - - - xy: Tuple representing the top-left corner of the border e.g. (32, 100) - where 32 is the x co-ordinate and 100 is the y-coordinate. - - - size: Size of the border as a tuple -> (width, height). - - - radius: Radius of the corners, where 0 = plain rectangle, 5 = round corners. - - - thickness: Thickness of the border in pixels. - - - shrinkage: A tuple containing decimals presenting a percentage of shrinking - -> (width_shrink_percentage, height_shrink_percentage). - e.g. (0.1, 0.2) ~ shrinks the width of border by 10%, shrinks height of - border by 20% + - image: Image on which the border should be drawn. + - xy: Tuple for the top-left corner of the border. + - size: Size of the border as a tuple (width, height). + - radius: Radius of the corners. + - thickness: Thickness of the border in pixels. + - shrinkage: Tuple for width and height shrinkage percentages. + - style: Style of the border ('solid', 'dotted', 'dashed'). """ colour = 'black' - - # size from function paramter width, height = int(size[0] * (1 - shrinkage[0])), int(size[1] * (1 - shrinkage[1])) - - # shift cursor to move rectangle to center offset_x, offset_y = int((size[0] - width) / 2), int((size[1] - height) / 2) x, y, diameter = xy[0] + offset_x, xy[1] + offset_y, radius * 2 - # lenght of rectangle size a, b = (width - diameter), (height - diameter) - # Set coordinates for staright lines p1, p2 = (x + radius, y), (x + radius + a, y) p3, p4 = (x + width, y + radius), (x + width, y + radius + b) p5, p6 = (p2[0], y + height), (p1[0], y + height) p7, p8 = (x, p4[1]), (x, p3[1]) + + draw = ImageDraw.Draw(image) + + # Choose the appropriate line drawing function based on style + if style == 'solid': + line_drawer = draw.line + elif style == 'dotted': + line_drawer = lambda coords, fill, width: draw_dotted_line(draw, coords[0], coords[1], fill, width) + elif style == 'dashed': + line_drawer = lambda coords, fill, width: draw_dashed_line(draw, coords[0], coords[1], fill, width) + else: + raise ValueError(f"Unknown style: {style}") + + # Draw lines according to the chosen style + line_drawer((p1, p2), fill=colour, width=thickness) + line_drawer((p3, p4), fill=colour, width=thickness) + line_drawer((p5, p6), fill=colour, width=thickness) + line_drawer((p7, p8), fill=colour, width=thickness) + if radius != 0: - # Set coordinates for arcs c1, c2 = (x, y), (x + diameter, y + diameter) c3, c4 = ((x + width) - diameter, y), (x + width, y + diameter) c5, c6 = ((x + width) - diameter, (y + height) - diameter), (x + width, y + height) c7, c8 = (x, (y + height) - diameter), (x + diameter, y + height) - # Draw lines and arcs, creating a square with round corners - draw = ImageDraw.Draw(image) - draw.line((p1, p2), fill=colour, width=thickness) - draw.line((p3, p4), fill=colour, width=thickness) - draw.line((p5, p6), fill=colour, width=thickness) - draw.line((p7, p8), fill=colour, width=thickness) - - if radius != 0: draw.arc((c1, c2), 180, 270, fill=colour, width=thickness) draw.arc((c3, c4), 270, 360, fill=colour, width=thickness) draw.arc((c5, c6), 0, 90, fill=colour, width=thickness) diff --git a/inkycal/modules/inkycal_calendar.py b/inkycal/modules/inkycal_calendar.py index 65012a1..a6d3154 100755 --- a/inkycal/modules/inkycal_calendar.py +++ b/inkycal/modules/inkycal_calendar.py @@ -296,14 +296,27 @@ class Calendar(inkycal_module): month_events = parser.get_events(month_start, month_end, self.timezone) parser.sort() self.month_events = month_events + + # Initialize days_with_events as an empty list + days_with_events = [] - # find out on which days of this month events are taking place - days_with_events = [ - int(events['begin'].format('D')) for events in month_events - ] + # Handle multi-day events by adding all days between start and end + for event in month_events: + start_date = event['begin'].date() + end_date = event['end'].date() + + # Convert start and end dates to arrow objects with timezone + start = arrow.get(event['begin'].date(), tzinfo=self.timezone) + end = arrow.get(event['end'].date(), tzinfo=self.timezone) + + # Use arrow's range function for generating dates + for day in arrow.Arrow.range('day', start, end): + day_num = int(day.format('D')) # get day number using arrow's format method + if day_num not in days_with_events: + days_with_events.append(day_num) # remove duplicates (more than one event in a single day) - list(set(days_with_events)).sort() + days_with_events = sorted(set(days_with_events)) self._days_with_events = days_with_events # Draw a border with specified parameters around days with events @@ -355,7 +368,13 @@ class Calendar(inkycal_module): cursor = 0 for event in upcoming_events: if cursor < len(event_lines): - the_name = event['title'] + event_duration = (event['end'] - event['begin']).days + if event_duration > 1: + # Format the duration using Arrow's localization + days_translation = arrow.get().shift(days=event_duration).humanize(only_distance=True, locale=lang) + the_name = f"{event['title']} ({days_translation})" + else: + the_name = event['title'] the_date = event['begin'].format(self.date_format, locale=lang) the_time = event['begin'].format(self.time_format, locale=lang) # logger.debug(f"name:{the_name} date:{the_date} time:{the_time}") diff --git a/inkycal/modules/inkycal_stocks.py b/inkycal/modules/inkycal_stocks.py index e91e9f0..a18ed34 100755 --- a/inkycal/modules/inkycal_stocks.py +++ b/inkycal/modules/inkycal_stocks.py @@ -1,4 +1,5 @@ -#!python3 +#!/usr/bin/python3 +# -*- coding: utf-8 -*- """ Stocks Module for Inkycal Project @@ -10,26 +11,17 @@ Version 0.1: Migration to Inkycal 2.0.0b by https://github.com/worstface """ -import os import logging - -from inkycal.modules.template import inkycal_module -from inkycal.custom import write, internet_available +import os from PIL import Image -try: - import yfinance as yf -except ImportError: - print('yfinance is not installed! Please install with:') - print('pip3 install yfinance') +from inkycal.custom import write, internet_available +from inkycal.modules.template import inkycal_module -try: - import matplotlib.pyplot as plt - import matplotlib.image as mpimg -except ImportError: - print('matplotlib is not installed! Please install with:') - print('pip3 install matplotlib') +import yfinance as yf +import matplotlib.pyplot as plt +import matplotlib.image as mpimg logger = logging.getLogger(__name__) @@ -82,11 +74,11 @@ class Stocks(inkycal_module): tmpPath = '/tmp/inkycal_stocks/' try: - if not os.path.exists(tmpPath): - os.mkdir(tmpPath) - print(f"Successfully created tmp directory {tmpPath} ") + os.mkdir(tmpPath) except OSError: print(f"Creation of tmp directory {tmpPath} failed") + else: + print(f"Successfully created tmp directory {tmpPath} ") # Check if internet is available if internet_available() == True: @@ -96,10 +88,10 @@ class Stocks(inkycal_module): # Set some parameters for formatting feeds line_spacing = 1 - text_bbox_height = self.font.getbbox("hg") - line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing + text_bbox = self.font.getbbox("hg") + line_height = text_bbox[3] - text_bbox[1] + line_spacing line_width = im_width - max_lines = (im_height // line_height) + max_lines = (im_height // (line_height + line_spacing)) logger.debug(f"max_lines: {max_lines}") diff --git a/inkycal/tests/test_inkycal_stocks.py b/inkycal/tests/test_inkycal_stocks.py index e9ebaba..6ed6334 100755 --- a/inkycal/tests/test_inkycal_stocks.py +++ b/inkycal/tests/test_inkycal_stocks.py @@ -1,56 +1,75 @@ -# #!python3 -# """ -# inkycal_stocks unittest -# """ -# import logging -# import sys -# import unittest -# from inkycal.modules import Stocks as Module -# -# from inkycal.modules.inky_image import Inkyimage -# from inkycal.tests import Config -# preview = Inkyimage.preview -# merge = Inkyimage.merge -# -# tests = [ -# { -# "name": "Stocks", -# "config": { -# "size": [528, 30], -# "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'], -# "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" -# } -# }, -# { -# "name": "Stocks", -# "config": { -# "size": [528, 50], -# "tickers": [], -# "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" -# } -# } -# ] -# -# -# class module_test(unittest.TestCase): -# def test_get_config(self): -# print('getting data for web-ui...', end="") -# Module.get_config() -# print('OK') -# -# def test_generate_image(self): -# for test in tests: -# print(f'test {tests.index(test) + 1} generating image..') -# module = Module(test) -# im_black, im_colour = module.generate_image() -# print('OK') -# if Config.USE_PREVIEW: -# preview(merge(im_black, im_colour)) -# -# -# if __name__ == '__main__': -# logger = logging.getLogger() -# logger.level = logging.DEBUG -# logger.addHandler(logging.StreamHandler(sys.stdout)) -# -# unittest.main() +import unittest +from inkycal.modules import Stocks as Module + +tests = [ +{ + "position": 1, + "name": "Stocks", + "config": { + "size": [528, 20], + "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'], + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Stocks", + "config": { + "size": [528, 20], + "tickers": [], + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Stocks", + "config": { + "size": [528, 200], + "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'], + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Stocks", + "config": { + "size": [528, 800], + "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'], + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Stocks", + "config": { + "size": [528, 100], + "tickers": "TSLA,AMD,NVDA,^DJI,BTC-USD,EURUSD=X", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Stocks", + "config": { + "size": [528, 400], + "tickers": ['TSLA', 'AMD', 'NVDA', '^DJI', 'BTC-USD', 'EURUSD=X'], + "padding_x": 10, "padding_y": 10, "fontsize": 14, "language": "en" + } +}, +] + +class module_test(unittest.TestCase): + def test_get_config(self): + print('getting data for web-ui...', end = "") + Module.get_config() + print('OK') + + def test_generate_image(self): + for test in tests: + print(f'test {tests.index(test)+1} generating image..') + module = Module(test) + module.generate_image() + print('OK') + +if __name__ == '__main__': + unittest.main() diff --git a/requirements.txt b/requirements.txt index 2bdd099..1b0b392 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,3 +24,4 @@ urllib3==2.0.7 python-dotenv==1.0.0 setuptools==68.2.2 html2text==2020.1.16 +yfinance==0.2.32