diff --git a/doi2dataset/__init__.py b/doi2dataset/__init__.py index b23e9f9..1c260d7 100644 --- a/doi2dataset/__init__.py +++ b/doi2dataset/__init__.py @@ -51,6 +51,7 @@ from .core import ( Person, PrimitiveMetadataField, ) +from .core.constants import ICONS from .processing import ( CitationBuilder, MetadataProcessor, @@ -80,6 +81,8 @@ __all__ = [ "Institution", "License", "Abstract", + # Constants + "ICONS", # Metadata fields "BaseMetadataField", "PrimitiveMetadataField", diff --git a/doi2dataset/api/processors.py b/doi2dataset/api/processors.py index d94cd57..a117366 100644 --- a/doi2dataset/api/processors.py +++ b/doi2dataset/api/processors.py @@ -10,6 +10,7 @@ from typing import Any from rich.console import Console +from ..core.constants import ICONS from ..core.models import Abstract, License from .client import APIClient @@ -66,9 +67,6 @@ class AbstractProcessor: Retrieves and processes abstracts from CrossRef and OpenAlex. """ - # Icons for console output - TODO: should be moved to a constants module - ICONS = {"info": "ℹ️", "warning": "⚠️", "error": "❌"} - def __init__(self, api_client: APIClient, console: Console | None = None): """ Initialize with an APIClient instance. @@ -98,7 +96,7 @@ class AbstractProcessor: if license.short in license_ok: self.console.print( - f"\n{self.ICONS['info']} License {license.name} allows derivative works. Pulling abstract from CrossRef.", + f"\n{ICONS['info']} License {license.name} allows derivative works. Pulling abstract from CrossRef.", style="info", ) crossref_abstract = self._get_crossref_abstract(doi) @@ -106,18 +104,18 @@ class AbstractProcessor: return Abstract(text=crossref_abstract, source="crossref") else: self.console.print( - f"\n{self.ICONS['warning']} No abstract found in CrossRef!", + f"\n{ICONS['warning']} No abstract found in CrossRef!", style="warning", ) else: if license.name: self.console.print( - f"\n{self.ICONS['info']} License {license.name} does not allow derivative works. Reconstructing abstract from OpenAlex!", + f"\n{ICONS['info']} License {license.name} does not allow derivative works. Reconstructing abstract from OpenAlex!", style="info", ) else: self.console.print( - f"\n{self.ICONS['info']} Custom license does not allow derivative works. Reconstructing abstract from OpenAlex!", + f"\n{ICONS['info']} Custom license does not allow derivative works. Reconstructing abstract from OpenAlex!", style="info", ) @@ -126,12 +124,12 @@ class AbstractProcessor: return Abstract(text=openalex_abstract, source="openalex") else: self.console.print( - f"\n{self.ICONS['warning']} No abstract found in OpenAlex!", + f"\n{ICONS['warning']} No abstract found in OpenAlex!", style="warning", ) self.console.print( - f"\n{self.ICONS['warning']} No abstract found in either CrossRef nor OpenAlex!", + f"\n{ICONS['warning']} No abstract found in either CrossRef nor OpenAlex!", style="warning", ) return Abstract(text="", source="none") diff --git a/doi2dataset/cli.py b/doi2dataset/cli.py index d092be3..6137b4a 100644 --- a/doi2dataset/cli.py +++ b/doi2dataset/cli.py @@ -23,28 +23,10 @@ from rich.progress import ( from rich.table import Table from rich.theme import Theme +from .core.constants import ICONS from .processing.metadata import MetadataProcessor from .utils.validation import normalize_doi, sanitize_filename, validate_email_address -# Console icons for user-friendly output -ICONS = { - "success": "✓", # Simple checkmark - "error": "✗", # Simple X - "warning": "!", # Simple exclamation - "info": "ℹ", # Info symbol - "processing": "⋯", # Three dots - "done": "∎", # Filled square - "file": "⨳", # Document symbol - "folder": "⊞", # Folder symbol - "clock": "◷", # Clock symbol - "search": "⌕", # Search symbol - "data": "≡", # Three lines - "doi": "∾", # Link symbol - "total": "∑", # Sum symbol - "save": "⤓", # Save/download arrow - "upload": "⤒", # Upload arrow -} - # Theme configuration for Rich console output THEME = Theme( { diff --git a/doi2dataset/core/constants.py b/doi2dataset/core/constants.py new file mode 100644 index 0000000..dacbe48 --- /dev/null +++ b/doi2dataset/core/constants.py @@ -0,0 +1,43 @@ +""" +Constants for doi2dataset. + +This module contains shared constants used across the application, +including console icons and other configuration values. +""" + +# Console icons for user-friendly output +ICONS = { + # Status indicators + "success": "✓", # Simple checkmark + "error": "✗", # Simple X / ❌ (emoji alternative) + "warning": "!", # Simple exclamation / ⚠️ (emoji alternative) + "info": "ℹ", # Info symbol / ℹ️ (emoji alternative) + # Process indicators + "processing": "⋯", # Three dots / ⚙️ (emoji alternative) + "done": "∎", # Filled square + # File/data indicators + "file": "⨳", # Document symbol + "folder": "⊞", # Folder symbol + "save": "⤓", # Save/download arrow + "upload": "⤒", # Upload arrow + # UI elements + "clock": "◷", # Clock symbol + "search": "⌕", # Search symbol + "data": "≡", # Three lines + "doi": "∾", # Link symbol + "total": "∑", # Sum symbol +} + +# Alternative emoji-based icons for better visibility in some terminals +EMOJI_ICONS = { + "success": "✅", + "error": "❌", + "warning": "⚠️", + "info": "ℹ️", + "processing": "⚙️", + "upload": "📤", + "save": "💾", +} + +# Default icon set preference +DEFAULT_ICONS = ICONS diff --git a/doi2dataset/processing/metadata.py b/doi2dataset/processing/metadata.py index b38bc21..e0e1a55 100644 --- a/doi2dataset/processing/metadata.py +++ b/doi2dataset/processing/metadata.py @@ -16,6 +16,7 @@ from rich.progress import Progress, TaskID from ..api.client import APIClient from ..api.processors import AbstractProcessor, LicenseProcessor from ..core.config import Config +from ..core.constants import ICONS from ..core.metadata_fields import ( CompoundMetadataField, ControlledVocabularyMetadataField, @@ -41,17 +42,6 @@ class MetadataProcessor: building metadata blocks, and optionally uploading the dataset. """ - # Icons for console output - TODO: should be moved to a constants module - ICONS = { - "processing": "⚙️", - "success": "✅", - "error": "❌", - "warning": "⚠️", - "info": "ℹ️", - "upload": "📤", - "save": "💾", - } - def __init__( self, doi: str, @@ -131,7 +121,7 @@ class MetadataProcessor: dict[str, Any]: The constructed metadata dictionary. """ self.console.print( - f"{self.ICONS['processing']} Processing DOI: {self.doi}", style="info" + f"{ICONS['processing']} Processing DOI: {self.doi}", style="info" ) data = self._fetch_data() @@ -148,7 +138,7 @@ class MetadataProcessor: self._update_progress() self.console.print( - f"\n{self.ICONS['success']} Successfully processed: {self.doi}\n", + f"\n{ICONS['success']} Successfully processed: {self.doi}\n", style="success", ) return metadata @@ -177,14 +167,14 @@ class MetadataProcessor: if response is None or response.status_code != 201: self.console.print( - f"\n{self.ICONS['error']} Failed to upload to Dataverse: {url}", + f"\n{ICONS['error']} Failed to upload to Dataverse: {url}", style="error", ) raise ValueError(f"Failed to upload to Dataverse: {url}") else: perma = response.json().get("data", {}).get("persistentId", "") self.console.print( - f"{self.ICONS['upload']} Dataset uploaded to: {config.DATAVERSE['dataverse']} with ID {perma}", + f"{ICONS['upload']} Dataset uploaded to: {config.DATAVERSE['dataverse']} with ID {perma}", style="info", ) @@ -205,7 +195,7 @@ class MetadataProcessor: if response is None or response.status_code != 200: self.console.print( - f"\n{self.ICONS['error']} Failed to fetch data for DOI: {self.doi}", + f"\n{ICONS['error']} Failed to fetch data for DOI: {self.doi}", style="error", ) raise ValueError(f"Failed to fetch data for DOI: {self.doi}") @@ -238,7 +228,7 @@ class MetadataProcessor: if not corresponding_authors: self.console.print( - f"{self.ICONS['warning']} No corresponding authors explicitly declared; PIs are used as a fallback!", + f"{ICONS['warning']} No corresponding authors explicitly declared; PIs are used as a fallback!", style="warning", ) pis = self._get_involved_pis(data) @@ -371,7 +361,7 @@ class MetadataProcessor: return f"

This {doc_type} was published on {publication_date} in {journal}

" self.console.print( - f"{self.ICONS['warning']} No abstract header added, missing information (journal, publication date and/or document type)", + f"{ICONS['warning']} No abstract header added, missing information (journal, publication date and/or document type)", style="warning", ) return "" @@ -461,12 +451,12 @@ class MetadataProcessor: metadata, f, indent=4, ensure_ascii=False, cls=CustomEncoder ) self.console.print( - f"{self.ICONS['save']} Metadata saved in: {self.output_path}", + f"{ICONS['save']} Metadata saved in: {self.output_path}", style="info", ) except Exception as e: self.console.print( - f"{self.ICONS['error']} Error saving metadata: {str(e)}\n", + f"{ICONS['error']} Error saving metadata: {str(e)}\n", style="error", ) raise