neurolibre 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 61b64abbb7fd96d5b841b156ff5872972ba51b2c7765c2f32b08c2894ac39917
4
+ data.tar.gz: 011d818ffd6862603ab817e359cedcd344c1c6263703cac5004f1ca8339a8003
5
+ SHA512:
6
+ metadata.gz: ff5f5745c38a93662bca47cf623671c34b4784217270762a66a3b489082feadc072f94996c954b1d20bd8e52571355feeade515811315cdaccd75e77c2157cb4
7
+ data.tar.gz: 777020c7718448df0b583fc5dc82e7cc3df8ad3b5e699853328a92bd4c946dd7f998b0dc20db903f82346d587c243b546ee02a38bf33453de9632024085b2a08
data/CHANGELOG.md ADDED
@@ -0,0 +1,104 @@
1
+ # Changelog
2
+
3
+ ## 1.5.2 (2022-10-29)
4
+
5
+ - Updated journal data for JuliaCon
6
+
7
+ ## 1.5.1 (2022-10-25)
8
+
9
+ - Remove newlines from paper's title
10
+ - Faraday and Octokit dependencies updated to latest major versions
11
+
12
+ ## 1.5.0 (2022-10-02)
13
+
14
+ - Added Commonmarker as dependency
15
+ - Submission metadata's title is converted to plain text
16
+
17
+ ## 1.4.1 (2022-09-28)
18
+
19
+ - Added method to Submission to get track information
20
+
21
+ ## 1.4.0 (2022-08-01)
22
+
23
+ - Added method to Journal to get year/volume/issue information for any date
24
+ - Submission metadata now takes into account published paper status
25
+
26
+ ## 1.3.6 (2022-07-11)
27
+
28
+ - Improve parsing of author names
29
+
30
+ ## 1.3.5 (2022-07-01)
31
+
32
+ - Added configuration for JuliaCon
33
+
34
+ ## 1.3.4 (2022-05-29)
35
+
36
+ - Use Faraday v2 (updated Octokit and other dependencies)
37
+
38
+ ## 1.3.3 (2022-03-31)
39
+
40
+ - Fix bug with upcased user logins
41
+
42
+ ## 1.3.2 (2022-03-17)
43
+
44
+ - Allow setting GitHub access using GITHUB_TOKEN env var
45
+
46
+ ## 1.3.1 (2022-03-09)
47
+
48
+ - Fix error when loading metadata for papers with wrong path
49
+
50
+ ## 1.3.0 (2022-03-02)
51
+
52
+ - Change branches using git-switch instead of git-checkout to remove ambiguaties. Requires Git >= 2.23
53
+
54
+ ## 1.2.1 (2021-11-30)
55
+
56
+ - Changed metadata dates format to ISO
57
+ - Changed editor and reviewers metadata values to github login
58
+ - Removed languages from article metadata
59
+
60
+ ## 1.2.0 (2021-11-23)
61
+
62
+ - Added reviews_repository_url to Journal
63
+ - Added article_metadata to Submission
64
+ - Added editor and paper dates lookup information in Submission
65
+ - Fixed error reading reviewers list from issue body
66
+
67
+ ## 1.1.1 (2021-11-05)
68
+
69
+ - Added support for test-journal
70
+
71
+ ## 1.1.0 (2021-10-29)
72
+
73
+ - Added Theoj::PublishedPaper object with metadata from Journal's API
74
+ - Added custom Error class
75
+
76
+ ## 1.0.0 (2021-10-20)
77
+
78
+ - Added method to Journal to create paper_id from issue_id
79
+ - Added method to Journal to get a DOI based on a paper id
80
+ - Added languages to Paper
81
+ - Added authors info to Paper
82
+ - Author object
83
+ - Added ORCID validation
84
+ - Added Submission object, grouping a paper, a review issue and a journal
85
+ - Added paper depositing
86
+
87
+ ## 0.0.3 (2021-10-08)
88
+
89
+ - Added metadata methods to Paper
90
+ - Added to ReviewIssue: editor, reviewers, archive, version
91
+ - New method to read any value from review's issue body
92
+ - Values read from issue boy will be empty if Pending or TBD
93
+ - Added journal config data for OpenJournals: JOSS and JOSE
94
+
95
+
96
+ ## 0.0.2 (2021-09-22)
97
+
98
+ - Available objects: Theoj::Journal, Theoj::ReviewIssue and Theoj::Paper
99
+
100
+
101
+ ## 0.0.1 (2021-09-22)
102
+
103
+ - Gem created
104
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Juanjo Bazán
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.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # The Open Journal
2
+ A library to manage editorial objects used by the open journals review process
3
+
4
+ [![CI](https://github.com/xuanxu/theoj/actions/workflows/ci.yml/badge.svg)](https://github.com/xuanxu/theoj/actions/workflows/ci.yml)
5
+ [![Gem Version](https://badge.fury.io/rb/theoj.svg)](https://badge.fury.io/rb/theoj)
data/lib/neurolibre.rb ADDED
@@ -0,0 +1,14 @@
1
+ require_relative "theoj/version"
2
+ require_relative "theoj/git"
3
+ require_relative "theoj/github"
4
+ require_relative "theoj/orcid"
5
+ require_relative "theoj/published_paper"
6
+ require_relative "theoj/submission"
7
+ require_relative "theoj/journal"
8
+ require_relative "theoj/review_issue"
9
+ require_relative "theoj/paper"
10
+ require_relative "theoj/author"
11
+
12
+ module Theoj
13
+ class Error < StandardError; end
14
+ end
@@ -0,0 +1,107 @@
1
+ require "nameable"
2
+
3
+ module Theoj
4
+ class Author
5
+ attr_accessor :name
6
+ attr_accessor :orcid
7
+ attr_accessor :affiliation
8
+
9
+ AUTHOR_FOOTNOTE_REGEX = /^[^\^]*/
10
+
11
+ # Initialized with authors & affiliations block in the YAML header from an Open Journal paper
12
+ # e.g. https://joss.readthedocs.io/en/latest/submitting.html#example-paper-and-bibliography
13
+ def initialize(name, orcid, index, affiliations_hash)
14
+ parse_name name
15
+ @orcid = validate_orcid orcid
16
+ @affiliation = build_affiliation_string(index, affiliations_hash)
17
+ end
18
+
19
+ def given_name
20
+ @parsed_name.first
21
+ end
22
+
23
+ def middle_name
24
+ @parsed_name.middle
25
+ end
26
+
27
+ def last_name
28
+ @parsed_name.last
29
+ end
30
+
31
+ def initials
32
+ [@parsed_name.first, @parsed_name.middle].compact.map {|v| v[0] + "."} * ' '
33
+ end
34
+
35
+ def to_h
36
+ {
37
+ given_name: given_name,
38
+ middle_name: middle_name,
39
+ last_name: last_name,
40
+ orcid: orcid,
41
+ affiliation: affiliation
42
+ }
43
+ end
44
+
45
+ private
46
+
47
+ def parse_name(author_name)
48
+ if author_name.is_a? Hash
49
+ g = author_name["given-names"] || author_name["given"] || author_name["first"] || author_name["firstname"]
50
+ m = author_name["dropping-particle"]
51
+ s = author_name["surname"] || author_name["family"]
52
+
53
+ g = strip_footnotes(g) unless g.nil?
54
+ s = strip_footnotes(s) unless s.nil?
55
+
56
+ @parsed_name = m.nil? ? Nameable::Latin.new(g, s) : Nameable::Latin.new(g, m, s)
57
+ else
58
+ @parsed_name = Nameable::Latin.new.parse(strip_footnotes(author_name))
59
+ end
60
+
61
+ @name = @parsed_name.to_nameable
62
+ end
63
+
64
+ # Input: Arfon Smith^[Corresponding author: arfon@example.com]
65
+ # Output: Arfon Smith
66
+ def strip_footnotes(author_name)
67
+ author_name.to_s[AUTHOR_FOOTNOTE_REGEX]
68
+ end
69
+
70
+ def validate_orcid(author_orcid)
71
+ return nil if author_orcid.to_s.strip.empty?
72
+
73
+ validator = Theoj::Orcid.new(author_orcid)
74
+ if validator.valid?
75
+ return author_orcid.strip
76
+ else
77
+ raise Theoj::Error, "Problem with ORCID (#{author_orcid}) for #{self.name}. #{validator.error}"
78
+ end
79
+ end
80
+
81
+ # Takes the author affiliation index and a hash of all affiliations and
82
+ # associates them. Then builds the author affiliation string
83
+ def build_affiliation_string(index, affiliations_hash)
84
+ return nil if index.nil? # Some authors don't have an affiliation
85
+
86
+ # If multiple affiliations, parse each one and build the affiliation string
87
+ author_affiliations = []
88
+
89
+ # Turn YAML keys into strings so that mixed integer and string affiliations work
90
+ affiliations_hash.transform_keys!(&:to_s)
91
+
92
+ affiliations = index.to_s.split(',').map(&:strip)
93
+
94
+ # Raise if we can't parse the string, might be because of this bug :-(
95
+ # https://bugs.ruby-lang.org/issues/12451
96
+ affiliations.each do |a|
97
+ raise Theoj::Error, "Problem with affiliations for #{self.name}, perhaps the " +
98
+ "affiliations index need quoting?" unless affiliations_hash.has_key?(a)
99
+
100
+ author_affiliations << affiliations_hash[a].strip
101
+ end
102
+
103
+ author_affiliations.join(', ')
104
+ end
105
+
106
+ end
107
+ end
data/lib/theoj/git.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "open3"
2
+ require "fileutils"
3
+
4
+ module Theoj
5
+ module Git
6
+ def clone_repo(url, local_path)
7
+ url = URI.extract(url.to_s).first
8
+ return false if url.nil?
9
+
10
+ FileUtils.mkdir_p(local_path)
11
+ stdout, stderr, status = Open3.capture3 "git clone #{url} #{local_path}"
12
+ status.success?
13
+ end
14
+
15
+ def change_branch(branch, local_path)
16
+ return true if (branch.nil? || branch.strip.empty?)
17
+ stdout, stderr, status = Open3.capture3 "git -C #{local_path} switch #{branch}"
18
+ status.success?
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ require "octokit"
2
+
3
+ # This module includes all the methods involving calls to the GitHub API
4
+ # It reuses a memoized Octokit::Client instance
5
+ module Theoj
6
+ module GitHub
7
+
8
+ # Authenticated Octokit
9
+ def github_client
10
+ @github_client ||= Octokit::Client.new(access_token: github_access_token, auto_paginate: true)
11
+ end
12
+
13
+ # GitHub access token
14
+ def github_access_token
15
+ @github_access_token ||= (ENV["GH_ACCESS_TOKEN"] || ENV["GITHUB_TOKEN"])
16
+ end
17
+
18
+ # GitHub API headers
19
+ def github_headers
20
+ @github_headers ||= { "Authorization" => "token #{github_access_token}",
21
+ "Content-Type" => "application/json",
22
+ "Accept" => "application/vnd.github.v3+json" }
23
+ end
24
+
25
+ # Return an Octokit GitHub Issue
26
+ def issue(repo, issue_id)
27
+ @issue ||= github_client.issue(repo, issue_id)
28
+ end
29
+
30
+ # List labels of a GitHub issue
31
+ def issue_labels(repo, issue_id)
32
+ github_client.labels_for_issue(repo, issue_id).map { |l| l[:name] }
33
+ end
34
+
35
+ # Uses the GitHub API to determine if a user is already a collaborator of the repo
36
+ def is_collaborator?(repo, username)
37
+ username = user_login(username)
38
+ github_client.collaborator?(repo, username)
39
+ end
40
+
41
+ # Uses the GitHub API to determine if a user is already a collaborator of the repo
42
+ def can_be_assignee?(repo, username)
43
+ username = user_login(username)
44
+ github_client.check_assignee(repo, username)
45
+ end
46
+
47
+ # Uses the GitHub API to determine if a user has a pending invitation
48
+ def is_invited?(repo, username)
49
+ username = user_login(username).downcase
50
+ github_client.repository_invitations(repo).any? { |i| i.invitee.login.downcase == username }
51
+ end
52
+
53
+ # Returns the user login (removes the @ from the username)
54
+ def user_login(username)
55
+ username.to_s.strip.sub(/^@/, "")
56
+ end
57
+
58
+ # Returns true if the string is a valid GitHub isername (starts with @)
59
+ def username?(username)
60
+ username.match?(/\A@/)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,81 @@
1
+ require_relative "journals_data"
2
+
3
+ module Theoj
4
+ class Journal
5
+ attr_accessor :data
6
+ attr_accessor :doi_prefix
7
+ attr_accessor :url
8
+ attr_accessor :name
9
+ attr_accessor :alias
10
+ attr_accessor :launch_date
11
+
12
+ def initialize(custom_data = {})
13
+ set_data custom_data
14
+ end
15
+
16
+ def current_year
17
+ data[:current_year] || Time.new.year
18
+ end
19
+
20
+ def current_volume
21
+ data[:current_volume] || (Time.new.year - (launch_year - 1))
22
+ end
23
+
24
+ def current_issue
25
+ data[:current_issue] || (1 + ((Time.new.year * 12 + Time.new.month) - (launch_year * 12 + launch_month)))
26
+ end
27
+
28
+ def paper_id_from_issue(review_issue_id)
29
+ id = "%05d" % review_issue_id
30
+ "#{@alias}.#{id}"
31
+ end
32
+
33
+ def paper_doi_for_id(paper_id)
34
+ "#@doi_prefix/#{paper_id}"
35
+ end
36
+
37
+ def reviews_repository_url(issue_id=nil)
38
+ reviews_url = "https://github.com/#{data[:reviews_repository]}"
39
+ if issue_id
40
+ reviews_url += "/issues/" + issue_id.to_s
41
+ end
42
+ reviews_url
43
+ end
44
+
45
+ def year_volume_issue_for_date(d)
46
+ d = Date.parse(d) if d.is_a? String
47
+ year = d.year
48
+ volume = year - (launch_year - 1)
49
+ issue = (1 + ((year * 12 + d.month) - (launch_year * 12 + launch_month)))
50
+
51
+ [year, volume, issue]
52
+ end
53
+
54
+ private
55
+
56
+ def set_data(custom_data)
57
+ @data = default_data.merge(custom_data)
58
+ @doi_prefix = data[:doi_prefix]
59
+ @url = data[:url]
60
+ @name = data[:name]
61
+ @alias = data[:alias]
62
+ @launch_date = data[:launch_date]
63
+ end
64
+
65
+ def default_data
66
+ Theoj::JOURNALS_DATA[:neurolibre]
67
+ end
68
+
69
+ def parsed_launch_date
70
+ @parsed_launch_date ||= Time.parse(data[:launch_date])
71
+ end
72
+
73
+ def launch_year
74
+ parsed_launch_date.year
75
+ end
76
+
77
+ def launch_month
78
+ parsed_launch_date.month
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,66 @@
1
+ require "date"
2
+
3
+ module Theoj
4
+ JOURNALS_DATA = {
5
+ joss: {
6
+ doi_prefix: "10.21105",
7
+ url: "https://joss.theoj.org",
8
+ name: "Journal of Open Source Software",
9
+ alias: "joss",
10
+ launch_date: "2016-05-05",
11
+ papers_repository: "openjournals/joss-papers",
12
+ reviews_repository: "openjournals/joss-reviews",
13
+ deposit_url: "https://joss.theoj.org/papers/api_deposit"
14
+ },
15
+ jose: {
16
+ doi_prefix: "10.21105",
17
+ url: "https://jose.theoj.org",
18
+ name: "Journal of Open Source Education",
19
+ alias: "jose",
20
+ launch_date: "2018-03-07",
21
+ papers_repository: "openjournals/jose-papers",
22
+ reviews_repository: "openjournals/jose-reviews",
23
+ deposit_url: "https://joss.theoj.org/papers/api_deposit"
24
+ },
25
+ jcon: {
26
+ doi_prefix: "10.21105",
27
+ url: "https://proceedings.juliacon.org/",
28
+ name: "The Proceedings of the JuliaCon Conferences",
29
+ alias: "jcon",
30
+ launch_date: "2019-07-21",
31
+ papers_repository: "JuliaCon/proceedings-papers",
32
+ reviews_repository: "JuliaCon/proceedings-review",
33
+ deposit_url: "https://proceedings.juliacon.org/papers/api_deposit"
34
+ },
35
+ resciencec: {
36
+ doi_prefix: "10.21105",
37
+ url: "https://resciencec.theoj.org",
38
+ name: "ReScience C",
39
+ alias: "resciencec",
40
+ launch_date: "2015-05-23",
41
+ papers_repository: "ReScience/ReScienceC-papers",
42
+ reviews_repository: "ReScience/ReScienceC-reviews",
43
+ deposit_url: "https://resciencec.theoj.org/papers/api_deposit"
44
+ },
45
+ neurolibre: {
46
+ doi_prefix: "10.55458",
47
+ url: "https://neurolibre.org/",
48
+ name: "NeuroLibre",
49
+ alias: "neurolibre",
50
+ launch_date: "2021-01-01",
51
+ papers_repository: "neurolibre/preprints",
52
+ reviews_repository: "neurolibre/neurolibre-reviews",
53
+ deposit_url: "https://neurolibre-neo.herokuapp.com/papers/api_deposit"
54
+ },
55
+ test_journal: {
56
+ doi_prefix: "10.55458",
57
+ url: "https://staging.neurolibre.org/",
58
+ name: "NeuroLibre",
59
+ alias: "neurolibre",
60
+ launch_date: "2021-01-01",
61
+ papers_repository: "neurolibre/neurolibre-preprints-testing",
62
+ reviews_repository: "openjournals/joss-reviews-testing",
63
+ deposit_url: "https://staging.neurolibre.org/papers/api_deposit"
64
+ }
65
+ }
66
+ end
@@ -0,0 +1,96 @@
1
+ module Theoj
2
+ class Orcid
3
+ attr_reader :orcid, :error
4
+
5
+ def initialize(orcid)
6
+ @orcid = orcid.strip
7
+ @error = nil
8
+ end
9
+
10
+ def valid?
11
+ @error = nil
12
+ return false unless check_structure
13
+ return false unless check_length
14
+ return false unless check_chars
15
+
16
+ return false unless correct_checksum?
17
+
18
+ true
19
+ end
20
+
21
+ def packed_orcid
22
+ orcid.gsub('-', '')
23
+ end
24
+
25
+ private
26
+
27
+ # Returns the last character of the string
28
+ def checksum_char
29
+ packed_orcid[-1]
30
+ end
31
+
32
+ def first_11
33
+ packed_orcid.chop
34
+ end
35
+
36
+ def check_structure
37
+ groups = orcid.split('-')
38
+ if groups.size == 4
39
+ return true
40
+ else
41
+ @error = "ORCID looks malformed"
42
+ return false
43
+ end
44
+ end
45
+
46
+ def check_length
47
+ if packed_orcid.length == 16
48
+ return true
49
+ else
50
+ @error = "ORCID looks to be the wrong length"
51
+ return false
52
+ end
53
+ end
54
+
55
+ def check_chars
56
+ valid = true
57
+ first_11.each_char do |c|
58
+ if !numeric?(c)
59
+ @error = "Invalid ORCID digit (#{c})"
60
+ valid = false
61
+ end
62
+ end
63
+
64
+ return valid
65
+ end
66
+
67
+ def correct_checksum?
68
+ validate_against = checksum_char.to_i
69
+ validate_against = 10 if (checksum_char == "X" || checksum_char == "x")
70
+
71
+ if checksum == validate_against
72
+ return true
73
+ else
74
+ @error = "Invalid ORCID"
75
+ return false
76
+ end
77
+ end
78
+
79
+ # https://support.orcid.org/knowledgebase/articles/116780-structure-of-the-orcid-identifier
80
+ def checksum
81
+ total = 0
82
+ first_11.each_char do |c|
83
+ total = (total + c.to_i) * 2
84
+ end
85
+
86
+ remainder = total % 11
87
+ result = (12 - remainder) % 11
88
+ end
89
+
90
+
91
+ def numeric?(s)
92
+ Float(s) != nil rescue false
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,161 @@
1
+ require "find"
2
+ require "yaml"
3
+ require "rugged"
4
+ require "linguist"
5
+
6
+ module Theoj
7
+ class Paper
8
+ include Theoj::Git
9
+
10
+ attr_accessor :review_issue
11
+ attr_accessor :repository
12
+ attr_accessor :paper_path
13
+ attr_accessor :branch
14
+ attr_accessor :paper_metadata
15
+
16
+ def initialize(repository_url, branch, path = nil)
17
+ @repository = repository_url
18
+ @branch = branch
19
+ find_paper path
20
+ load_metadata
21
+ end
22
+
23
+ def authors
24
+ @authors ||= parse_authors
25
+ end
26
+
27
+ def citation_author
28
+ surname = authors.first.last_name
29
+ initials = authors.first.initials
30
+
31
+ if authors.size > 1
32
+ return "#{surname} et al."
33
+ else
34
+ return "#{surname}, #{initials}"
35
+ end
36
+ end
37
+
38
+ def title
39
+ @paper_metadata["title"]
40
+ end
41
+
42
+ def tags
43
+ @paper_metadata["tags"]
44
+ end
45
+
46
+ def date
47
+ @paper_metadata["date"]
48
+ end
49
+
50
+ def languages
51
+ @languages ||= detect_languages
52
+ end
53
+
54
+ def bibliography_path
55
+ @paper_metadata["bibliography"]
56
+ end
57
+
58
+ def local_path
59
+ @local_path ||= "tmp/#{SecureRandom.hex}"
60
+ end
61
+
62
+ def cleanup
63
+ FileUtils.rm_rf(local_path) if Dir.exist?(local_path)
64
+ end
65
+
66
+ def self.find_paper_path(search_path)
67
+ paper_path = nil
68
+
69
+ if Dir.exist? search_path
70
+ Find.find(search_path).each do |path|
71
+ if path =~ /paper\.tex$|paper\.md$/
72
+ paper_path = path
73
+ break
74
+ end
75
+ end
76
+ end
77
+
78
+ paper_path
79
+ end
80
+
81
+ def self.from_repo(repository_url, branch = "")
82
+ Paper.new(repository_url, branch, nil)
83
+ end
84
+
85
+ private
86
+
87
+ def find_paper(path)
88
+ if path.to_s.strip.empty?
89
+ setup_local_repo
90
+ @paper_path = Theoj::Paper.find_paper_path(local_path)
91
+ else
92
+ @paper_path = path
93
+ end
94
+ end
95
+
96
+ def setup_local_repo
97
+ msg_no_repo = "Downloading of the repository failed. Please make sure the URL is correct."
98
+ msg_no_branch = "Couldn't check the bibtex because branch name is incorrect: #{branch.to_s}"
99
+
100
+ error = clone_repo(repository, local_path) ? nil : msg_no_repo
101
+ (error = change_branch(branch, local_path) ? nil : msg_no_branch) unless error
102
+
103
+ failure(error) if error
104
+ error.nil?
105
+ end
106
+
107
+ def load_metadata
108
+ @paper_metadata ||= if paper_path.nil?
109
+ {}
110
+ elsif paper_path.include?('.tex')
111
+ YAML.load_file(paper_path.gsub('.tex', '.yml'))
112
+ else
113
+ YAML.load_file(paper_path)
114
+ end
115
+ end
116
+
117
+ def parse_authors
118
+ parsed_authors = []
119
+ authors_metadata = @paper_metadata['authors']
120
+ affiliations_metadata = parse_affiliations(@paper_metadata['affiliations'])
121
+
122
+ # Loop through the authors block and build up the affiliation
123
+ authors_metadata.each do |author|
124
+ author['name'] = author.dup if author['name'].nil?
125
+ affiliation_index = author['affiliation']
126
+ failure "Author (#{author['name']}) is missing affiliation" if affiliation_index.nil?
127
+ begin
128
+ parsed_author = Author.new(author['name'], author['orcid'], affiliation_index, affiliations_metadata)
129
+ rescue Exception => e
130
+ failure(e.message)
131
+ end
132
+ parsed_authors << parsed_author
133
+ end
134
+
135
+ parsed_authors
136
+ end
137
+
138
+ def parse_affiliations(affiliations_yaml)
139
+ affiliations_metadata = {}
140
+
141
+ affiliations_yaml.each do |affiliation|
142
+ affiliations_metadata[affiliation['index']] = affiliation['name']
143
+ end
144
+
145
+ affiliations_metadata
146
+ end
147
+
148
+ def detect_languages
149
+ repo = Rugged::Repository.discover(paper_path)
150
+ project = Linguist::Repository.new(repo, repo.head.target_id)
151
+
152
+ # Take top five languages from Linguist
153
+ project.languages.keys.take(5)
154
+ end
155
+
156
+ def failure(msg)
157
+ cleanup
158
+ raise Theoj::Error, msg
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,46 @@
1
+ require "faraday"
2
+ require "yaml"
3
+ require "json"
4
+
5
+ module Theoj
6
+ class PublishedPaper
7
+ include Theoj::Git
8
+
9
+ attr_accessor :metadata
10
+
11
+ def initialize(doi)
12
+ doi_url = "https://doi.org/#{doi}"
13
+ doi_response = Faraday.get(doi_url)
14
+ if doi_response.status == 302
15
+ paper_url = doi_response.headers[:location]
16
+ else
17
+ raise Theoj::Error, "The DOI is invalid, url does not resolve #{doi_url}"
18
+ end
19
+
20
+ paper_data = Faraday.get(paper_url + ".json")
21
+ if paper_data.status == 200
22
+ @metadata = JSON.parse(paper_data.body, symbolize_names: true)
23
+ else
24
+ raise Theoj::Error, "Could not find the paper data at #{paper_url + ".json"}"
25
+ end
26
+ end
27
+
28
+ [:title, :state, :submitted_at, :doi, :published_at,
29
+ :volume, :issue, :year, :page, :authors, :editor,
30
+ :editor_name, :editor_url, :editor_orcid, :reviewers,
31
+ :languages, :tags, :software_repository, :paper_review,
32
+ :pdf_url, :software_archive].each do |method_name|
33
+ define_method(method_name) { metadata[method_name] }
34
+ define_method("#{method_name}=") {|value| @metadata[method_name] = value }
35
+ end
36
+
37
+ def yaml_metadata
38
+ metadata.transform_keys(&:to_s).to_yaml
39
+ end
40
+
41
+ def json_metadata
42
+ metadata.to_json
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,92 @@
1
+ require "securerandom"
2
+
3
+ module Theoj
4
+ class ReviewIssue
5
+ include Theoj::GitHub
6
+
7
+ attr_accessor :issue_id
8
+ attr_accessor :repository
9
+ attr_accessor :paper
10
+ attr_accessor :local_path
11
+
12
+ def initialize(repository, issue_id, access_token=nil)
13
+ @issue_id = issue_id
14
+ @repository = repository
15
+ @github_access_token = access_token
16
+ end
17
+
18
+ def issue_body
19
+ @issue_body ||= issue(repository, issue_id).body
20
+ end
21
+
22
+ def paper
23
+ @paper ||= Theoj::Paper.new(target_repository, paper_branch)
24
+ end
25
+
26
+ def target_repository
27
+ @target_repository ||= read_value_from_body("target-repository")
28
+ end
29
+
30
+ def reviewers
31
+ @reviewers ||= read_value_from_body("reviewers-list").split(",").map{|r| r.strip} - ["Pending", "TBD"]
32
+ end
33
+
34
+ def editor
35
+ @editor ||= read_value_from_body("editor")
36
+ end
37
+
38
+ #@NeuroLibre -- START
39
+ # Removes @archive, uses the following instead.
40
+ def book_exec_url
41
+ @book_exec_url ||= read_value_from_body("book-exec-url")
42
+ end
43
+
44
+ def repository_archive
45
+ @repository_archive ||= read_value_from_body("repository-archive")
46
+ end
47
+
48
+ def data_archive
49
+ @data_archive ||= read_value_from_body("data-archive")
50
+ end
51
+
52
+ def book_archive
53
+ @book_archive ||= read_value_from_body("book-archive")
54
+ end
55
+
56
+ def docker_archive
57
+ @docker_archive ||= read_value_from_body("docker-archive")
58
+ end
59
+ #@NeuroLibre -- END
60
+
61
+ def version
62
+ @version ||= read_value_from_body("version")
63
+ end
64
+
65
+ def paper_branch
66
+ @paper_branch ||= read_value_from_body("branch")
67
+ end
68
+
69
+ def value_for(value_name)
70
+ read_value_from_body(value_name)
71
+ end
72
+
73
+ private
74
+
75
+ def read_from_body(start_mark, end_mark)
76
+ text = ""
77
+ issue_body.match(/#{start_mark}(.*)#{end_mark}/im) do |m|
78
+ text = m[1]
79
+ end
80
+
81
+ text = "" if ["Pending", "TBD"].include?(text.strip)
82
+ text.strip
83
+ end
84
+
85
+ # Read value in issue's body between HTML comments
86
+ def read_value_from_body(value_name)
87
+ start_mark = "<!--#{value_name}-->"
88
+ end_mark = "<!--end-#{value_name}-->"
89
+ read_from_body(start_mark, end_mark)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,233 @@
1
+ require "json"
2
+ require "date"
3
+ require "base64"
4
+ require "faraday"
5
+ require "commonmarker"
6
+
7
+ module Theoj
8
+ class Submission
9
+ include Theoj::GitHub
10
+
11
+ attr_accessor :journal
12
+ attr_accessor :review_issue
13
+ attr_accessor :paper
14
+
15
+ def initialize(journal, review_issue, paper=nil)
16
+ @journal = journal
17
+ @review_issue = review_issue
18
+ @paper = paper || @review_issue.paper
19
+ end
20
+
21
+ # @NeuroLibre -- START
22
+ # Removes archive_doi, adds neurolibre reproducibility assets DOIs.
23
+ def deposit_payload
24
+ {
25
+ id: metadata_info[:review_issue_id],
26
+ metadata: Base64.encode64(metadata_payload),
27
+ doi: metadata_info[:doi],
28
+ repository_doi: metadata_info[:repository_doi],
29
+ book_exec_url: metadata_info[:book_exec_url],
30
+ data_doi: metadata_info[:data_doi],
31
+ book_doi: metadata_info[:book_doi],
32
+ docker_doi: metadata_info[:docker_doi],
33
+ citation_string: citation_string,
34
+ title: metadata_info[:title]
35
+ }
36
+ end
37
+ # @NeuroLibre -- END
38
+
39
+ # Create a metadata json payload
40
+ # @NeuroLibre -- START
41
+ # Removes archive_doi, adds neurolibre reproducibility assets DOIs.
42
+ def metadata_payload
43
+ {
44
+ paper: {
45
+ title: metadata_info[:title],
46
+ tags: metadata_info[:tags],
47
+ languages: metadata_info[:languages],
48
+ authors: metadata_info[:authors],
49
+ doi: metadata_info[:doi],
50
+
51
+ repository_doi: metadata_info[:repository_doi],
52
+ book_exec_url: metadata_info[:book_exec_url],
53
+ data_doi: metadata_info[:data_doi],
54
+ book_doi: metadata_info[:book_doi],
55
+ docker_doi: metadata_info[:docker_doi],
56
+
57
+ repository_address: metadata_info[:software_repository_url],
58
+ editor: metadata_info[:review_editor],
59
+ reviewers: metadata_info[:reviewers].collect(&:strip),
60
+ volume: metadata_info[:volume],
61
+ issue: metadata_info[:issue],
62
+ year: metadata_info[:year],
63
+ page: metadata_info[:page]
64
+ }
65
+ }.to_json
66
+ end
67
+ # @NeuroLibre -- END
68
+
69
+ # Create metadata used to generate PDF/JATS outputs
70
+ # @NeuroLibre -- START
71
+ # Removes archive_doi, adds neurolibre reproducibility assets DOIs.
72
+ def article_metadata
73
+ {
74
+ title: metadata_info[:title],
75
+ tags: metadata_info[:tags],
76
+ authors: metadata_info[:authors],
77
+ doi: metadata_info[:doi],
78
+ software_repository_url: metadata_info[:software_repository_url],
79
+ reviewers: metadata_info[:reviewers].collect{|r| user_login(r)},
80
+ volume: metadata_info[:volume],
81
+ issue: metadata_info[:issue],
82
+ year: metadata_info[:year],
83
+ page: metadata_info[:page],
84
+ journal_alias: metadata_info[:journal_alias],
85
+ software_review_url: metadata_info[:software_review_url],
86
+
87
+ repository_doi: metadata_info[:repository_doi],
88
+ book_exec_url: metadata_info[:book_exec_url],
89
+ data_doi: metadata_info[:data_doi],
90
+ book_doi: metadata_info[:book_doi],
91
+ docker_doi: metadata_info[:docker_doi],
92
+
93
+ citation_string: metadata_info[:citation_string],
94
+ editor: metadata_info[:editor],
95
+ submitted_at: metadata_info[:submitted_at],
96
+ published_at: metadata_info[:published_at]
97
+ }
98
+ end
99
+ # @NeuroLibre -- END
100
+
101
+ def metadata_info
102
+ @metadata_info ||= all_metadata
103
+ end
104
+
105
+ def deposit!(secret)
106
+ parameters = deposit_payload.merge(secret: secret)
107
+ Faraday.post(journal.data[:deposit_url], parameters.to_json, {"Content-Type" => "application/json"})
108
+ end
109
+
110
+ def citation_string
111
+ metadata_info[:citation_string]
112
+ end
113
+
114
+ def paper_id
115
+ journal.paper_id_from_issue(review_issue.issue_id)
116
+ end
117
+
118
+ def paper_doi
119
+ journal.paper_doi_for_id(paper_id)
120
+ end
121
+
122
+ # @NeuroLibre -- START
123
+ # Removes archive_doi, adds neurolibre reproducibility assets DOIs.
124
+ def all_metadata
125
+ metadata = {
126
+ title: plaintext(paper.title),
127
+ tags: paper.tags,
128
+ languages: paper.languages,
129
+ authors: paper.authors.collect { |a| a.to_h },
130
+ doi: paper_doi,
131
+ software_repository_url: review_issue.target_repository,
132
+ review_issue_id: review_issue.issue_id,
133
+ review_editor: review_issue.editor,
134
+ reviewers: review_issue.reviewers,
135
+ volume: journal.current_volume,
136
+ issue: journal.current_issue,
137
+ year: journal.current_year,
138
+ page: review_issue.issue_id,
139
+ journal_alias: journal.alias,
140
+ journal_name: journal.name,
141
+ software_review_url: journal.reviews_repository_url(review_issue.issue_id),
142
+
143
+ # Context changes from archive --> doi
144
+ repository_doi: review_issue.repository_archive,
145
+ data_doi: review_issue.data_archive,
146
+ book_doi: review_issue.book_archive,
147
+ docker_doi: review_issue.docker_archive,
148
+ # Context does not change for this
149
+ book_exec_url: review_issue.book_exec_url,
150
+
151
+ citation_author: paper.citation_author
152
+ }
153
+ # @NeuroLibre -- END
154
+
155
+ metadata.merge!(editor_info, dates_info)
156
+ metadata[:citation_string] = build_citation_string(metadata)
157
+ metadata
158
+ end
159
+
160
+ def editor_info
161
+ editor_info = { editor: {
162
+ github_user: user_login(review_issue.editor),
163
+ name: nil,
164
+ url: nil,
165
+ orcid: nil
166
+ }
167
+ }
168
+
169
+ if review_issue.editor
170
+ editor_lookup = Faraday.get(journal.url + "/editors/lookup/" + user_login(review_issue.editor))
171
+ if editor_lookup.status == 200
172
+ info = JSON.parse(editor_lookup.body, symbolize_names: true)
173
+ editor_info[:editor][:name] = info[:name]
174
+ editor_info[:editor][:url] = info[:url]
175
+ editor_info[:editor][:orcid] = info[:orcid]
176
+ end
177
+ end
178
+
179
+ editor_info
180
+ end
181
+
182
+ def dates_info
183
+ dates_info = { submitted_at: nil, published_at: nil }
184
+
185
+ if review_issue.issue_id
186
+ paper_lookup = Faraday.get(journal.url + "/papers/lookup/" + review_issue.issue_id.to_s)
187
+ if paper_lookup.status == 200
188
+ info = JSON.parse(paper_lookup.body, symbolize_names: true)
189
+ dates_info[:submitted_at] = format_date(info[:submitted]) if info[:submitted]
190
+ dates_info[:published_at] = format_date(info[:accepted]) if info[:accepted]
191
+ end
192
+
193
+ if dates_info[:published_at]
194
+ yvi = journal.year_volume_issue_for_date(Date.parse(dates_info[:published_at]))
195
+ dates_info[:year] = yvi[0]
196
+ dates_info[:volume] = yvi[1]
197
+ dates_info[:issue] = yvi[2]
198
+ end
199
+ end
200
+
201
+ dates_info
202
+ end
203
+
204
+ def track
205
+ track_info = { name: nil, short_name: nil, code: nil, label: nil, parameterized: nil}
206
+
207
+ if review_issue.issue_id
208
+ track_lookup = Faraday.get(journal.url + "/papers/" + review_issue.issue_id.to_s + "/lookup_track" )
209
+ if track_lookup.status == 200
210
+ track_info = JSON.parse(track_lookup.body, symbolize_names: true)
211
+ end
212
+ end
213
+
214
+ track_info
215
+ end
216
+
217
+ private
218
+
219
+ def build_citation_string(metadata)
220
+ "#{metadata[:citation_author]}, (#{metadata[:year]}). #{metadata[:title]}. #{metadata[:journal_name]}, #{metadata[:volume]}(#{metadata[:issue]}), #{metadata[:review_issue_id]}, https://doi.org/#{metadata[:doi]}"
221
+ end
222
+
223
+ def plaintext(t)
224
+ CommonMarker.render_doc(t, :DEFAULT).to_plaintext.strip.gsub(" ", " ").gsub("\n", " ")
225
+ end
226
+
227
+ def format_date(date_string)
228
+ Date.parse(date_string.to_s).strftime("%Y-%m-%d")
229
+ rescue Date::Error
230
+ nil
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,3 @@
1
+ module Theoj
2
+ VERSION = "1.5.3"
3
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: neurolibre
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.5.3
5
+ platform: ruby
6
+ authors:
7
+ - Juanjo Bazán
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-04-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: octokit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday-retry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: openjournals-nameable
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: github-linguist
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '7.24'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '7.24'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rugged
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.5'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: commonmarker
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.23'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.23'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 13.0.6
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 13.0.6
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.11'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.11'
139
+ description: A library to manage editorial objects used in the NeuroLibre's moderation
140
+ and screening process
141
+ email:
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - CHANGELOG.md
147
+ - LICENSE
148
+ - README.md
149
+ - lib/neurolibre.rb
150
+ - lib/theoj/author.rb
151
+ - lib/theoj/git.rb
152
+ - lib/theoj/github.rb
153
+ - lib/theoj/journal.rb
154
+ - lib/theoj/journals_data.rb
155
+ - lib/theoj/orcid.rb
156
+ - lib/theoj/paper.rb
157
+ - lib/theoj/published_paper.rb
158
+ - lib/theoj/review_issue.rb
159
+ - lib/theoj/submission.rb
160
+ - lib/theoj/version.rb
161
+ homepage: http://github.com/neurolibre/neurolibre-gem
162
+ licenses:
163
+ - MIT
164
+ metadata:
165
+ bug_tracker_uri: https://github.com/neurolibre/neurolibre-gem/issues
166
+ changelog_uri: https://github.com/neurolibre/neurolibre-gem/blob/main/CHANGELOG.md
167
+ documentation_uri: https://www.rubydoc.info/gems/neurolibre
168
+ homepage_uri: http://github.com/neurolibre/neurolibre-gem
169
+ source_code_uri: http://github.com/neurolibre/neurolibre-gem
170
+ post_install_message:
171
+ rdoc_options:
172
+ - "--main"
173
+ - README.md
174
+ - "--charset=UTF-8"
175
+ require_paths:
176
+ - lib
177
+ required_ruby_version: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ required_rubygems_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ requirements: []
188
+ rubygems_version: 3.4.6
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Editorial objects used by NeuroLibre
192
+ test_files: []