neurolibre 1.5.3

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.
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: []