refactor: centralize ICONS in constants module
- create core/constants.py with centralized ICONS definition - remove duplicate ICONS definitions from multiple modules - add emoji alternatives for better terminal support - update all modules to use centralized constants - export ICONS from package __init__.py This eliminates code duplication and provides a single source of truth for UI icons used throughout the application.
This commit is contained in:
parent
0f74e5dab2
commit
93d54ebc61
5 changed files with 64 additions and 48 deletions
|
@ -51,6 +51,7 @@ from .core import (
|
||||||
Person,
|
Person,
|
||||||
PrimitiveMetadataField,
|
PrimitiveMetadataField,
|
||||||
)
|
)
|
||||||
|
from .core.constants import ICONS
|
||||||
from .processing import (
|
from .processing import (
|
||||||
CitationBuilder,
|
CitationBuilder,
|
||||||
MetadataProcessor,
|
MetadataProcessor,
|
||||||
|
@ -80,6 +81,8 @@ __all__ = [
|
||||||
"Institution",
|
"Institution",
|
||||||
"License",
|
"License",
|
||||||
"Abstract",
|
"Abstract",
|
||||||
|
# Constants
|
||||||
|
"ICONS",
|
||||||
# Metadata fields
|
# Metadata fields
|
||||||
"BaseMetadataField",
|
"BaseMetadataField",
|
||||||
"PrimitiveMetadataField",
|
"PrimitiveMetadataField",
|
||||||
|
|
|
@ -10,6 +10,7 @@ from typing import Any
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
|
from ..core.constants import ICONS
|
||||||
from ..core.models import Abstract, License
|
from ..core.models import Abstract, License
|
||||||
from .client import APIClient
|
from .client import APIClient
|
||||||
|
|
||||||
|
@ -66,9 +67,6 @@ class AbstractProcessor:
|
||||||
Retrieves and processes abstracts from CrossRef and OpenAlex.
|
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):
|
def __init__(self, api_client: APIClient, console: Console | None = None):
|
||||||
"""
|
"""
|
||||||
Initialize with an APIClient instance.
|
Initialize with an APIClient instance.
|
||||||
|
@ -98,7 +96,7 @@ class AbstractProcessor:
|
||||||
|
|
||||||
if license.short in license_ok:
|
if license.short in license_ok:
|
||||||
self.console.print(
|
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",
|
style="info",
|
||||||
)
|
)
|
||||||
crossref_abstract = self._get_crossref_abstract(doi)
|
crossref_abstract = self._get_crossref_abstract(doi)
|
||||||
|
@ -106,18 +104,18 @@ class AbstractProcessor:
|
||||||
return Abstract(text=crossref_abstract, source="crossref")
|
return Abstract(text=crossref_abstract, source="crossref")
|
||||||
else:
|
else:
|
||||||
self.console.print(
|
self.console.print(
|
||||||
f"\n{self.ICONS['warning']} No abstract found in CrossRef!",
|
f"\n{ICONS['warning']} No abstract found in CrossRef!",
|
||||||
style="warning",
|
style="warning",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if license.name:
|
if license.name:
|
||||||
self.console.print(
|
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",
|
style="info",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.console.print(
|
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",
|
style="info",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -126,12 +124,12 @@ class AbstractProcessor:
|
||||||
return Abstract(text=openalex_abstract, source="openalex")
|
return Abstract(text=openalex_abstract, source="openalex")
|
||||||
else:
|
else:
|
||||||
self.console.print(
|
self.console.print(
|
||||||
f"\n{self.ICONS['warning']} No abstract found in OpenAlex!",
|
f"\n{ICONS['warning']} No abstract found in OpenAlex!",
|
||||||
style="warning",
|
style="warning",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.console.print(
|
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",
|
style="warning",
|
||||||
)
|
)
|
||||||
return Abstract(text="", source="none")
|
return Abstract(text="", source="none")
|
||||||
|
|
|
@ -23,28 +23,10 @@ from rich.progress import (
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from rich.theme import Theme
|
from rich.theme import Theme
|
||||||
|
|
||||||
|
from .core.constants import ICONS
|
||||||
from .processing.metadata import MetadataProcessor
|
from .processing.metadata import MetadataProcessor
|
||||||
from .utils.validation import normalize_doi, sanitize_filename, validate_email_address
|
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 configuration for Rich console output
|
||||||
THEME = Theme(
|
THEME = Theme(
|
||||||
{
|
{
|
||||||
|
|
43
doi2dataset/core/constants.py
Normal file
43
doi2dataset/core/constants.py
Normal file
|
@ -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
|
|
@ -16,6 +16,7 @@ from rich.progress import Progress, TaskID
|
||||||
from ..api.client import APIClient
|
from ..api.client import APIClient
|
||||||
from ..api.processors import AbstractProcessor, LicenseProcessor
|
from ..api.processors import AbstractProcessor, LicenseProcessor
|
||||||
from ..core.config import Config
|
from ..core.config import Config
|
||||||
|
from ..core.constants import ICONS
|
||||||
from ..core.metadata_fields import (
|
from ..core.metadata_fields import (
|
||||||
CompoundMetadataField,
|
CompoundMetadataField,
|
||||||
ControlledVocabularyMetadataField,
|
ControlledVocabularyMetadataField,
|
||||||
|
@ -41,17 +42,6 @@ class MetadataProcessor:
|
||||||
building metadata blocks, and optionally uploading the dataset.
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
doi: str,
|
doi: str,
|
||||||
|
@ -131,7 +121,7 @@ class MetadataProcessor:
|
||||||
dict[str, Any]: The constructed metadata dictionary.
|
dict[str, Any]: The constructed metadata dictionary.
|
||||||
"""
|
"""
|
||||||
self.console.print(
|
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()
|
data = self._fetch_data()
|
||||||
|
@ -148,7 +138,7 @@ class MetadataProcessor:
|
||||||
self._update_progress()
|
self._update_progress()
|
||||||
|
|
||||||
self.console.print(
|
self.console.print(
|
||||||
f"\n{self.ICONS['success']} Successfully processed: {self.doi}\n",
|
f"\n{ICONS['success']} Successfully processed: {self.doi}\n",
|
||||||
style="success",
|
style="success",
|
||||||
)
|
)
|
||||||
return metadata
|
return metadata
|
||||||
|
@ -177,14 +167,14 @@ class MetadataProcessor:
|
||||||
|
|
||||||
if response is None or response.status_code != 201:
|
if response is None or response.status_code != 201:
|
||||||
self.console.print(
|
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",
|
style="error",
|
||||||
)
|
)
|
||||||
raise ValueError(f"Failed to upload to Dataverse: {url}")
|
raise ValueError(f"Failed to upload to Dataverse: {url}")
|
||||||
else:
|
else:
|
||||||
perma = response.json().get("data", {}).get("persistentId", "")
|
perma = response.json().get("data", {}).get("persistentId", "")
|
||||||
self.console.print(
|
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",
|
style="info",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -205,7 +195,7 @@ class MetadataProcessor:
|
||||||
|
|
||||||
if response is None or response.status_code != 200:
|
if response is None or response.status_code != 200:
|
||||||
self.console.print(
|
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",
|
style="error",
|
||||||
)
|
)
|
||||||
raise ValueError(f"Failed to fetch data for DOI: {self.doi}")
|
raise ValueError(f"Failed to fetch data for DOI: {self.doi}")
|
||||||
|
@ -238,7 +228,7 @@ class MetadataProcessor:
|
||||||
|
|
||||||
if not corresponding_authors:
|
if not corresponding_authors:
|
||||||
self.console.print(
|
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",
|
style="warning",
|
||||||
)
|
)
|
||||||
pis = self._get_involved_pis(data)
|
pis = self._get_involved_pis(data)
|
||||||
|
@ -371,7 +361,7 @@ class MetadataProcessor:
|
||||||
return f"<p>This {doc_type} was published on {publication_date} in <i>{journal}</i></p>"
|
return f"<p>This {doc_type} was published on {publication_date} in <i>{journal}</i></p>"
|
||||||
|
|
||||||
self.console.print(
|
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",
|
style="warning",
|
||||||
)
|
)
|
||||||
return ""
|
return ""
|
||||||
|
@ -461,12 +451,12 @@ class MetadataProcessor:
|
||||||
metadata, f, indent=4, ensure_ascii=False, cls=CustomEncoder
|
metadata, f, indent=4, ensure_ascii=False, cls=CustomEncoder
|
||||||
)
|
)
|
||||||
self.console.print(
|
self.console.print(
|
||||||
f"{self.ICONS['save']} Metadata saved in: {self.output_path}",
|
f"{ICONS['save']} Metadata saved in: {self.output_path}",
|
||||||
style="info",
|
style="info",
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.console.print(
|
self.console.print(
|
||||||
f"{self.ICONS['error']} Error saving metadata: {str(e)}\n",
|
f"{ICONS['error']} Error saving metadata: {str(e)}\n",
|
||||||
style="error",
|
style="error",
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue