pub-analyzer 0.5.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pub_analyzer/__init__.py +1 -0
- pub_analyzer/__main__.py +7 -0
- pub_analyzer/css/body.tcss +87 -0
- pub_analyzer/css/buttons.tcss +24 -0
- pub_analyzer/css/checkbox.tcss +29 -0
- pub_analyzer/css/collapsible.tcss +31 -0
- pub_analyzer/css/datatable.tcss +50 -0
- pub_analyzer/css/editor.tcss +60 -0
- pub_analyzer/css/main.tcss +50 -0
- pub_analyzer/css/report.tcss +131 -0
- pub_analyzer/css/search.tcss +81 -0
- pub_analyzer/css/summary.tcss +75 -0
- pub_analyzer/css/tabs.tcss +18 -0
- pub_analyzer/css/tree.tcss +44 -0
- pub_analyzer/internal/__init__.py +1 -0
- pub_analyzer/internal/identifier.py +106 -0
- pub_analyzer/internal/limiter.py +34 -0
- pub_analyzer/internal/render.py +41 -0
- pub_analyzer/internal/report.py +497 -0
- pub_analyzer/internal/templates/author_report.typ +591 -0
- pub_analyzer/main.py +81 -0
- pub_analyzer/models/__init__.py +1 -0
- pub_analyzer/models/author.py +87 -0
- pub_analyzer/models/concept.py +19 -0
- pub_analyzer/models/institution.py +138 -0
- pub_analyzer/models/report.py +111 -0
- pub_analyzer/models/source.py +77 -0
- pub_analyzer/models/topic.py +59 -0
- pub_analyzer/models/work.py +158 -0
- pub_analyzer/widgets/__init__.py +1 -0
- pub_analyzer/widgets/author/__init__.py +1 -0
- pub_analyzer/widgets/author/cards.py +65 -0
- pub_analyzer/widgets/author/core.py +122 -0
- pub_analyzer/widgets/author/tables.py +50 -0
- pub_analyzer/widgets/body.py +55 -0
- pub_analyzer/widgets/common/__init__.py +18 -0
- pub_analyzer/widgets/common/card.py +29 -0
- pub_analyzer/widgets/common/filesystem.py +203 -0
- pub_analyzer/widgets/common/filters.py +111 -0
- pub_analyzer/widgets/common/input.py +97 -0
- pub_analyzer/widgets/common/label.py +36 -0
- pub_analyzer/widgets/common/modal.py +43 -0
- pub_analyzer/widgets/common/selector.py +66 -0
- pub_analyzer/widgets/common/summary.py +7 -0
- pub_analyzer/widgets/institution/__init__.py +1 -0
- pub_analyzer/widgets/institution/cards.py +78 -0
- pub_analyzer/widgets/institution/core.py +122 -0
- pub_analyzer/widgets/institution/tables.py +24 -0
- pub_analyzer/widgets/report/__init__.py +1 -0
- pub_analyzer/widgets/report/author.py +43 -0
- pub_analyzer/widgets/report/cards.py +130 -0
- pub_analyzer/widgets/report/concept.py +47 -0
- pub_analyzer/widgets/report/core.py +308 -0
- pub_analyzer/widgets/report/editor.py +80 -0
- pub_analyzer/widgets/report/export.py +112 -0
- pub_analyzer/widgets/report/grants.py +85 -0
- pub_analyzer/widgets/report/institution.py +39 -0
- pub_analyzer/widgets/report/locations.py +75 -0
- pub_analyzer/widgets/report/source.py +90 -0
- pub_analyzer/widgets/report/topic.py +55 -0
- pub_analyzer/widgets/report/work.py +391 -0
- pub_analyzer/widgets/search/__init__.py +11 -0
- pub_analyzer/widgets/search/core.py +96 -0
- pub_analyzer/widgets/search/results.py +82 -0
- pub_analyzer/widgets/sidebar.py +70 -0
- pub_analyzer-0.5.6.dist-info/METADATA +102 -0
- pub_analyzer-0.5.6.dist-info/RECORD +70 -0
- pub_analyzer-0.5.6.dist-info/WHEEL +4 -0
- pub_analyzer-0.5.6.dist-info/entry_points.txt +3 -0
- pub_analyzer-0.5.6.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Searchbar widget."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from pydantic import TypeAdapter
|
|
7
|
+
from textual import on
|
|
8
|
+
from textual.app import ComposeResult
|
|
9
|
+
from textual.containers import Horizontal, VerticalScroll
|
|
10
|
+
from textual.widgets import Static
|
|
11
|
+
|
|
12
|
+
from pub_analyzer.models.author import AuthorResult
|
|
13
|
+
from pub_analyzer.models.institution import InstitutionResult
|
|
14
|
+
from pub_analyzer.widgets.common import Input, Select
|
|
15
|
+
|
|
16
|
+
from .results import AuthorResultWidget, InstitutionResultWidget
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SearchBar(Input):
|
|
20
|
+
"""SearchBar."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FinderWidget(Static):
|
|
24
|
+
"""Search in Open Alex API as-you-type Widget."""
|
|
25
|
+
|
|
26
|
+
class OpenAlexEndPoint(Enum):
|
|
27
|
+
"""OpenAlex Endpoints."""
|
|
28
|
+
|
|
29
|
+
AUTHOR = "https://api.openalex.org/autocomplete/authors?author_hint=institution"
|
|
30
|
+
INSTITUTION = "https://api.openalex.org/autocomplete/institutions?"
|
|
31
|
+
|
|
32
|
+
class EntityTypeSelector(Select[OpenAlexEndPoint]):
|
|
33
|
+
"""Entity_type Selector."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, url: OpenAlexEndPoint = OpenAlexEndPoint.AUTHOR) -> None:
|
|
36
|
+
self.url = url
|
|
37
|
+
super().__init__()
|
|
38
|
+
|
|
39
|
+
def compose(self) -> ComposeResult:
|
|
40
|
+
"""Generate an input field and displays the results."""
|
|
41
|
+
entity_options = [(name.title(), endpoint) for name, endpoint in self.OpenAlexEndPoint.__members__.items()]
|
|
42
|
+
|
|
43
|
+
with Horizontal(classes="searchbar-container"):
|
|
44
|
+
yield SearchBar(placeholder="Search author")
|
|
45
|
+
yield self.EntityTypeSelector(options=entity_options, value=self.OpenAlexEndPoint.AUTHOR, allow_blank=False)
|
|
46
|
+
yield VerticalScroll(id="results-container")
|
|
47
|
+
|
|
48
|
+
async def lookup(self, input: str) -> None:
|
|
49
|
+
"""Search in OpenAlex API."""
|
|
50
|
+
async with httpx.AsyncClient() as client:
|
|
51
|
+
url = self.url.value + f"&q={input}"
|
|
52
|
+
response = (await client.get(url)).json().get("results")
|
|
53
|
+
|
|
54
|
+
if input == self.query_one(Input).value:
|
|
55
|
+
# Clear the results
|
|
56
|
+
await self.query("#results-container > *").remove()
|
|
57
|
+
|
|
58
|
+
match self.url:
|
|
59
|
+
case self.OpenAlexEndPoint.AUTHOR:
|
|
60
|
+
author_results: list[AuthorResult] = TypeAdapter(list[AuthorResult]).validate_python(response)
|
|
61
|
+
for author_result in author_results:
|
|
62
|
+
await self.query_one("#results-container").mount(AuthorResultWidget(author_result))
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
case self.OpenAlexEndPoint.INSTITUTION:
|
|
66
|
+
institution_results: list[InstitutionResult] = TypeAdapter(list[InstitutionResult]).validate_python(response)
|
|
67
|
+
for institution_result in institution_results:
|
|
68
|
+
await self.query_one("#results-container").mount(InstitutionResultWidget(institution_result))
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
@on(Input.Changed)
|
|
72
|
+
async def on_type(self, event: Input.Changed) -> None:
|
|
73
|
+
"""Coroutine to handle search input."""
|
|
74
|
+
if event.value:
|
|
75
|
+
# Look up the author in the background
|
|
76
|
+
self.run_worker(self.lookup(event.value), exclusive=True)
|
|
77
|
+
else:
|
|
78
|
+
# Clear the results
|
|
79
|
+
await self.query("#results-container > *").remove()
|
|
80
|
+
|
|
81
|
+
@on(Select.Changed)
|
|
82
|
+
async def on_select_entity(self, event: Select.Changed) -> None:
|
|
83
|
+
"""Change entity endpoint."""
|
|
84
|
+
search_bar = self.query_one(SearchBar)
|
|
85
|
+
match event.value:
|
|
86
|
+
case self.OpenAlexEndPoint.AUTHOR:
|
|
87
|
+
self.url = self.OpenAlexEndPoint.AUTHOR
|
|
88
|
+
search_bar.placeholder = "Search author"
|
|
89
|
+
case self.OpenAlexEndPoint.INSTITUTION:
|
|
90
|
+
self.url = self.OpenAlexEndPoint.INSTITUTION
|
|
91
|
+
search_bar.placeholder = "Search institution"
|
|
92
|
+
case _:
|
|
93
|
+
self.url = self.OpenAlexEndPoint.AUTHOR
|
|
94
|
+
search_bar.placeholder = "Search author"
|
|
95
|
+
|
|
96
|
+
search_bar.value = ""
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Module that allows searching for authors using OpenAlex."""
|
|
2
|
+
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
5
|
+
from textual.app import ComposeResult
|
|
6
|
+
from textual.containers import Horizontal, Vertical
|
|
7
|
+
from textual.widgets import Button, Label, Static
|
|
8
|
+
|
|
9
|
+
from pub_analyzer.models.author import AuthorResult
|
|
10
|
+
from pub_analyzer.models.institution import InstitutionResult
|
|
11
|
+
from pub_analyzer.widgets.author.core import AuthorSummaryWidget
|
|
12
|
+
from pub_analyzer.widgets.institution.core import InstitutionSummaryWidget
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ResultWidget(Static):
|
|
16
|
+
"""Result Widget."""
|
|
17
|
+
|
|
18
|
+
DEFAULT_CLASSES = "result-widget"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AuthorResultWidget(ResultWidget):
|
|
22
|
+
"""Author result widget."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, author_result: AuthorResult) -> None:
|
|
25
|
+
self.author_result = author_result
|
|
26
|
+
super().__init__()
|
|
27
|
+
|
|
28
|
+
def compose(self) -> ComposeResult:
|
|
29
|
+
"""Compose Author result widget."""
|
|
30
|
+
orcid_link = self.author_result.external_id
|
|
31
|
+
|
|
32
|
+
yield Button(label=self.author_result.display_name)
|
|
33
|
+
with Vertical(classes="vertical-content"):
|
|
34
|
+
# Main info
|
|
35
|
+
with Horizontal(classes="main-info-container"):
|
|
36
|
+
yield Label(f"[bold]Cited by count:[/bold] {self.author_result.cited_by_count}", classes="cited-by-count")
|
|
37
|
+
yield Label(f"[bold]Works count:[/bold] {self.author_result.works_count}", classes="works-count")
|
|
38
|
+
|
|
39
|
+
if orcid_link:
|
|
40
|
+
yield Label(f"""[@click=app.open_link('{quote(str(orcid_link))}')]ORCID[/]""", classes="external-id")
|
|
41
|
+
else:
|
|
42
|
+
yield Label(f"""[@click=app.open_link('{quote(str(self.author_result.id))}')]OpenAlexID[/]""", classes="external-id")
|
|
43
|
+
|
|
44
|
+
# Author hint
|
|
45
|
+
yield Label(self.author_result.hint or "", classes="text-hint")
|
|
46
|
+
|
|
47
|
+
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
48
|
+
"""Go to the Author summary page."""
|
|
49
|
+
from pub_analyzer.widgets.body import MainContent
|
|
50
|
+
|
|
51
|
+
author_summary_widget = AuthorSummaryWidget(author_result=self.author_result)
|
|
52
|
+
self.post_message(MainContent.UpdateMainContent(new_widget=author_summary_widget, title=self.author_result.display_name))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class InstitutionResultWidget(ResultWidget):
|
|
56
|
+
"""Institution result widget."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, institution_result: InstitutionResult) -> None:
|
|
59
|
+
self.institution_result = institution_result
|
|
60
|
+
super().__init__()
|
|
61
|
+
|
|
62
|
+
def compose(self) -> ComposeResult:
|
|
63
|
+
"""Compose Institution result widget."""
|
|
64
|
+
yield Button(label=self.institution_result.display_name)
|
|
65
|
+
with Vertical(classes="vertical-content"):
|
|
66
|
+
# Main info
|
|
67
|
+
with Horizontal(classes="main-info-container"):
|
|
68
|
+
external_id = self.institution_result.external_id or self.institution_result.id
|
|
69
|
+
|
|
70
|
+
yield Label(f"[bold]Cited by count:[/bold] {self.institution_result.cited_by_count}", classes="cited-by-count")
|
|
71
|
+
yield Label(f"[bold]Works count:[/bold] {self.institution_result.works_count}", classes="works-count")
|
|
72
|
+
yield Label(f"""[@click=app.open_link('{quote(str(external_id))}')]External ID[/]""", classes="external-id")
|
|
73
|
+
|
|
74
|
+
# Institution hint
|
|
75
|
+
yield Label(self.institution_result.hint or "", classes="text-hint")
|
|
76
|
+
|
|
77
|
+
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
78
|
+
"""Go to the Institution summary page."""
|
|
79
|
+
from pub_analyzer.widgets.body import MainContent
|
|
80
|
+
|
|
81
|
+
institution_summary_widget = InstitutionSummaryWidget(institution_result=self.institution_result)
|
|
82
|
+
self.post_message(MainContent.UpdateMainContent(new_widget=institution_summary_widget, title=self.institution_result.display_name))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Sidebar components and options."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from importlib.metadata import version
|
|
5
|
+
|
|
6
|
+
from textual import on
|
|
7
|
+
from textual.app import ComposeResult
|
|
8
|
+
from textual.containers import Vertical
|
|
9
|
+
from textual.widget import Widget
|
|
10
|
+
from textual.widgets import Button, Label, Static
|
|
11
|
+
|
|
12
|
+
from pub_analyzer.widgets.report.core import LoadReportWidget
|
|
13
|
+
from pub_analyzer.widgets.search import FinderWidget
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SideBarOptionsName(Enum):
|
|
17
|
+
"""List of existing Tabs titles."""
|
|
18
|
+
|
|
19
|
+
SEARCH = "Search"
|
|
20
|
+
LOAD_REPORT = "Load report"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SideBar(Static):
|
|
24
|
+
"""SideBar Widget."""
|
|
25
|
+
|
|
26
|
+
DEFAULT_CLASSES = "sidebar"
|
|
27
|
+
|
|
28
|
+
def compose(self) -> ComposeResult:
|
|
29
|
+
"""Compose dynamically the sidebar options."""
|
|
30
|
+
pub_analyzer_version = version("pub-analyzer")
|
|
31
|
+
|
|
32
|
+
with Vertical(classes="sidebar-options-column"):
|
|
33
|
+
yield Label("Menu", id="sidebar-title")
|
|
34
|
+
|
|
35
|
+
with Vertical(classes="sidebar-buttons-column"):
|
|
36
|
+
yield Button(SideBarOptionsName.SEARCH.value, variant="primary", id="search-sidebar-button", classes="sidebar-option")
|
|
37
|
+
yield Button(SideBarOptionsName.LOAD_REPORT.value, variant="primary", id="load-sidebar-button", classes="sidebar-option")
|
|
38
|
+
|
|
39
|
+
yield Label(f"v{pub_analyzer_version}", id="module-version-label")
|
|
40
|
+
|
|
41
|
+
def toggle(self) -> None:
|
|
42
|
+
"""Show/Hide Sidebar."""
|
|
43
|
+
if self.has_class("-hidden"):
|
|
44
|
+
self.remove_class("-hidden")
|
|
45
|
+
self.styles.animate("width", value=20, duration=0.5)
|
|
46
|
+
else:
|
|
47
|
+
if self.query("*:focus"):
|
|
48
|
+
self.screen.set_focus(None)
|
|
49
|
+
self.styles.animate("width", value=0, duration=0.5)
|
|
50
|
+
self.add_class("-hidden")
|
|
51
|
+
|
|
52
|
+
async def _replace_main_content(self, new_title: str, new_widget: Widget) -> None:
|
|
53
|
+
"""Delete the old widgets in the main section, update the main title and replace it with the given Widget."""
|
|
54
|
+
from pub_analyzer.widgets.body import MainContent
|
|
55
|
+
|
|
56
|
+
main_content = self.app.query_one(MainContent)
|
|
57
|
+
await main_content.query("*").exclude("#page-title").remove()
|
|
58
|
+
await main_content.mount(new_widget)
|
|
59
|
+
|
|
60
|
+
main_content.update_title(title=new_title)
|
|
61
|
+
|
|
62
|
+
@on(Button.Pressed, "#search-sidebar-button")
|
|
63
|
+
async def search(self) -> None:
|
|
64
|
+
"""Load the FinderWidget in the main view."""
|
|
65
|
+
await self._replace_main_content(new_title=SideBarOptionsName.SEARCH.value, new_widget=FinderWidget())
|
|
66
|
+
|
|
67
|
+
@on(Button.Pressed, "#load-sidebar-button")
|
|
68
|
+
async def load_report(self) -> None:
|
|
69
|
+
"""Load the LoadReportWidget in the main view."""
|
|
70
|
+
await self._replace_main_content(new_title=SideBarOptionsName.LOAD_REPORT.value, new_widget=LoadReportWidget())
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pub-analyzer
|
|
3
|
+
Version: 0.5.6
|
|
4
|
+
Summary: A text user interface, written in python, which automates the generation of scientific production reports using OpenAlex
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Alejandro Gaspar
|
|
8
|
+
Author-email: alejandro@gaspar.land
|
|
9
|
+
Maintainer: Alejandro Gaspar
|
|
10
|
+
Maintainer-email: alejandro@gaspar.land
|
|
11
|
+
Requires-Python: >=3.10,<4.0
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Dist: httpx[http2] (==0.28.1)
|
|
28
|
+
Requires-Dist: pydantic (==2.12.5)
|
|
29
|
+
Requires-Dist: textual (==0.85.2)
|
|
30
|
+
Requires-Dist: typst (==0.13.2)
|
|
31
|
+
Project-URL: Documentation, https://pub-analyzer.com/
|
|
32
|
+
Project-URL: Homepage, https://github.com/alejandrgaspar/pub-analyzer
|
|
33
|
+
Project-URL: Repository, https://github.com/alejandrgaspar/pub-analyzer
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Pub Analyzer
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="https://raw.githubusercontent.com/alejandrgaspar/pub-analyzer/main/docs/assets/img/logo.png" alt="PubAnalyzer splash image" width="275">
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<a href="https://github.com/alejandrgaspar/pub-analyzer/actions/workflows/python-test.yml" target="_blank">
|
|
44
|
+
<img src="https://github.com/alejandrgaspar/pub-analyzer/actions/workflows/python-test.yml/badge.svg?branch=main" alt="Test status">
|
|
45
|
+
</a>
|
|
46
|
+
<a href="https://pypi.org/project/pub-analyzer/" target="_blank">
|
|
47
|
+
<img src="https://img.shields.io/pypi/v/pub-analyzer?color=%230f80c1" alt="PyPI - Version">
|
|
48
|
+
</a>
|
|
49
|
+
<a href="https://pypi.org/project/pub-analyzer/" target="_blank">
|
|
50
|
+
<img src="https://img.shields.io/pypi/pyversions/pub-analyzer?color=%230f80c1" alt="PyPI - Python Version">
|
|
51
|
+
</a>
|
|
52
|
+
<a href="https://github.com/alejandrgaspar/pub-analyzer/blob/main/LICENSE" target="_blank">
|
|
53
|
+
<img src="https://img.shields.io/github/license/alejandrgaspar/pub-analyzer?color=%2331c553" alt="License MIT">
|
|
54
|
+
</a>
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
<p align="center">
|
|
58
|
+
Pub-Analyzer —<a href="https://github.com/alejandrgaspar/pub-analyzer" target="_blank"><em>Publication Analyzer</em></a>— is a Text User Interface —<em>TUI</em>— written in Python, which automates the generation of scientific production reports using <a href="https://openalex.org/" target="_blank"><em>OpenAlex</em></a>.
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## What is Pub Analyzer for?
|
|
64
|
+
|
|
65
|
+
Pub Analyzer is a tool designed to retrieve, process and present in a concise and understandable way the scientific production of a researcher, including detailed information about their articles, citations, collaborations and other relevant metrics. The tool automates the collection and processing of data, providing users with a comprehensive view of the impact of their research and contributions in academia.
|
|
66
|
+
|
|
67
|
+
All our information comes from OpenAlex, an open and complete catalog of the world's scholarly papers, researchers, journals, and institutions — along with all the ways they're connected to one another. This is the key piece to **make all this possible**.
|
|
68
|
+
|
|
69
|
+
Pub Analyzer generates reports that you can view directly within the app, **export as PDF** files to submit as evidence, or **export as JSON** for analysis with the tools you use every day.
|
|
70
|
+
|
|
71
|
+
## Why use Pub Analyzer?
|
|
72
|
+
|
|
73
|
+
Researchers are generally required to submit scientific production reports when seeking promotions or funding to support their ongoing research. Instead of laborious manual tracking of their publications and citations, researchers now have the opportunity to perform all of these tasks automatically **in a matter of minutes**.
|
|
74
|
+
|
|
75
|
+
## Requirements
|
|
76
|
+
|
|
77
|
+
Pub Analyzer requires **Python 3.10 or later** and can run on Linux, macOS, and Windows.
|
|
78
|
+
|
|
79
|
+
## Installation
|
|
80
|
+
|
|
81
|
+
Install Pub Analyzer via PyPI, with the following command:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
pip install pub-analyzer
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
Open the app with the following command:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
pub-analyzer
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Documentation
|
|
96
|
+
|
|
97
|
+
See [documentation](https://pub-analyzer.com/) for more details.
|
|
98
|
+
|
|
99
|
+
## Contributing
|
|
100
|
+
|
|
101
|
+
Pull requests are welcome!
|
|
102
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
pub_analyzer/__init__.py,sha256=8xDULqkSyOBAa5bvzcnteo5nW1sqXB6pR9fSc9DpXFo,40
|
|
2
|
+
pub_analyzer/__main__.py,sha256=iCZ8He6JBJ4I7reLgIMf3ISggRiFg2Zuf8fgiTh6dXg,139
|
|
3
|
+
pub_analyzer/css/body.tcss,sha256=Yw9bx5eWABVQPKWDAfWk8AdSXnBAg396JB9Lm4LXD-o,1682
|
|
4
|
+
pub_analyzer/css/buttons.tcss,sha256=FruJ39dXmKnZm3_y0CxAJByKHrbmt6RQky0T0uM906g,546
|
|
5
|
+
pub_analyzer/css/checkbox.tcss,sha256=FblyIHns-r1K0ikOnSJtoTMz57C6iDEcscdFAsJ7s48,506
|
|
6
|
+
pub_analyzer/css/collapsible.tcss,sha256=Rh-L5PcIMhnZ7RhY1udd_BcYC1mfCMew2m6o6ty3juE,605
|
|
7
|
+
pub_analyzer/css/datatable.tcss,sha256=JgdMUPc4fYmZlXi_FxbuD88pegK6Pi4FgDHIfA_TKxo,994
|
|
8
|
+
pub_analyzer/css/editor.tcss,sha256=1IzL92r7sJlnzIJbrTuEGyB16jFCCcAZ9eDDECCZGLc,1198
|
|
9
|
+
pub_analyzer/css/main.tcss,sha256=rtHLKCVZaBuJ83dAg840cetzVtEy8Rjbw1UKxgafeE8,1027
|
|
10
|
+
pub_analyzer/css/report.tcss,sha256=y3-N0mgfyGp4X02LRwFwhxMXMuY7oDsoA9tLDEaUNm8,2047
|
|
11
|
+
pub_analyzer/css/search.tcss,sha256=rovbWjp4pYfCF_OyAC_QrV_0WdMUlsYoQ3vbs9pGw7g,1326
|
|
12
|
+
pub_analyzer/css/summary.tcss,sha256=i4ixICwoQFj2BToW9NVmJGUIYk5upbukbTCnDgT40ds,1350
|
|
13
|
+
pub_analyzer/css/tabs.tcss,sha256=dS7y6ZZmo1Vw7Wqpx66-O-oE7zeqPE9reWqIhQ1KcZs,311
|
|
14
|
+
pub_analyzer/css/tree.tcss,sha256=5BSabX9ZmRL3VTz0Gya2RRJnWrwdIF9cTf6dXj2R4kE,818
|
|
15
|
+
pub_analyzer/internal/__init__.py,sha256=9aqrBJDedUiBO5kEO81kSAuPbOSFoaDZZK8w5NydPhs,22
|
|
16
|
+
pub_analyzer/internal/identifier.py,sha256=LDYew25TLuwqJHmLg9iRNTURWynN27ZbTxTVGbuOUD0,2939
|
|
17
|
+
pub_analyzer/internal/limiter.py,sha256=1YaVBSSG7IfFg0nhD_up21NNL_H2Q4qaIQTvZS674Vo,1002
|
|
18
|
+
pub_analyzer/internal/render.py,sha256=uF1LsY39UkTpkTJgU4hyYnVv6b1MCQayubrPwrGW2DI,1271
|
|
19
|
+
pub_analyzer/internal/report.py,sha256=7geNhs1JRSi2-cqQgQFzMpO0C8lRKa0Ld8oPdFrkYPM,20798
|
|
20
|
+
pub_analyzer/internal/templates/author_report.typ,sha256=URYvit6bsp_8XmdY3Q43vdaiCSMfzrOgE621rFRJfCM,17861
|
|
21
|
+
pub_analyzer/main.py,sha256=0iNj4cggG-HJ8FMODwZ67Yp3-GaFPw-gUEcSCCzwMcc,2332
|
|
22
|
+
pub_analyzer/models/__init__.py,sha256=hvR6m379slQw7gSwnl_OFY21Ytv90mmmOe7bp8vZYkk,59
|
|
23
|
+
pub_analyzer/models/author.py,sha256=7N7DFStmyJAiceNAH-0v8rro8eE9GC9ZXa0v01IX9Hs,2155
|
|
24
|
+
pub_analyzer/models/concept.py,sha256=yNvajKWTn6uBalNoJmlobitvbFBOjF80jlZnjKjwDRw,677
|
|
25
|
+
pub_analyzer/models/institution.py,sha256=1WEfwx-XLeMo-4B5lECNKUFmWbywCvn5QyUfRtDCbDo,3189
|
|
26
|
+
pub_analyzer/models/report.py,sha256=yma70xpD6I8hv_kk5ylMhylLu60eOpQwNEJa-Dd-XQI,2672
|
|
27
|
+
pub_analyzer/models/source.py,sha256=sZkWcf7clYmIBVwgVFKzJcHLAGQRdgkfPL36zGL2zt4,2744
|
|
28
|
+
pub_analyzer/models/topic.py,sha256=3MBQV-njnjfmOVvgmFZxy8fFU7sMj5yxUW8EHFAjlD4,1825
|
|
29
|
+
pub_analyzer/models/work.py,sha256=xFG54n64XhiDUxFpOGYJ9eObqYBNqtDUbYd2a7Ezge4,4375
|
|
30
|
+
pub_analyzer/widgets/__init__.py,sha256=JALs1yGE06XYwjoY_0AG-Wt_pMknI1WEWNYK3atQaEA,18
|
|
31
|
+
pub_analyzer/widgets/author/__init__.py,sha256=oiJibt7YiuGpovOnFIAlC9YwLO-0LN3SDgPWFL-LVPQ,22
|
|
32
|
+
pub_analyzer/widgets/author/cards.py,sha256=JWZxYy4Oen5fToiSBgvfEgmBJlrIVXCWpT-XjkLbxY4,2445
|
|
33
|
+
pub_analyzer/widgets/author/core.py,sha256=9i3U7jSeyQdAdWAslQUMd0_juw3yy9s4mEgl-nJ1LVg,5068
|
|
34
|
+
pub_analyzer/widgets/author/tables.py,sha256=szuRctMtfsivw3OCwJuja4k45A2h636AlOxZHJm0aUA,1893
|
|
35
|
+
pub_analyzer/widgets/body.py,sha256=BvNDe63FE0-KLl89SlhDXtALlLZX4cRqedEG55urTrM,1656
|
|
36
|
+
pub_analyzer/widgets/common/__init__.py,sha256=a65SpyN6RPKA8caSEyDm8mIzW_r4e0LdQGjJaLk07v8,341
|
|
37
|
+
pub_analyzer/widgets/common/card.py,sha256=GGSaeuZt6AqY7kAvcVnWNMrhNPzr7do66YRQOYNSYvU,595
|
|
38
|
+
pub_analyzer/widgets/common/filesystem.py,sha256=i0S3D6JJzPkF1Sqm83SSQlmYFKRf82SnoFgKVE6BdYI,6460
|
|
39
|
+
pub_analyzer/widgets/common/filters.py,sha256=bdtWaxahbFksaZZf6l0Yhgi9opH_RygFHXQV4_CYYj0,3372
|
|
40
|
+
pub_analyzer/widgets/common/input.py,sha256=tK_UCtLDGHlI_NKpKjGkVu4gWiwMAIHixT9Im--Un4c,2649
|
|
41
|
+
pub_analyzer/widgets/common/label.py,sha256=03tl0RdayTeSixdRu4Fyt-jU1cDrI99iQww0hEXahCA,1091
|
|
42
|
+
pub_analyzer/widgets/common/modal.py,sha256=otLQZotdTRTlSeTBknIxqRyduVY6lRZ5yW5u20SLcwI,882
|
|
43
|
+
pub_analyzer/widgets/common/selector.py,sha256=Jh5bsn-zYmHGfEE3eO9XL6BsgKpLMGfg8FJur4gQmH0,1493
|
|
44
|
+
pub_analyzer/widgets/common/summary.py,sha256=Qj-FRfAVgJmCaVUJI-jQrHX2sGKHTP2b75KukuJWlog,165
|
|
45
|
+
pub_analyzer/widgets/institution/__init__.py,sha256=T_WDTDistaaq2obl1Cy_wZI5nTBiJNUnB-_OwBOLFTE,27
|
|
46
|
+
pub_analyzer/widgets/institution/cards.py,sha256=OgLWP8M0xENa5NnY9NtmwjdqOwZJUN77fXSHFNT3BYU,2862
|
|
47
|
+
pub_analyzer/widgets/institution/core.py,sha256=6Tb1xQFJERswE1ra2FQFeZcQrb8zSsg-UHfPjWYUOWY,5331
|
|
48
|
+
pub_analyzer/widgets/institution/tables.py,sha256=tXjrop9HGSkZGjYIOGQEOKVoyoeIGPd-8oSh08iuTRw,838
|
|
49
|
+
pub_analyzer/widgets/report/__init__.py,sha256=oolRVss3JKaQHaQVDncjtxbLINRJ5Rd1ulW1uk7MLhc,54
|
|
50
|
+
pub_analyzer/widgets/report/author.py,sha256=IEfRDfsA8jcmFwQQk1O-iuh8MKr4DbzBPpjoE8xECZA,1459
|
|
51
|
+
pub_analyzer/widgets/report/cards.py,sha256=2jf9cpfzVFZO0I9b29bkNaVhENMnfL26etEpUG-NMk0,4854
|
|
52
|
+
pub_analyzer/widgets/report/concept.py,sha256=xiGXy_RXO_XmdqnlePkOozYPmQrsDdqKPMRXHsZbDP0,1485
|
|
53
|
+
pub_analyzer/widgets/report/core.py,sha256=Bgy_fK-IwGjoIidcr687xXsHzN3LEml-A3ykyXNeVW8,11704
|
|
54
|
+
pub_analyzer/widgets/report/editor.py,sha256=WlhjNQCrqeot2rvV1266Vr8yDYJQLL1lJ1XY040UoJI,2768
|
|
55
|
+
pub_analyzer/widgets/report/export.py,sha256=as2yM2FXsqgvMnF4KVWVuxboULXqJ62v7wzMYek23s4,4633
|
|
56
|
+
pub_analyzer/widgets/report/grants.py,sha256=-RP8LXiXdp0YMaTjrSPcMzlpXYYzRNYK_aStwrLQMj8,2725
|
|
57
|
+
pub_analyzer/widgets/report/institution.py,sha256=PDPE9fK18l9kKKch5sJrbnHHDss0kJ6bgVhM4hTyrAo,1297
|
|
58
|
+
pub_analyzer/widgets/report/locations.py,sha256=0K0p6EkkxRHB6AdPbFJy3k_xH6D4UK9vTH39qTE8Y2g,2899
|
|
59
|
+
pub_analyzer/widgets/report/source.py,sha256=_09lA0bfXAyrm88bdqnQZgRDtXKsuWLCqSegXGpkP8k,3052
|
|
60
|
+
pub_analyzer/widgets/report/topic.py,sha256=SI3STTBFlpR-VJcsNhJyu6vc9uyytU_ASKuWXb-qr60,1969
|
|
61
|
+
pub_analyzer/widgets/report/work.py,sha256=kRIWTokXTvycl1dlLFHNBo3y0s2Ltq7966XTAnbQacc,16112
|
|
62
|
+
pub_analyzer/widgets/search/__init__.py,sha256=90L9IghqXD2jAWBKWK6-UeHLSVlci7D3_OGjFSSRgEs,239
|
|
63
|
+
pub_analyzer/widgets/search/core.py,sha256=4NvowtBcrH1fmob9kuF7v9Tq3Nd99jzB2S7xaD8OYeI,3861
|
|
64
|
+
pub_analyzer/widgets/search/results.py,sha256=3ko7zcToGp9MV-mzz_9uTJxSec7IozlIWDZe7QeRmj0,3703
|
|
65
|
+
pub_analyzer/widgets/sidebar.py,sha256=XlIshlCVW5Bb3MXFPnU9is0qQrUrGdT6xlkKiYNEcAM,2704
|
|
66
|
+
pub_analyzer-0.5.6.dist-info/METADATA,sha256=DNdotrEjI_pVTM897gt3dadF8vdjMJQdoe2ZL1L8bg0,4620
|
|
67
|
+
pub_analyzer-0.5.6.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
68
|
+
pub_analyzer-0.5.6.dist-info/entry_points.txt,sha256=mVb_gUNX_-aVWHlNKLjcMAS8YLgNnSq9JLRXVJGIF2c,54
|
|
69
|
+
pub_analyzer-0.5.6.dist-info/licenses/LICENSE,sha256=OPopoEowTMKqIea8Kbxk3TKdCQ97YkLvIknjTHE5oCI,1080
|
|
70
|
+
pub_analyzer-0.5.6.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Alejandro Gaspar Flores
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|