theoj 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86f78e023b1c26cfcd13eea306fcb1ee89a862a300e43e173437655fddcc53b9
4
- data.tar.gz: 43c48ae2941a5f72076f048a58315f861aedddd1708a5886b437b1858739553d
3
+ metadata.gz: 984f9feb0bdcfeaf7d9533aa94c09dde7877ca2381e75e60b0f05d73cb390eb7
4
+ data.tar.gz: 749c5b08e99824f720c5ed4e820d45b7d2b772c345f820d4332393376b215512
5
5
  SHA512:
6
- metadata.gz: 1cba25b3701a0f52f40317e74df287db75a4f79944183a13bd1ea23567e51b010cdaa2f6d707aaedaa6e5c2ac30119c1dd7eab3f83e0d107a4d048d247e9ee80
7
- data.tar.gz: 592c523fb973d4f7c57044b451d17038b5a891aa0d534f4081180c4409f5da81ccebd31686c4f98151fbdeea465f61ea2e58df08aa4b1c2770d3af13a71d72d1
6
+ metadata.gz: 5fa3741bb127abcfb1c590c197fa8ea9631b510624b25b615ccfc60131cdf7ce68af2d400ab16f339cc6705a5aae1129403ac0c1d24e41e04209704bdfbcb076
7
+ data.tar.gz: 246621ddee2c0495eccad79723727205b724ec36b4b401b24ae60f22c1e1cb7a8ece320ab2ccefb6a5074e4423a4eabcbf401272d40e137b0eb831bf0dafd662
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0 (2021-10-20)
4
+
5
+ - Added method to Journal to create paper_id from issue_id
6
+ - Added method to Journal to get a DOI based on a paper id
7
+ - Added languages to Paper
8
+ - Added authors info to Paper
9
+ - Author object
10
+ - Added ORCID validation
11
+ - Added Submission object, grouping a paper, a review issue and a journal
12
+ - Added paper depositing
13
+
3
14
  ## 0.0.3 (2021-10-08)
4
15
 
5
16
  - Added metadata methods to Paper
data/lib/theoj/author.rb CHANGED
@@ -1,16 +1,94 @@
1
+ require "nameable"
2
+
1
3
  module Theoj
2
4
  class Author
3
5
  attr_accessor :name
4
6
  attr_accessor :orcid
5
7
  attr_accessor :affiliation
6
8
 
7
- def initialize(name, orcid, affiliation)
8
- @name = name
9
- @orcid = orcid
10
- @affiliation = affiliation
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
+ @parsed_name = Nameable::Latin.new.parse(strip_footnotes(author_name))
49
+ @name = @parsed_name.to_nameable
50
+ end
51
+
52
+ # Input: Arfon Smith^[Corresponding author: arfon@example.com]
53
+ # Output: Arfon Smith
54
+ def strip_footnotes(author_name)
55
+ author_name.to_s[AUTHOR_FOOTNOTE_REGEX]
56
+ end
57
+
58
+ def validate_orcid(author_orcid)
59
+ return nil if author_orcid.to_s.strip.empty?
60
+
61
+ validator = Theoj::Orcid.new(author_orcid)
62
+ if validator.valid?
63
+ return author_orcid.strip
64
+ else
65
+ raise "Problem with ORCID (#{author_orcid}) for #{self.name}. #{validator.error}"
66
+ end
11
67
  end
12
68
 
13
- def validate_orcid
69
+ # Takes the author affiliation index and a hash of all affiliations and
70
+ # associates them. Then builds the author affiliation string
71
+ def build_affiliation_string(index, affiliations_hash)
72
+ return nil if index.nil? # Some authors don't have an affiliation
73
+
74
+ # If multiple affiliations, parse each one and build the affiliation string
75
+ author_affiliations = []
76
+
77
+ # Turn YAML keys into strings so that mixed integer and string affiliations work
78
+ affiliations_hash.transform_keys!(&:to_s)
79
+
80
+ affiliations = index.to_s.split(',').map(&:strip)
81
+
82
+ # Raise if we can't parse the string, might be because of this bug :-(
83
+ # https://bugs.ruby-lang.org/issues/12451
84
+ affiliations.each do |a|
85
+ raise "Problem with affiliations for #{self.name}, perhaps the " +
86
+ "affiliations index need quoting?" unless affiliations_hash.has_key?(a)
87
+
88
+ author_affiliations << affiliations_hash[a].strip
89
+ end
90
+
91
+ author_affiliations.join(', ')
14
92
  end
15
93
 
16
94
  end
data/lib/theoj/git.rb CHANGED
@@ -1,5 +1,5 @@
1
- require 'open3'
2
- require 'fileutils'
1
+ require "open3"
2
+ require "fileutils"
3
3
 
4
4
  module Theoj
5
5
  module Git
data/lib/theoj/journal.rb CHANGED
@@ -25,6 +25,15 @@ module Theoj
25
25
  data[:current_issue] || (1 + ((Time.new.year * 12 + Time.new.month) - (launch_year * 12 + launch_month)))
26
26
  end
27
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
+
28
37
  private
29
38
 
30
39
  def set_data(custom_data)
@@ -6,8 +6,9 @@ module Theoj
6
6
  name: "Journal of Open Source Software",
7
7
  alias: "joss",
8
8
  launch_date: "2016-05-05",
9
- paper_repository: "openjournals/joss-papers",
10
- reviews_repository: "openjournals/joss-reviews"
9
+ papers_repository: "openjournals/joss-papers",
10
+ reviews_repository: "openjournals/joss-reviews",
11
+ deposit_url: "https://joss.theoj.org/papers/api_deposit"
11
12
  },
12
13
  jose: {
13
14
  doi_prefix: "10.21105",
@@ -15,8 +16,9 @@ module Theoj
15
16
  name: "Journal of Open Source Education",
16
17
  alias: "jose",
17
18
  launch_date: "2018-03-07",
18
- paper_repository: "openjournals/jose-papers",
19
- reviews_repository: "openjournals/jose-reviews"
19
+ papers_repository: "openjournals/jose-papers",
20
+ reviews_repository: "openjournals/jose-reviews",
21
+ deposit_url: "https://joss.theoj.org/papers/api_deposit"
20
22
  }
21
23
  }
22
24
  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
data/lib/theoj/paper.rb CHANGED
@@ -1,5 +1,7 @@
1
- require 'find'
2
- require 'yaml'
1
+ require "find"
2
+ require "yaml"
3
+ require "rugged"
4
+ require "linguist"
3
5
 
4
6
  module Theoj
5
7
  class Paper
@@ -19,22 +21,18 @@ module Theoj
19
21
  end
20
22
 
21
23
  def authors
22
- []
24
+ @authors ||= parse_authors
23
25
  end
24
26
 
25
- def self.find_paper_path(search_path)
26
- paper_path = nil
27
+ def citation_author
28
+ surname = authors.first.last_name
29
+ initials = authors.first.initials
27
30
 
28
- if Dir.exist? search_path
29
- Find.find(search_path).each do |path|
30
- if path =~ /paper\.tex$|paper\.md$/
31
- paper_path = path
32
- break
33
- end
34
- end
31
+ if authors.size > 1
32
+ return "#{surname} et al."
33
+ else
34
+ return "#{surname}, #{initials}"
35
35
  end
36
-
37
- paper_path
38
36
  end
39
37
 
40
38
  def title
@@ -49,6 +47,10 @@ module Theoj
49
47
  @paper_metadata["date"]
50
48
  end
51
49
 
50
+ def languages
51
+ @languages ||= detect_languages
52
+ end
53
+
52
54
  def bibliography_path
53
55
  @paper_metadata["bibliography"]
54
56
  end
@@ -61,6 +63,21 @@ module Theoj
61
63
  FileUtils.rm_rf(local_path) if Dir.exist?(local_path)
62
64
  end
63
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
+
64
81
  def self.from_repo(repository_url, branch = "")
65
82
  Paper.new(repository_url, branch, nil)
66
83
  end
@@ -95,6 +112,44 @@ module Theoj
95
112
  end
96
113
  end
97
114
 
115
+ def parse_authors
116
+ parsed_authors = []
117
+ authors_metadata = @paper_metadata['authors']
118
+ affiliations_metadata = parse_affiliations(@paper_metadata['affiliations'])
119
+
120
+ # Loop through the authors block and build up the affiliation
121
+ authors_metadata.each do |author|
122
+ affiliation_index = author['affiliation']
123
+ failure "Author (#{author['name']}) is missing affiliation" if affiliation_index.nil?
124
+ begin
125
+ parsed_author = Author.new(author['name'], author['orcid'], affiliation_index, affiliations_metadata)
126
+ rescue Exception => e
127
+ failure(e.message)
128
+ end
129
+ parsed_authors << parsed_author
130
+ end
131
+
132
+ parsed_authors
133
+ end
134
+
135
+ def parse_affiliations(affliations_yaml)
136
+ affliations_metadata = {}
137
+
138
+ affliations_yaml.each do |affiliation|
139
+ affliations_metadata[affiliation['index']] = affiliation['name']
140
+ end
141
+
142
+ affliations_metadata
143
+ end
144
+
145
+ def detect_languages
146
+ repo = Rugged::Repository.discover(paper_path)
147
+ project = Linguist::Repository.new(repo, repo.head.target_id)
148
+
149
+ # Take top five languages from Linguist
150
+ project.languages.keys.take(5)
151
+ end
152
+
98
153
  def failure(msg)
99
154
  cleanup
100
155
  raise(msg)
@@ -0,0 +1,70 @@
1
+ require "json"
2
+ require "base64"
3
+ require "faraday"
4
+
5
+ module Theoj
6
+ class Submission
7
+ attr_accessor :journal
8
+ attr_accessor :review_issue
9
+ attr_accessor :paper
10
+
11
+ def initialize(journal, review_issue, paper=nil)
12
+ @journal = journal
13
+ @review_issue = review_issue
14
+ @paper = paper || @review_issue.paper
15
+ end
16
+
17
+ # Create the payload to use to post for depositing with Open Journals
18
+ def deposit_payload
19
+ {
20
+ id: review_issue.issue_id,
21
+ metadata: Base64.encode64(metadata_payload),
22
+ doi: paper_doi,
23
+ archive_doi: review_issue.archive,
24
+ citation_string: citation_string,
25
+ title: paper.title
26
+ }
27
+ end
28
+
29
+ # Create a metadata json payload
30
+ def metadata_payload
31
+ metadata = {
32
+ paper: {
33
+ title: paper.title,
34
+ tags: paper.tags,
35
+ languages: paper.languages,
36
+ authors: paper.authors.collect { |a| a.to_h },
37
+ doi: paper_doi,
38
+ archive_doi: review_issue.archive,
39
+ repository_address: review_issue.target_repository,
40
+ editor: review_issue.editor,
41
+ reviewers: review_issue.reviewers.collect(&:strip),
42
+ volume: journal.current_volume,
43
+ issue: journal.current_issue,
44
+ year: journal.current_year,
45
+ page: review_issue.issue_id,
46
+ }
47
+ }
48
+
49
+ metadata.to_json
50
+ end
51
+
52
+ def deposit!(secret)
53
+ parameters = deposit_payload.merge(secret: secret)
54
+ Faraday.post(journal.data[:deposit_url], parameters.to_json, {"Content-Type" => "application/json"})
55
+ end
56
+
57
+ def citation_string
58
+ paper_year = Time.now.strftime('%Y')
59
+ "#{paper.citation_author}, (#{paper_year}). #{paper.title}. #{journal.name}, #{journal.current_volume}(#{journal.current_issue}), #{review_issue.issue_id}, https://doi.org/#{paper_doi}"
60
+ end
61
+
62
+ def paper_id
63
+ journal.paper_id_from_issue(review_issue.issue_id)
64
+ end
65
+
66
+ def paper_doi
67
+ journal.paper_doi_for_id(paper_id)
68
+ end
69
+ end
70
+ end
data/lib/theoj/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Theoj
2
- VERSION = "0.0.3"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/theoj.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require_relative "theoj/version"
2
2
  require_relative "theoj/git"
3
3
  require_relative "theoj/github"
4
+ require_relative "theoj/orcid"
5
+ require_relative "theoj/submission"
4
6
  require_relative "theoj/journal"
5
7
  require_relative "theoj/review_issue"
6
8
  require_relative "theoj/paper"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: theoj
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juanjo Bazán
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-08 00:00:00.000000000 Z
11
+ date: 2021-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: octokit
@@ -24,6 +24,62 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.21'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: openjournals-nameable
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: github-linguist
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rugged
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
27
83
  - !ruby/object:Gem::Dependency
28
84
  name: rake
29
85
  requirement: !ruby/object:Gem::Requirement
@@ -68,8 +124,10 @@ files:
68
124
  - lib/theoj/github.rb
69
125
  - lib/theoj/journal.rb
70
126
  - lib/theoj/journals_data.rb
127
+ - lib/theoj/orcid.rb
71
128
  - lib/theoj/paper.rb
72
129
  - lib/theoj/review_issue.rb
130
+ - lib/theoj/submission.rb
73
131
  - lib/theoj/version.rb
74
132
  homepage: http://github.com/xuanxu/theoj
75
133
  licenses: