├── .gitignore ├── .vscode └── launch.json ├── README.md ├── highlight_butler ├── __init__.py ├── app │ └── cli.py ├── entities │ ├── __init__.py │ └── highlight.py ├── handlers │ └── highlight_import_handler.py ├── resources │ ├── config.yaml │ └── document.md ├── service │ ├── __init__.py │ ├── hypothesis_importer.py │ └── markdown_renderer.py ├── usecase │ ├── __init__.py │ ├── highlight_importer │ │ ├── __init__.py │ │ ├── contract.py │ │ └── usecase.py │ └── highlight_renderer │ │ ├── contract.py │ │ └── usecase.py └── utils │ ├── config.py │ └── singleton.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/renderer 139 | 140 | pages/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Module", 9 | "type": "python", 10 | "request": "launch", 11 | "module": "highlight_butler.app.cli" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Highlight Butler 2 | 3 | Higlight Butler collects highlights from different services and creates notes for reference in your note taking system. 4 | 5 | # Usage 6 | 7 | Highlight Butler is installed as a Python Package using `pip` 8 | 9 | ```sh 10 | # install using pip 11 | pip install git+https://github.com/Boot-Error/highlight_butler 12 | 13 | # create a config file 14 | curl https://github.com/Boot-Error/highlight_butler/blob/main/highlight_butler/resources/config.yaml -O config.yaml 15 | 16 | # add hypothesis API keys 17 | # add notes directory 18 | # add markdown template 19 | highlight_butler --config config.yaml 20 | ``` -------------------------------------------------------------------------------- /highlight_butler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boot-Error/highlight_butler/b686e9a1c6c58bd74b4601abcd69e8f5a672c24c/highlight_butler/__init__.py -------------------------------------------------------------------------------- /highlight_butler/app/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from highlight_butler.handlers.highlight_import_handler import HighlightImportHandler 3 | from highlight_butler.utils.config import Config 4 | 5 | def main(): 6 | 7 | parser = argparse.ArgumentParser( 8 | description="Highlight butler imports highlights from various sources and creates markdown notes for note-taking systems") 9 | 10 | parser.add_argument("--config", type=argparse.FileType("r"), 11 | default="./highlight_butler/resources/config.yaml", help="config file") 12 | 13 | args = parser.parse_args() 14 | 15 | _config = Config() 16 | _config.load(args.config) 17 | 18 | highlightImportHandler: HighlightImportHandler = HighlightImportHandler() 19 | highlightImportHandler.handle() 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /highlight_butler/entities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boot-Error/highlight_butler/b686e9a1c6c58bd74b4601abcd69e8f5a672c24c/highlight_butler/entities/__init__.py -------------------------------------------------------------------------------- /highlight_butler/entities/highlight.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List, Optional 3 | from datetime import datetime 4 | 5 | @dataclass 6 | class HighlightLocation: 7 | locationIdentifier: str 8 | value: str 9 | 10 | @dataclass 11 | class Highlight: 12 | text: str 13 | annotation: str 14 | location: Optional[HighlightLocation] 15 | tags: Optional[List[str]] 16 | 17 | @dataclass 18 | class HighlightDocument: 19 | author: str 20 | category: str 21 | tags: str 22 | created: datetime 23 | updated: datetime 24 | url: str 25 | title: str 26 | highlights: List[Highlight] 27 | 28 | -------------------------------------------------------------------------------- /highlight_butler/handlers/highlight_import_handler.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | from pathlib import Path 4 | from typing import List 5 | 6 | import pydash 7 | from highlight_butler.entities.highlight import HighlightDocument 8 | from highlight_butler.service.hypothesis_importer import \ 9 | HypothesisImporterService 10 | from highlight_butler.service.markdown_renderer import MarkdownRendererService 11 | from highlight_butler.usecase.highlight_importer.contract import \ 12 | HighlightImporterService 13 | from highlight_butler.usecase.highlight_importer.usecase import \ 14 | HighlightImporter 15 | from highlight_butler.usecase.highlight_renderer.usecase import \ 16 | HighlightRenderer 17 | from highlight_butler.utils.config import Config 18 | from highlight_butler.utils.singleton import Singleton 19 | 20 | 21 | class HighlightImportHandler(metaclass=Singleton): 22 | def __init__(self): 23 | self.config = Config() 24 | 25 | def handle(self): 26 | 27 | # get all importers and renderers from the config 28 | _service_names = self.config.get_value("butler.importers").keys() 29 | services: List[HighlightImporterService] = list( 30 | map(self._import_service, _service_names)) 31 | 32 | highlightDocuments: List[HighlightDocument] = [] 33 | for service in services: 34 | _highlightDocuments: List[highlightDocuments] = service( 35 | ).import_highlights() 36 | highlightDocuments.extend(_highlightDocuments) 37 | 38 | # render all documents 39 | markdownRendererService: MarkdownRendererService = MarkdownRendererService() 40 | highlightRenderer: HighlightRenderer = HighlightRenderer( 41 | markdownRendererService) 42 | 43 | _filenamePrefix = self.config.get_value( 44 | "butler.library.filenamePrefix", "") 45 | for highlightDocument in highlightDocuments: 46 | renderedDocument = highlightRenderer.render_highlight( 47 | highlightDocument) 48 | filename = "{prefix}{title}.md".format( 49 | prefix=_filenamePrefix, title=pydash.strings.replace(highlightDocument.title, re.compile("[ :?]"), "_")) 50 | filepath = Path(self.config.get_value( 51 | "butler.library.dir")).joinpath(filename) 52 | with open(filepath, "w") as f: 53 | f.write(renderedDocument) 54 | 55 | def _import_service(self, service: str): 56 | return { 57 | "hypothesis": HypothesisImporterService 58 | }.get(service) 59 | -------------------------------------------------------------------------------- /highlight_butler/resources/config.yaml: -------------------------------------------------------------------------------- 1 | butler: 2 | importers: 3 | hypothesis: 4 | apikey: "" 5 | tagPrefix: "highlight/" 6 | category: "hypothesis" 7 | groups: 8 | - "my highlight library" 9 | renderer: 10 | markdown: 11 | template: "./highlight_butler/resources/document.md" 12 | meta: 13 | tags: ["highlight"] 14 | category: "highlight" 15 | library: 16 | dir: "./pages/" 17 | filenamePrefix: "highlight_" -------------------------------------------------------------------------------- /highlight_butler/resources/document.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{doc.title}} 3 | created: {{doc.created | prettyDate}} 4 | author: {{doc.author}} 5 | tags: {{doc.tags | join(",")}} 6 | category: {{doc.category}} 7 | url: {{doc.url}} 8 | --- 9 | 10 | ## Summary 11 | 12 | ## Content 13 | {% for highlight in doc.highlights %} 14 | - {{highlight.text}} {{highlight.tags | map('asTag') | join(" ")}} 15 | {% if highlight.annotation %} 16 | - {{highlight.annotation}} 17 | {% endif %} 18 | - #ref {{highlight.location.value}} 19 | {% endfor %} -------------------------------------------------------------------------------- /highlight_butler/service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boot-Error/highlight_butler/b686e9a1c6c58bd74b4601abcd69e8f5a672c24c/highlight_butler/service/__init__.py -------------------------------------------------------------------------------- /highlight_butler/service/hypothesis_importer.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import itertools 3 | from highlight_butler.entities.highlight import Highlight, HighlightDocument, HighlightLocation 4 | from typing import List, Optional 5 | from datetime import timezone, datetime 6 | 7 | import requests 8 | import pydash 9 | from highlight_butler.utils.config import Config 10 | from highlight_butler.utils.singleton import Singleton 11 | from highlight_butler.usecase.highlight_importer.contract import HighlightImporterService 12 | 13 | 14 | class HypothesisConnector(metaclass=Singleton): 15 | def __init__(self): 16 | self.config = Config() 17 | self._apikey = self.config.get_value( 18 | "butler.importers.hypothesis.apikey") 19 | 20 | def _headers(self): 21 | headers = {} 22 | headers.setdefault("Accept", "application/json") 23 | headers.setdefault("Content-Type", "application/json") 24 | headers.setdefault( 25 | "Authorization", "Bearer {apikey}".format(apikey=self._apikey)) 26 | 27 | return headers 28 | 29 | def search_annotation(self, query: dict) -> dict: 30 | 31 | url = "https://api.hypothes.is/api/search" 32 | 33 | try: 34 | r = requests.get(url, headers=self._headers(), params=query) 35 | if r.status_code != 200: 36 | print("Failed to fetch annotations") 37 | return None 38 | return r.json() 39 | except Exception as e: 40 | print(e) 41 | return None 42 | 43 | @functools.lru_cache(maxsize=10) 44 | def group_list(self): 45 | 46 | url = "https://api.hypothes.is/api/groups" 47 | 48 | try: 49 | r = requests.get(url, headers=self._headers()) 50 | if r.status_code != 200: 51 | print("Failed to fetch annotations") 52 | return None 53 | return r.json() 54 | except Exception as e: 55 | print(e) 56 | return None 57 | 58 | 59 | class HypothesisImporterService(HighlightImporterService): 60 | def __init__(self): 61 | self._hypothesis_connector = HypothesisConnector() 62 | self._config = Config() 63 | 64 | def import_highlights(self) -> List[HighlightDocument]: 65 | groups = self._config.get_value("butler.importers.hypothesis.groups") 66 | groupIds = map(self._get_groupid, groups) 67 | _annotations = pydash.collections.flat_map( 68 | groupIds, self._get_all_annotation_by_group) 69 | 70 | annotations_by_uri = itertools.groupby(_annotations, 71 | key=lambda annotation: pydash.get(annotation, "uri")) 72 | 73 | highlightDocuments: List[HighlightDocument] = [] 74 | for url, annotations in annotations_by_uri: 75 | highlightDocument = self._create_highlight_document( 76 | url=url, annotations=list(annotations)) 77 | highlightDocuments.append(highlightDocument) 78 | 79 | return highlightDocuments 80 | 81 | def _create_highlight_document(self, url: str, annotations: List[dict]) -> HighlightDocument: 82 | 83 | # get common values from one annotation 84 | _annotation = pydash.arrays.head(annotations) 85 | _tagPrefix = self._config.get_value("butler.importers.hypothesis.tagPrefix") 86 | 87 | highlightDocument: HighlightDocument = HighlightDocument( 88 | author="Highlight Butler", 89 | created=datetime.utcnow().replace(tzinfo=timezone.utc), 90 | updated=datetime.fromtimestamp(0, timezone.utc), 91 | # TODO: remove improvised array indexing 92 | title=pydash.get(_annotation, "document.title")[0], 93 | url=pydash.get(_annotation, "uri"), 94 | highlights=list(map(self._annotation_to_highlight, annotations)), 95 | category=self._config.get_value( 96 | "butler.importers.hypothesis.category"), 97 | tags=pydash.py_(annotations).map(lambda annotation: pydash.get(annotation, "tags")) 98 | .reduce(lambda allTags, tags: allTags + pydash.map_(tags, lambda t: "#{prefix}{tag}".format(prefix=_tagPrefix, tag=t)), []) 99 | .value() 100 | ) 101 | highlightDocument = self._update_document_time(highlightDocument, annotations) 102 | return highlightDocument 103 | 104 | def _update_document_time(self, highlightDocument: HighlightDocument, annotations: List[dict]) -> HighlightDocument: 105 | """ 106 | Calculates created date and update dates based on heuristics and updates the Highlight Document 107 | 108 | - create is oldest created time of all the highlights 109 | - updated is latest update ttime of all the highlights 110 | """ 111 | created = highlightDocument.created 112 | updated = highlightDocument.updated 113 | for annotation in annotations: 114 | annotation_created = pydash.get(annotation, "created") 115 | annotation_created = self._parse_isodatetime(annotation_created) 116 | if created > annotation_created: 117 | created = annotation_created 118 | 119 | annotation_updated = pydash.get(annotation, "updated") 120 | annotation_updated = self._parse_isodatetime(annotation_updated) 121 | if updated < annotation_updated: 122 | updated = annotation_updated 123 | 124 | highlightDocument.created = created 125 | highlightDocument.updated = updated 126 | 127 | return highlightDocument 128 | 129 | def _parse_isodatetime(self, time: str) -> datetime: 130 | return datetime.strptime(time, '%Y-%m-%dT%H:%M:%S.%f%z') 131 | 132 | def _annotation_to_highlight(self, annotation: dict) -> Highlight: 133 | _tagPrefix = self._config.get_value( 134 | "butler.importers.hypothesis.tagPrefix") 135 | _selector = pydash.get(annotation, "target")[0].get("selector") 136 | _quoter_selector = pydash.collections.find( 137 | _selector, {"type": "TextQuoteSelector"}) 138 | highlight_text = pydash.get(_quoter_selector, "exact") 139 | annotation_text = pydash.get(annotation, "text") 140 | 141 | highlight_location: HighlightLocation = HighlightLocation( 142 | locationIdentifier="url", value=pydash.get(annotation, "links.incontext")) 143 | highlight: Highlight = Highlight( 144 | text=highlight_text, annotation=annotation_text, location=highlight_location, 145 | tags=pydash.map_(pydash.get(annotation, "tags"), lambda tag: _tagPrefix + tag)) 146 | 147 | return highlight 148 | 149 | def _get_all_annotation_by_group(self, groupId): 150 | query = {"group": groupId} 151 | return self._hypothesis_connector.search_annotation(query).get("rows") 152 | 153 | def _get_groupid(self, group_name: str) -> Optional[str]: 154 | groups = self._hypothesis_connector.group_list() 155 | for group in groups: 156 | if group.get("name") == group_name: 157 | return group.get("id") 158 | # TODO: raise exception for invalid group_name 159 | return None 160 | -------------------------------------------------------------------------------- /highlight_butler/service/markdown_renderer.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Optional 3 | 4 | import jinja2 5 | from frontmatter import Frontmatter 6 | from highlight_butler.entities.highlight import Highlight, HighlightDocument 7 | from highlight_butler.usecase.highlight_renderer.contract import \ 8 | HighlightRendererService 9 | from highlight_butler.utils.config import Config 10 | from highlight_butler.utils.singleton import Singleton 11 | 12 | 13 | class Jinja2Renderer: 14 | def __init__(self, template: str): 15 | # setup custom filers 16 | env = jinja2.Environment(lstrip_blocks=True, trim_blocks=True) 17 | env.filters["prettyDate"] = Jinja2Renderer.prettyDateFilter 18 | env.filters["asTag"] = Jinja2Renderer.asTagFilter 19 | # load template 20 | self.template = env.from_string(template) 21 | 22 | def render(self, args: str) -> str: 23 | try: 24 | renderedDocument = self.template.render(args) 25 | return renderedDocument 26 | except Exception as e: 27 | print("Failed to render template due to", e) 28 | return "" 29 | 30 | @staticmethod 31 | def prettyDateFilter(value: str) -> str: 32 | isodt: datetime = value 33 | suffix = 'th' if 11 <= isodt.day <= 13 else { 34 | 1: 'st', 2: 'nd', 3: 'rd'}.get(isodt.day % 10, 'th') 35 | return isodt.strftime("%B {S}, %Y").replace('{S}', str(isodt.day) + suffix) 36 | 37 | @staticmethod 38 | def asTagFilter(value: str) -> str: 39 | return "#{tag}".format(tag=value) 40 | 41 | class MarkdownRendererService(HighlightRendererService): 42 | def __init__(self): 43 | self.config = Config() 44 | 45 | def load_document(self, document: str) -> Optional[HighlightDocument]: 46 | try: 47 | frontmatterData = Frontmatter.read_file(document) 48 | highlightDocument: HighlightDocument = HighlightDocument( 49 | title=frontmatterData.get("title"), 50 | author=frontmatterData.get("author"), 51 | category=frontmatterData.get("category"), 52 | tags=frontmatterData.get("tags"), 53 | created=frontmatterData.get('created'), 54 | url=frontmatterData.get('url'), 55 | highlights=[] 56 | ) 57 | return highlightDocument 58 | 59 | except Exception as e: 60 | print("Failed to load document due to:", e) 61 | return None 62 | 63 | def update_document(self, highlightDocument: HighlightDocument, highlights: List[Highlight]): 64 | return super().update_document(highlightDocument, highlights) 65 | 66 | def render_document(self, highlightDocument: HighlightDocument) -> str: 67 | template = self._load_template() 68 | jinja2renderer: Jinja2Renderer = Jinja2Renderer(template) 69 | return jinja2renderer.render({"doc": highlightDocument.__dict__}) 70 | 71 | def _load_template(self) -> Optional[str]: 72 | template_path = self.config.get_value( 73 | "butler.renderer.markdown.template") 74 | try: 75 | with open(template_path) as f: 76 | template_content = f.read() 77 | return template_content 78 | except Exception as e: 79 | print("Failed to load template", e) 80 | return None 81 | -------------------------------------------------------------------------------- /highlight_butler/usecase/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boot-Error/highlight_butler/b686e9a1c6c58bd74b4601abcd69e8f5a672c24c/highlight_butler/usecase/__init__.py -------------------------------------------------------------------------------- /highlight_butler/usecase/highlight_importer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boot-Error/highlight_butler/b686e9a1c6c58bd74b4601abcd69e8f5a672c24c/highlight_butler/usecase/highlight_importer/__init__.py -------------------------------------------------------------------------------- /highlight_butler/usecase/highlight_importer/contract.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from highlight_butler.entities.highlight import HighlightDocument 4 | 5 | 6 | class HighlightImporterService: 7 | def __init__(self) -> None: 8 | pass 9 | def import_highlights(self) -> List[HighlightDocument]: 10 | raise NotImplementedError() 11 | -------------------------------------------------------------------------------- /highlight_butler/usecase/highlight_importer/usecase.py: -------------------------------------------------------------------------------- 1 | from highlight_butler.entities.highlight import HighlightDocument 2 | from typing import List, Optional 3 | from highlight_butler.usecase.highlight_importer.contract import HighlightImporterService 4 | 5 | 6 | class HighlightImporter: 7 | def __init__(self, highlightImporterService: HighlightImporterService): 8 | self._service = highlightImporterService() 9 | 10 | def import_highlight(self, params: Optional[dict] = None) -> List[HighlightDocument]: 11 | return self._service.import_highlights() -------------------------------------------------------------------------------- /highlight_butler/usecase/highlight_renderer/contract.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from highlight_butler.entities.highlight import Highlight, HighlightDocument 3 | 4 | class HighlightRendererService: 5 | def load_document(self, document: str) -> HighlightDocument: 6 | raise NotImplementedError() 7 | 8 | def update_document(self, highlightDocument: HighlightDocument, highlights: List[Highlight]): 9 | raise NotImplementedError() 10 | 11 | def render_document(self, highlightDocument: HighlightDocument) -> str: 12 | raise NotImplementedError() -------------------------------------------------------------------------------- /highlight_butler/usecase/highlight_renderer/usecase.py: -------------------------------------------------------------------------------- 1 | from highlight_butler.entities.highlight import HighlightDocument 2 | from highlight_butler.usecase.highlight_renderer.contract import HighlightRendererService 3 | 4 | 5 | class HighlightRenderer: 6 | def __init__(self, service: HighlightRendererService): 7 | self._service: HighlightRendererService = service 8 | 9 | def render_highlight(self, document: HighlightDocument) -> str: 10 | rendered_document = self._service.render_document(document) 11 | return rendered_document -------------------------------------------------------------------------------- /highlight_butler/utils/config.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List 2 | import yaml 3 | 4 | from highlight_butler.utils.singleton import Singleton 5 | 6 | class Config(metaclass=Singleton): 7 | def __init__(self): 8 | self.config = {} 9 | 10 | def load(self, config_data): 11 | try: 12 | self.config = yaml.full_load(config_data) 13 | except Exception as e: 14 | print(e) 15 | 16 | def get_value(self, query: Any, default=None): 17 | value = self.config 18 | if type(query) == str: 19 | query = query.split(".") 20 | while len(query) > 0: 21 | k = query.pop(0) 22 | value = value.get(k) 23 | if value == None: 24 | return default 25 | 26 | return value -------------------------------------------------------------------------------- /highlight_butler/utils/singleton.py: -------------------------------------------------------------------------------- 1 | class Singleton(type): 2 | _instances = {} 3 | def __call__(cls, *args, **kwargs): 4 | if cls not in cls._instances: 5 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 6 | return cls._instances[cls] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.25.1 2 | Jinja2==3.0.1 3 | pydash==5.0.1 4 | PyYAML==5.1 5 | frontmatter==3.0.7 6 | marko==1.0.3 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | def get_dependencies(): 4 | with open("requirements.txt") as f: 5 | dependencies = f.read() 6 | return dependencies.split("\n") 7 | 8 | setup(name='highlight_butler-Boot-Error', 9 | version='0.1', 10 | description='Highlight Butler collects highlights from services and create notes in your note-taking systems', 11 | url='http://github.com/Boot-Error/highlight_butler', 12 | author='Boot-Error', 13 | author_email='booterror99@gmail.com', 14 | license='MIT', 15 | packages=[ 16 | 'highlight_butler', 17 | 'highlight_butler.app', 18 | 'highlight_butler.entities', 19 | 'highlight_butler.service', 20 | 'highlight_butler.usecase', 21 | 'highlight_butler.usecase.highlight_renderer', 22 | 'highlight_butler.usecase.highlight_importer', 23 | 'highlight_butler.utils', 24 | 'highlight_butler.handlers', 25 | ], 26 | zip_safe=False, 27 | install_requires=get_dependencies(), 28 | entry_points={ 29 | 'console_scripts': ['highlight_butler=highlight_butler.app.cli:main']}) --------------------------------------------------------------------------------