Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
P
papersurfer
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Jacobsohn, Johann
papersurfer
Commits
0230da34
Commit
0230da34
authored
4 years ago
by
Johann Jacobsohn
Browse files
Options
Downloads
Patches
Plain Diff
lint
parent
0b0e44d1
No related branches found
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
.pydocstyle
+8
-0
8 additions, 0 deletions
.pydocstyle
.pylintrc
+4
-0
4 additions, 0 deletions
.pylintrc
papersurfer/papersurfer.py
+64
-35
64 additions, 35 deletions
papersurfer/papersurfer.py
with
76 additions
and
35 deletions
.pydocstyle
0 → 100755
+
8
−
0
View file @
0230da34
[pydocstyle]
ignore = D104,D105,D107,D203,D204,D213
# D104: Missing docstring in public package
# D105 Missing docstring in magic method
# D107 Missing docstring in __init__
# D203 1 blank line required before class docstring
# D204 1 blank line required after class docstring (found 0)
# D213 Multi-line docstring summary should start at the second line
\ No newline at end of file
This diff is collapsed.
Click to expand it.
.pylintrc
0 → 100644
+
4
−
0
View file @
0230da34
[MESSAGES CONTROL]
disable=no-self-use,too-many-instance-attributes
good-names=id, i
This diff is collapsed.
Click to expand it.
papersurfer/papersurfer.py
+
64
−
35
View file @
0230da34
"""
"
Paper surfer - browse papers posted on the mattermost channel.
"""
Paper surfer - browse papers posted on the mattermost channel.
UI:
...
...
@@ -16,6 +16,7 @@ from functools import partial
import
json
import
time
import
os
import
sys
import
requests
import
mattermostdriver
import
urwid
...
...
@@ -28,7 +29,7 @@ class ConfigError(Exception):
@dataclass
class
PostDTO
:
"""
"
Encapsulate Mattermost Posts.
"""
"""
Encapsulate Mattermost Posts.
"""
id
:
str
message
:
str
reporter
:
str
...
...
@@ -40,7 +41,7 @@ class PostDTO:
@dataclass
class
PaperDTO
:
"""
"
Encapsulate Paper meta data.
"""
"""
Encapsulate Paper meta data.
"""
author
:
str
authors
:
str
title
:
str
...
...
@@ -52,20 +53,24 @@ class PaperDTO:
class
Bibtex
:
"""
Interface for bibtex string.
"""
def
entry_from_doi
(
self
,
doi
):
"""
Get bibtex string for doi.
"""
return
Doi
().
get_bibtex
(
doi
)
def
bib_from_dois
(
self
,
dois
):
"""
Get bibtex string for mulitple dois.
"""
return
"
\n
"
.
join
([
Doi
().
get_bibtex
(
doi
)
for
doi
in
dois
])
class
Doi
:
"""
Interface w/ the doi.org api
"""
"""
Interface w/ the doi.org api
.
"""
def
get_doi_link
(
self
,
doi
):
"""
Assemble doi link.
"""
return
f
"
http://doi.org/
{
doi
}
"
def
load_doi_data
(
self
,
doi
):
"""
Load data for doi.
"""
headers
=
{
'
Accept
'
:
'
application/json
'
,
}
...
...
@@ -73,7 +78,7 @@ class Doi:
headers
=
headers
).
content
def
parse_doi_json
(
self
,
jsoncontent
):
"""
Tranform doi json to PaperDTO
"""
"""
Tranform doi json to PaperDTO
.
"""
info
=
json
.
loads
(
jsoncontent
)
with
open
(
"
debug.json
"
,
"
w
"
)
as
file
:
...
...
@@ -87,8 +92,7 @@ class Doi:
if
"
author
"
in
info
else
"
Authors N/A
"
)
title
=
(
info
[
'
title
'
]
if
"
title
"
in
info
and
isinstance
(
info
[
'
title
'
],
str
)
if
"
title
"
in
info
and
isinstance
(
info
[
'
title
'
],
str
)
else
"
Title N/A
"
)
journal
=
(
info
[
'
publisher
'
]
if
"
publisher
"
in
info
...
...
@@ -105,12 +109,14 @@ class Doi:
slug
)
def
get_bibtex
(
self
,
doi
):
"""
Get bibtex string for doi.
"""
headers
=
{
'
Accept
'
:
'
text/bibliography; style=bibtex
'
,
}
return
requests
.
get
(
f
'
http://dx.doi.org/
{
doi
}
'
,
headers
=
headers
).
text
def
get_info
(
self
,
doi
):
"""
Get information for doi.
"""
try
:
jsoncontent
=
self
.
load_doi_data
(
doi
)
data
=
self
.
parse_doi_json
(
jsoncontent
)
...
...
@@ -132,6 +138,7 @@ class Doi:
class
Mattermost
:
"""
Provide a simplified interaction w/ mattermost api.
"""
def
__init__
(
self
,
url
,
channelname
,
username
,
password
):
self
.
msgs
=
[]
self
.
mattermost
=
mattermostdriver
.
Driver
({
'
url
'
:
url
,
'
login_id
'
:
username
,
...
...
@@ -155,13 +162,14 @@ class Mattermost:
self
.
reporters
=
{}
def
get_channel
(
self
,
channelname
):
""""
Try to find the paper channel by display name.
"""
mm
=
self
.
mattermost
teams
=
[
team
[
"
id
"
]
for
team
in
mm
.
teams
.
get_user_teams
(
"
me
"
)]
"""
Try to find the paper channel by display name.
"""
teamapi
=
self
.
mattermost
.
teams
channelapi
=
self
.
mattermost
.
channels
teams
=
[
team
[
"
id
"
]
for
team
in
teamapi
.
get_user_teams
(
"
me
"
)]
channels
=
[]
for
team
in
teams
:
teamchannels
=
[
channel
for
channel
in
mm
.
channel
s
.
get_channels_for_user
(
"
me
"
,
team
)
in
channel
api
.
get_channels_for_user
(
"
me
"
,
team
)
if
channel
[
"
display_name
"
]
==
channelname
]
channels
.
extend
(
teamchannels
)
...
...
@@ -171,22 +179,20 @@ class Mattermost:
raise
ConfigError
return
channels
[
0
][
"
id
"
]
def
get_reporter
(
self
,
id
):
def
get_reporter
(
self
,
user
id
):
"""
Load user from mattermost api and cache.
"""
if
id
not
in
self
.
reporters
:
self
.
reporters
[
id
]
=
self
.
mattermost
.
users
.
get_user
(
id
)[
"
username
"
]
userapi
=
self
.
mattermost
.
users
if
userid
not
in
self
.
reporters
:
self
.
reporters
[
userid
]
=
userapi
.
get_user
(
userid
)[
"
username
"
]
return
self
.
reporters
[
id
]
return
self
.
reporters
[
user
id
]
def
retrieve_all_messages
(
self
):
"""
Retrieve all messages from mattermost, unfiltered for papers.
"""
posts
=
self
.
mattermost
.
posts
.
get_posts_for_channel
(
self
.
channel
)
return
[
PostDTO
(
id
=
m
[
'
id
'
],
message
=
m
[
'
message
'
],
return
[
PostDTO
(
id
=
m
[
'
id
'
],
message
=
m
[
'
message
'
],
reporter
=
self
.
get_reporter
(
m
[
'
user_id
'
]),
doi
=
Doi
().
extract_doi
(
m
[
'
message
'
]),
)
doi
=
Doi
().
extract_doi
(
m
[
'
message
'
]),)
for
m
in
posts
[
'
posts
'
].
values
()]
def
filter_incoming
(
self
,
posts
):
...
...
@@ -200,6 +206,7 @@ class Mattermost:
return
self
.
msgs
def
check_doi_exits
(
self
,
doi
):
"""
Check for doi in current paper list.
"""
doi_needle
=
Doi
().
extract_doi
(
doi
)
msg_found
=
[
msg
for
msg
in
self
.
msgs
if
Doi
().
extract_doi
(
msg
.
doi
)
==
doi_needle
]
...
...
@@ -212,12 +219,15 @@ class Mattermost:
or
needle
.
lower
()
in
m
.
reporter
.
lower
()]
def
post
(
self
,
message
):
"""
Post message to thread.
"""
self
.
mattermost
.
posts
.
create_post
({
"
channel_id
"
:
self
.
channel
,
"
message
"
:
message
})
class
PrettyButton
(
urwid
.
WidgetWrap
):
"""
Prettified urwid Button.
"""
def
__init__
(
self
,
label
,
on_press
=
None
,
user_data
=
None
):
self
.
label
=
""
self
.
text
=
urwid
.
Text
(
""
)
self
.
set_label
(
label
)
self
.
widget
=
urwid
.
AttrMap
(
self
.
text
,
''
,
'
highlight
'
)
...
...
@@ -226,21 +236,26 @@ class PrettyButton(urwid.WidgetWrap):
self
.
_hidden_btn
=
urwid
.
Button
(
f
"
hidden
{
self
.
label
}
"
,
on_press
,
user_data
)
super
(
self
.
__class__
,
self
).
__init__
(
self
.
widget
)
super
().
__init__
(
self
.
widget
)
def
selectable
(
self
):
"""
Make button selectable.
"""
return
True
def
keypress
(
self
,
*
args
,
**
kw
):
"""
Handle keypresses.
"""
return
self
.
_hidden_btn
.
keypress
(
*
args
,
**
kw
)
def
mouse_event
(
self
,
*
args
,
**
kw
):
"""
Handle mouse events.
"""
return
self
.
_hidden_btn
.
mouse_event
(
*
args
,
**
kw
)
def
get_label
(
self
):
"""
Return current input label.
"""
return
self
.
label
def
set_label
(
self
,
label
):
"""
Return current input label.
"""
self
.
label
=
label
self
.
text
.
set_text
(
f
"
[
{
label
}
]
"
)
...
...
@@ -308,16 +323,19 @@ class Papersurfer:
self
.
mainloop
.
run
()
def
h_unhandled_input
(
self
,
key
):
"""
Handle keyboard input not otherwise handled.
"""
if
key
==
"
esc
"
:
raise
urwid
.
ExitMainLoop
()
def
load_list
(
self
,
_loop
,
_data
):
"""
Load and display paper list.
"""
body
=
[
self
.
list_item
(
paper
)
for
paper
in
self
.
mtm
.
retrieve
()]
self
.
listcontent
.
clear
()
self
.
listcontent
.
extend
(
body
)
self
.
mainloop
.
widget
=
self
.
top
def
loading_indicator
(
self
):
"""
Create loading indicator dialog.
"""
body_text
=
urwid
.
Text
(
"
Loading...
"
,
align
=
'
center
'
)
body_filler
=
urwid
.
Filler
(
body_text
,
valign
=
'
middle
'
)
body_padding
=
urwid
.
Padding
(
...
...
@@ -329,6 +347,7 @@ class Papersurfer:
return
urwid
.
Frame
(
body_padding
)
def
details_popup
(
self
,
paper
):
"""
Create Dialog with paper details.
"""
header_text
=
urwid
.
Text
((
'
banner
'
,
'
Paper details
'
),
align
=
'
center
'
)
header
=
urwid
.
AttrMap
(
header_text
,
'
banner
'
)
...
...
@@ -391,7 +410,7 @@ class Papersurfer:
return
pile
def
updscrn
(
self
):
"""
"
Update (redraw) screen.
"""
"""
Update (redraw) screen.
"""
self
.
mainloop
.
draw_screen
()
def
onchange
(
self
,
_
,
needle
):
...
...
@@ -402,6 +421,7 @@ class Papersurfer:
for
paper
in
self
.
mtm
.
get_filtered
(
needle
)])
def
running_export
(
self
,
state
):
"""
Set exporting state.
"""
btn
=
self
.
exportbutton
label
=
btn
.
get_label
()
running_indicator
=
"
(running...)
"
...
...
@@ -422,6 +442,7 @@ class Papersurfer:
self
.
running_export
(
False
)
def
export_to_bibtex
(
self
):
"""
Export current filtered list to bibtex file.
"""
papers
=
self
.
mtm
.
get_filtered
(
self
.
filter
)
dois
=
[
paper
.
doi
for
paper
in
papers
]
string
=
Bibtex
().
bib_from_dois
(
dois
)
...
...
@@ -455,13 +476,15 @@ class Papersurfer:
self
.
mainloop
.
widget
=
self
.
details_popup
(
paper
)
def
h_close_dialog
(
self
,
_
):
"""
Handle close dialog button.
"""
self
.
close_dialog
()
def
close_dialog
(
self
):
"""
Close currently open dialog.
"""
self
.
mainloop
.
widget
=
self
.
top
def
open_submit_paper
(
self
,
_
):
"""
Open submit paper dialog.
"""
self
.
_pile
=
urwid
.
Pile
(
[
PostDialog
(
self
.
mtm
,
close
=
self
.
h_close_dialog
)
...
...
@@ -494,7 +517,6 @@ def get_config_file_paths():
>>>
type
(
get_config_file_paths
())
<
class
'
list
'
>
"""
env = os.environ
xdg_home = None
if
'
XDG_CONFIG_HOME
'
in env:
...
...
@@ -517,6 +539,7 @@ def get_config_file_paths():
def interactive_configuration():
"""
Query user for credentials.
"""
url = input(
"
Mattermost URL (eg. mattermost.example.net):
"
)
channel = input(
"
Channel (eg. Paper Club):
"
)
username = input(
"
Username (same as mattermost login,
"
...
...
@@ -526,7 +549,8 @@ def interactive_configuration():
class PostDialog(urwid.WidgetWrap):
"""
"""
Dialog to submit a new paper to mattermost thread.
UI:
DOI: [ _________________]
Generated Message:
...
...
@@ -535,6 +559,8 @@ class PostDialog(urwid.WidgetWrap):
[Submit] [Close]
"""
def __init__(self, mattermost, close):
self.doi = None
self.msg = None
self.mattermost = mattermost
self.close = close
self.doi_input = urwid.Edit(
"
Doi:
"
)
...
...
@@ -558,21 +584,21 @@ class PostDialog(urwid.WidgetWrap):
right=1
)
body = urwid.LineBox(body_padding)
frame = urwid.Frame(
body,
header=urwid.Text(
"
Submit new paper to list
"
),
)
frame = urwid.Frame(body,
header=urwid.Text(
"
Submit new paper to list
"
))
self.widget = frame
super(
self.__class__, self
).__init__(self.widget)
super().__init__(self.widget)
def submit(self, _):
"""
Submit post to thread.
"""
if not self.mattermost.check_doi_exits(self.doi):
self.mattermost.post(self.msg)
self.close(_)
def create_mgs(self, paper):
"""
Format post message.
"""
msg = f
"""
\
{paper.title}
{paper.authors}
...
...
@@ -581,6 +607,7 @@ class PostDialog(urwid.WidgetWrap):
return msg
def h_input(self, _, doi):
"""
Handle doi input field.
"""
self.doi_result.set_text(
"
... loading ...
"
)
self.doi = None
self.msg = None
...
...
@@ -602,8 +629,8 @@ class PostDialog(urwid.WidgetWrap):
def parse_args():
"""
Parse command line arguments and config file.
"""
parser = configargparse.ArgParser(
)
parser._
default_config_files
=
get_config_file_paths()
parser = configargparse.ArgParser(
default_config_files
=
get_config_file_paths()
)
parser.add(
"
-w
"
,
"
--write-out-config-file
"
,
help=
"
takes the current command line args and writes them out
"
"
to a config file at the given path
"
,
...
...
@@ -630,7 +657,7 @@ def parse_args():
Mattermost(url, channel, username, password)
except ConfigError:
print(
"
Failed to validate configuration, exiting.
"
)
exit(1)
sys.
exit(1)
options.url = url
options.channel = channel
...
...
@@ -648,7 +675,7 @@ def parse_args():
time.sleep(2)
else:
parser.print_help()
exit(0)
sys.
exit(0)
return options
...
...
@@ -661,12 +688,14 @@ def just_papers(url, channel, username, password):
def just_bibtex(url, channel, username, password):
"""
Retrieve and dump bibtext formated data, unfiltered.
"""
posts = Mattermost(url, channel, username, password).retrieve()
dois = [post.doi for post in posts]
print(Bibtex().bib_from_dois(dois))
def main():
"""
Run main program.
"""
opt = parse_args()
if opt.dump_posts:
just_papers(opt.url, opt.channel, opt.username, opt.password)
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment