roboneuro 0.1.1
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 +7 -0
- data/.DS_Store +0 -0
- data/.env-example +12 -0
- data/.env.test +8 -0
- data/.github/workflows/docker-image.yml +63 -0
- data/.github/workflows/tests.yml +28 -0
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +135 -0
- data/LICENSE +21 -0
- data/README.md +190 -0
- data/Rakefile +13 -0
- data/bin/whedon +131 -0
- data/fixtures/latex_paper/paper.tex +0 -0
- data/fixtures/paper/10.21105.jcon.00017.crossref.xml +88 -0
- data/fixtures/paper/crossref-metadata.yaml +34 -0
- data/fixtures/paper/paper-bib.md +27 -0
- data/fixtures/paper/paper-single-author.md +24 -0
- data/fixtures/paper/paper-with-harder-names.md +27 -0
- data/fixtures/paper/paper-with-missing-affiliations.md +21 -0
- data/fixtures/paper/paper.bib +65 -0
- data/fixtures/paper/paper.html +14 -0
- data/fixtures/paper/paper.md +27 -0
- data/fixtures/paper/paper.pdf +0 -0
- data/fixtures/paper/paper.xml +28 -0
- data/fixtures/paper/paper_with_missing_title.md +26 -0
- data/fixtures/review_body.txt +48 -0
- data/fixtures/test_paper/nested/paper.bib +0 -0
- data/fixtures/test_paper/nested/paper.md +0 -0
- data/fixtures/test_paper/paper.bib +0 -0
- data/fixtures/test_paper/paper.md +0 -0
- data/fixtures/vcr_cassettes/joss-lookup.yml +58 -0
- data/fixtures/vcr_cassettes/review.yml +237 -0
- data/fixtures/vcr_cassettes/reviews.yml +270 -0
- data/lib/whedon/auditor.rb +39 -0
- data/lib/whedon/author.rb +81 -0
- data/lib/whedon/bibtex_parser.rb +103 -0
- data/lib/whedon/compilers.rb +379 -0
- data/lib/whedon/github.rb +12 -0
- data/lib/whedon/orcid_validator.rb +83 -0
- data/lib/whedon/processor.rb +178 -0
- data/lib/whedon/review.rb +25 -0
- data/lib/whedon/reviews.rb +29 -0
- data/lib/whedon/version.rb +3 -0
- data/lib/whedon.rb +387 -0
- data/paperdraft.Dockerfile +48 -0
- data/pkg/roboneuro-0.1.0.gem +0 -0
- data/resources/.DS_Store +0 -0
- data/resources/NeuroLibre/.DS_Store +0 -0
- data/resources/NeuroLibre/aas-logo.png +0 -0
- data/resources/NeuroLibre/apa.csl +1919 -0
- data/resources/NeuroLibre/defaults.yaml +14 -0
- data/resources/NeuroLibre/latex.template +541 -0
- data/resources/NeuroLibre/logo.png +0 -0
- data/resources/NeuroLibre/logo_link.png +0 -0
- data/resources/apa.csl +1919 -0
- data/resources/crossref.template +89 -0
- data/resources/docker-defaults.yaml +42 -0
- data/resources/docker-entrypoint.sh +37 -0
- data/resources/jats.csl +204 -0
- data/resources/jats.template +105 -0
- data/resources/jose/apa.csl +1919 -0
- data/resources/jose/defaults.yaml +14 -0
- data/resources/jose/latex.template +486 -0
- data/resources/jose/logo.png +0 -0
- data/resources/joss/aas-logo.png +0 -0
- data/resources/joss/apa.csl +1919 -0
- data/resources/joss/defaults.yaml +14 -0
- data/resources/joss/latex.template +525 -0
- data/resources/joss/logo.png +0 -0
- data/resources/latex.template +485 -0
- data/resources/time.lua +8 -0
- data/roboneuro.gemspec +39 -0
- data/spec/auditor_spec.rb +22 -0
- data/spec/author_spec.rb +17 -0
- data/spec/bibtex_spec.rb +31 -0
- data/spec/orcid_validator_spec.rb +33 -0
- data/spec/processor_spec.rb +66 -0
- data/spec/review_spec.rb +12 -0
- data/spec/reviews_spec.rb +18 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/whedon_spec.rb +93 -0
- metadata +386 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
require_relative 'github'
|
2
|
+
|
3
|
+
require 'restclient'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module Whedon
|
8
|
+
class Processor
|
9
|
+
include GitHub
|
10
|
+
include Compilers
|
11
|
+
|
12
|
+
attr_accessor :review_issue_id
|
13
|
+
attr_accessor :review_body
|
14
|
+
attr_accessor :repository_address
|
15
|
+
attr_accessor :archive_doi
|
16
|
+
attr_accessor :paper_path
|
17
|
+
attr_accessor :xml_path
|
18
|
+
attr_accessor :doi_batch_id
|
19
|
+
attr_accessor :paper
|
20
|
+
attr_accessor :current_volume
|
21
|
+
attr_accessor :current_issue
|
22
|
+
attr_accessor :current_year
|
23
|
+
attr_accessor :custom_path
|
24
|
+
|
25
|
+
def initialize(review_issue_id, review_body, custom_path=nil)
|
26
|
+
@review_issue_id = review_issue_id
|
27
|
+
@review_body = review_body
|
28
|
+
@repository_address = review_body[REPO_REGEX]
|
29
|
+
@archive_doi = review_body[ARCHIVE_REGEX]
|
30
|
+
@custom_path = custom_path
|
31
|
+
# Probably a much nicer way to do this...
|
32
|
+
@current_year = ENV["CURRENT_YEAR"].nil? ? Time.new.year : ENV["CURRENT_YEAR"]
|
33
|
+
@current_volume = ENV["CURRENT_VOLUME"].nil? ? Time.new.year - (Time.parse(ENV['JOURNAL_LAUNCH_DATE']).year - 1) : ENV["CURRENT_VOLUME"]
|
34
|
+
@current_issue = ENV["CURRENT_ISSUE"].nil? ? 1 + ((Time.new.year * 12 + Time.new.month) - (Time.parse(ENV['JOURNAL_LAUNCH_DATE']).year * 12 + Time.parse(ENV['JOURNAL_LAUNCH_DATE']).month)) : ENV["CURRENT_ISSUE"]
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_paper(path)
|
38
|
+
@paper = Whedon::Paper.new(review_issue_id, custom_path, path)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Clone the repository... (assumes it's git)
|
42
|
+
def clone
|
43
|
+
repository_address = review_body[REPO_REGEX]
|
44
|
+
|
45
|
+
# Optionally set the path to work in
|
46
|
+
path = custom_path ? custom_path : "tmp/#{review_issue_id}"
|
47
|
+
|
48
|
+
# Skip if the repo has already been cloned
|
49
|
+
if File.exists?("#{path}/.git")
|
50
|
+
puts "Looks like Git repo already exists at #{path}"
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
# First make the folder
|
55
|
+
FileUtils::mkdir_p("#{path}")
|
56
|
+
|
57
|
+
# Then clone the repository
|
58
|
+
`git clone #{repository_address} #{path}`
|
59
|
+
end
|
60
|
+
|
61
|
+
# Find possible papers to be compiled
|
62
|
+
def find_paper_paths(search_path=nil)
|
63
|
+
search_path ||= "tmp/#{review_issue_id}"
|
64
|
+
paper_paths = []
|
65
|
+
|
66
|
+
Find.find(search_path) do |path|
|
67
|
+
paper_paths << path if path =~ /\bpaper\.tex$|\bpaper\.md$/
|
68
|
+
end
|
69
|
+
|
70
|
+
return paper_paths
|
71
|
+
end
|
72
|
+
|
73
|
+
# Find possible bibtex to be compiled
|
74
|
+
def find_bib_path(search_path=nil)
|
75
|
+
search_path ||= "tmp/#{review_issue_id}"
|
76
|
+
bib_paths = []
|
77
|
+
|
78
|
+
Find.find(search_path) do |path|
|
79
|
+
bib_paths << path if path =~ /paper.bib$/
|
80
|
+
end
|
81
|
+
|
82
|
+
return bib_paths
|
83
|
+
end
|
84
|
+
|
85
|
+
# Find XML paper
|
86
|
+
def find_xml_paths(search_path=nil)
|
87
|
+
search_path ||= "tmp/#{review_issue_id}"
|
88
|
+
xml_paths = []
|
89
|
+
|
90
|
+
Find.find(search_path) do |path|
|
91
|
+
xml_paths << path if path =~ /paper\.xml$/
|
92
|
+
end
|
93
|
+
|
94
|
+
return xml_paths
|
95
|
+
end
|
96
|
+
|
97
|
+
# Try and compile the paper target
|
98
|
+
def compile
|
99
|
+
generate_pdf(nil, false)
|
100
|
+
generate_crossref
|
101
|
+
# generate_jats
|
102
|
+
end
|
103
|
+
|
104
|
+
def citation_string
|
105
|
+
paper_year ||= Time.now.strftime('%Y')
|
106
|
+
paper_issue ||= @current_issue
|
107
|
+
paper_volume ||= @current_volume
|
108
|
+
|
109
|
+
return "#{paper.citation_author}, (#{paper_year}). #{paper.plain_title}. #{ENV['JOURNAL_NAME']}, #{paper_volume}(#{paper_issue}), #{paper.review_issue_id}, https://doi.org/#{paper.formatted_doi}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def deposit
|
113
|
+
crossref_deposit
|
114
|
+
joss_deposit
|
115
|
+
|
116
|
+
puts "p=dat #{@review_issue_id};p.doi='#{paper.formatted_doi}';"\
|
117
|
+
"p.archive_doi=#{archive_doi};p.accepted_at=Time.now;"\
|
118
|
+
"p.citation_string='#{citation_string}';"\
|
119
|
+
"p.authors='#{paper.authors_string}';p.title='#{paper.title}';"
|
120
|
+
end
|
121
|
+
|
122
|
+
def joss_deposit
|
123
|
+
puts "Depositing with JOSS..."
|
124
|
+
request = RestClient::Request.new(
|
125
|
+
:method => :post,
|
126
|
+
:url => "#{ENV['JOURNAL_URL']}/papers/api_deposit",
|
127
|
+
:payload => {
|
128
|
+
:id => paper.review_issue_id,
|
129
|
+
:metadata => Base64.encode64(paper.deposit_payload.to_json),
|
130
|
+
:doi => paper.formatted_doi,
|
131
|
+
:archive_doi => archive_doi,
|
132
|
+
:citation_string => citation_string,
|
133
|
+
:title => paper.plain_title,
|
134
|
+
:secret => ENV['WHEDON_SECRET']
|
135
|
+
})
|
136
|
+
|
137
|
+
response = request.execute
|
138
|
+
if response.code == 201
|
139
|
+
puts "Deposit looks good."
|
140
|
+
else
|
141
|
+
puts "Something went wrong with this deposit."
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def crossref_deposit
|
146
|
+
if File.exists?("#{paper.directory}/#{paper.filename_doi}.crossref.xml")
|
147
|
+
puts "Depositing with Crossref..."
|
148
|
+
request = RestClient::Request.new(
|
149
|
+
:method => :post,
|
150
|
+
:url => "https://doi.crossref.org/servlet/deposit",
|
151
|
+
:payload => {
|
152
|
+
:multipart => true,
|
153
|
+
:fname => File.new("#{paper.directory}/#{paper.filename_doi}.crossref.xml", 'rb'),
|
154
|
+
:login_id => ENV['CROSSREF_USERNAME'],
|
155
|
+
:login_passwd => ENV['CROSSREF_PASSWORD']
|
156
|
+
})
|
157
|
+
|
158
|
+
response = request.execute
|
159
|
+
if response.code == 200
|
160
|
+
puts "Deposit looks good. Check your email!"
|
161
|
+
else
|
162
|
+
puts "Something went wrong with this deposit."
|
163
|
+
end
|
164
|
+
else
|
165
|
+
puts "Can't deposit Crossref metadata - deposit XML is missing"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# http://www.crossref.org/help/schema_doc/4.3.7/4.3.7.html
|
170
|
+
# Publisher generated ID that uniquely identifies the DOI submission
|
171
|
+
# batch. It will be used as a reference in error messages sent by the MDDB, and can be
|
172
|
+
# used for submission tracking. The publisher must insure that this number is unique
|
173
|
+
# for every submission to CrossRef.
|
174
|
+
def generate_doi_batch_id
|
175
|
+
@doi_batch_id = SecureRandom.hex
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Whedon::Review is a helper class that makes it easier to work with GitHub
|
2
|
+
# review issues.
|
3
|
+
|
4
|
+
require_relative 'github'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'find'
|
7
|
+
|
8
|
+
module Whedon
|
9
|
+
class Review
|
10
|
+
include GitHub
|
11
|
+
|
12
|
+
attr_accessor :review_issue_id
|
13
|
+
attr_accessor :review_repository
|
14
|
+
|
15
|
+
def initialize(review_issue_id, repository = nil)
|
16
|
+
@review_issue_id = review_issue_id
|
17
|
+
@review_repository = repository.nil? ? ENV['REVIEW_REPOSITORY'] : repository
|
18
|
+
end
|
19
|
+
|
20
|
+
def issue_body
|
21
|
+
review = client.issue(review_repository, review_issue_id)
|
22
|
+
return review.body
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'github'
|
2
|
+
|
3
|
+
module Whedon
|
4
|
+
class Reviews
|
5
|
+
include GitHub
|
6
|
+
|
7
|
+
attr_accessor :review_repository_url
|
8
|
+
|
9
|
+
def initialize(review_repository_url)
|
10
|
+
@review_repository_url = review_repository_url
|
11
|
+
end
|
12
|
+
|
13
|
+
def list_current
|
14
|
+
reviews = Hash.new()
|
15
|
+
|
16
|
+
current_reviews = client.list_issues(@review_repository_url)
|
17
|
+
return nil if current_reviews.empty?
|
18
|
+
|
19
|
+
current_reviews.each do |issue|
|
20
|
+
reviews[issue.number] = {
|
21
|
+
:opened_at => issue.created_at,
|
22
|
+
:url => issue.html_url
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
return reviews
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/whedon.rb
ADDED
@@ -0,0 +1,387 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'linguist'
|
3
|
+
require 'octokit'
|
4
|
+
require 'redcarpet'
|
5
|
+
require 'redcarpet/render_strip'
|
6
|
+
require 'time'
|
7
|
+
|
8
|
+
require 'dotenv'
|
9
|
+
Dotenv.load
|
10
|
+
|
11
|
+
require_relative 'whedon/auditor'
|
12
|
+
require_relative 'whedon/author'
|
13
|
+
require_relative 'whedon/bibtex_parser'
|
14
|
+
require_relative 'whedon/compilers'
|
15
|
+
require_relative 'whedon/github'
|
16
|
+
require_relative 'whedon/orcid_validator'
|
17
|
+
require_relative 'whedon/processor'
|
18
|
+
require_relative 'whedon/review'
|
19
|
+
require_relative 'whedon/reviews'
|
20
|
+
require_relative 'whedon/version'
|
21
|
+
|
22
|
+
module Whedon
|
23
|
+
|
24
|
+
def self.root
|
25
|
+
File.dirname __dir__
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.resources
|
29
|
+
File.join root, 'resources'
|
30
|
+
end
|
31
|
+
|
32
|
+
AUTHOR_REGEX = /(?<=\*\*Submitting author:\*\*\s)(\S+)/
|
33
|
+
REPO_REGEX = /(?<=\*\*Repository:\*\*.<a\shref=)"(.*?)"/
|
34
|
+
VERSION_REGEX = /(?<=\*\*Version:\*\*\s)(\S+)/
|
35
|
+
ARCHIVE_REGEX = /(?<=\*\*Archive:\*\*.<a\shref=)"(.*?)"/
|
36
|
+
|
37
|
+
class Paper
|
38
|
+
include GitHub
|
39
|
+
|
40
|
+
attr_accessor :review_issue_id
|
41
|
+
attr_accessor :review_repository
|
42
|
+
attr_accessor :review_issue_body
|
43
|
+
attr_accessor :title, :tags, :authors, :date, :paper_path, :bibliography_path, :custom_path, :languages, :reviewers, :editor
|
44
|
+
|
45
|
+
# TODO: work out how to resolve this code duplication with lib/processor.rb
|
46
|
+
attr_accessor :current_volume
|
47
|
+
attr_accessor :current_issue
|
48
|
+
attr_accessor :current_year
|
49
|
+
|
50
|
+
EXPECTED_MARKDOWN_FIELDS = %w{
|
51
|
+
title
|
52
|
+
tags
|
53
|
+
authors
|
54
|
+
affiliations
|
55
|
+
date
|
56
|
+
bibliography
|
57
|
+
}
|
58
|
+
|
59
|
+
EXPECTED_LATEX_FIELDS = %w{
|
60
|
+
title
|
61
|
+
keywords
|
62
|
+
authors
|
63
|
+
affiliations
|
64
|
+
date
|
65
|
+
bibliography
|
66
|
+
}
|
67
|
+
|
68
|
+
def self.list
|
69
|
+
reviews = Whedon::Reviews.new(ENV['REVIEW_REPOSITORY']).list_current
|
70
|
+
return "No open reviews" if reviews.nil?
|
71
|
+
|
72
|
+
reviews.each do |issue_id, vals|
|
73
|
+
puts "#{issue_id}: #{vals[:url]} (#{vals[:opened_at]})"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Initialized with JOSS paper including YAML header
|
78
|
+
# e.g. http://joss.theoj.org/about#paper_structure
|
79
|
+
# Optionally return early if no paper_path is set
|
80
|
+
def initialize(review_issue_id, custom_path=nil, paper_path=nil)
|
81
|
+
@review_issue_id = review_issue_id
|
82
|
+
@review_repository = ENV['REVIEW_REPOSITORY']
|
83
|
+
@custom_path = custom_path
|
84
|
+
|
85
|
+
return if paper_path.nil?
|
86
|
+
|
87
|
+
parsed = load_yaml(paper_path)
|
88
|
+
|
89
|
+
check_fields(parsed, paper_path)
|
90
|
+
check_orcids(parsed)
|
91
|
+
|
92
|
+
@paper_path = paper_path
|
93
|
+
@authors = parse_authors(parsed)
|
94
|
+
@title = parsed['title']
|
95
|
+
@languages = detect_languages
|
96
|
+
@tags = parsed['tags']
|
97
|
+
@date = parsed['date']
|
98
|
+
@bibliography_path = parsed['bibliography']
|
99
|
+
# Probably a much nicer way to do this...
|
100
|
+
@current_year = ENV["CURRENT_YEAR"].nil? ? Time.new.year : ENV["CURRENT_YEAR"]
|
101
|
+
@current_volume = ENV["CURRENT_VOLUME"].nil? ? Time.new.year - (Time.parse(ENV['JOURNAL_LAUNCH_DATE']).year - 1) : ENV["CURRENT_VOLUME"]
|
102
|
+
@current_issue = ENV["CURRENT_ISSUE"].nil? ? 1 + ((Time.new.year * 12 + Time.new.month) - (Time.parse(ENV['JOURNAL_LAUNCH_DATE']).year * 12 + Time.parse(ENV['JOURNAL_LAUNCH_DATE']).month)) : ENV["CURRENT_ISSUE"]
|
103
|
+
end
|
104
|
+
|
105
|
+
def reviewers
|
106
|
+
review_issue if review_issue_body.nil?
|
107
|
+
@reviewers = review_issue_body.match(/Reviewers?:\*\*\s*(.+?)\r?\n/)[1].split(", ") - ["Pending"]
|
108
|
+
end
|
109
|
+
|
110
|
+
def reviewers_without_handles
|
111
|
+
reviewers.map { |reviewer_name| reviewer_name.sub(/^@/, "") }
|
112
|
+
end
|
113
|
+
|
114
|
+
def editor
|
115
|
+
review_issue if review_issue_body.nil?
|
116
|
+
if match = review_issue_body.match(/\*\*Editor:\*\*\s*.@(\S*)/)
|
117
|
+
@editor = match[1]
|
118
|
+
else
|
119
|
+
@editor = nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def load_yaml(paper_path)
|
124
|
+
if paper_path.include?('.tex')
|
125
|
+
return YAML.load_file(paper_path.gsub('.tex', '.yml'))
|
126
|
+
else
|
127
|
+
return YAML.load_file(paper_path)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def latex_source?
|
132
|
+
paper_path.end_with?('.tex')
|
133
|
+
end
|
134
|
+
|
135
|
+
def markdown_source?
|
136
|
+
paper_path.end_with?('.md')
|
137
|
+
end
|
138
|
+
|
139
|
+
# Check that the paper has the expected YAML header. Raise if missing fields
|
140
|
+
def check_fields(parsed, paper_path)
|
141
|
+
if paper_path.include?('.tex')
|
142
|
+
expected_fields = EXPECTED_LATEX_FIELDS
|
143
|
+
else
|
144
|
+
expected_fields = EXPECTED_MARKDOWN_FIELDS
|
145
|
+
end
|
146
|
+
fields = expected_fields - parsed.keys
|
147
|
+
raise "Paper YAML header is missing expected fields: #{fields.join(', ')}" if !fields.empty?
|
148
|
+
end
|
149
|
+
|
150
|
+
# Check that the user-defined ORCIDs look valid
|
151
|
+
def check_orcids(parsed)
|
152
|
+
authors = parsed['authors']
|
153
|
+
authors.each do |author|
|
154
|
+
next unless author.has_key?('orcid')
|
155
|
+
raise "Problem with ORCID (#{author['orcid']}) for #{author['name']}" unless OrcidValidator.new(author['orcid']).validate
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def detect_languages
|
160
|
+
path = custom_path ? custom_path : "tmp/#{review_issue_id}"
|
161
|
+
|
162
|
+
repo = Rugged::Repository.new("#{path}")
|
163
|
+
project = Linguist::Repository.new(repo, repo.head.target_id)
|
164
|
+
|
165
|
+
# Take top five languages from Linguist
|
166
|
+
project.languages.keys.take(5)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Create the payload that we're going to use to post back to JOSS/JOSE
|
170
|
+
def deposit_payload
|
171
|
+
review_issue if review_issue_body.nil?
|
172
|
+
payload = {
|
173
|
+
'paper' => {}
|
174
|
+
}
|
175
|
+
|
176
|
+
%w(title tags languages).each { |var| payload['paper'][var] = self.send(var) }
|
177
|
+
payload['paper']['authors'] = authors.collect { |a| a.to_h }
|
178
|
+
payload['paper']['doi'] = formatted_doi
|
179
|
+
payload['paper']['archive_doi'] = review_issue_body[ARCHIVE_REGEX].gsub('"', '')
|
180
|
+
payload['paper']['repository_address'] = review_issue_body[REPO_REGEX].gsub('"', '')
|
181
|
+
payload['paper']['editor'] = "@#{editor}"
|
182
|
+
payload['paper']['reviewers'] = reviewers.collect(&:strip)
|
183
|
+
payload['paper']['volume'] = current_volume
|
184
|
+
payload['paper']['issue'] = current_issue
|
185
|
+
payload['paper']['year'] = current_year
|
186
|
+
payload['paper']['page'] = review_issue_id
|
187
|
+
|
188
|
+
return payload
|
189
|
+
end
|
190
|
+
|
191
|
+
def plain_title
|
192
|
+
renderer = Redcarpet::Markdown.new(Redcarpet::Render::StripDown)
|
193
|
+
return renderer.render(self.title).strip
|
194
|
+
end
|
195
|
+
|
196
|
+
def parse_authors(yaml)
|
197
|
+
returned = []
|
198
|
+
authors_yaml = yaml['authors']
|
199
|
+
affiliations = parse_affiliations(yaml['affiliations'])
|
200
|
+
|
201
|
+
# Loop through the authors block and build up the affiliation
|
202
|
+
authors_yaml.each do |author|
|
203
|
+
affiliation_index = author['affiliation']
|
204
|
+
raise "Author (#{author['name']}) is missing affiliation" if affiliation_index.nil?
|
205
|
+
returned << Author.new(author['name'], author['orcid'], affiliation_index, affiliations)
|
206
|
+
end
|
207
|
+
|
208
|
+
returned
|
209
|
+
end
|
210
|
+
|
211
|
+
def parse_affiliations(affliations_yaml)
|
212
|
+
returned = {}
|
213
|
+
affliations_yaml.each do |affiliation|
|
214
|
+
returned[affiliation['index']] = affiliation['name']
|
215
|
+
end
|
216
|
+
|
217
|
+
returned
|
218
|
+
end
|
219
|
+
|
220
|
+
# A 5-figure integer used to produce the JOSS DOI
|
221
|
+
# Note, this doesn't actually include the string 'joss' in the DOI any
|
222
|
+
# longer (it's now generalized) but the method name remains
|
223
|
+
def joss_id
|
224
|
+
id = "%05d" % review_issue_id
|
225
|
+
"#{ENV['JOURNAL_ALIAS']}.#{id}"
|
226
|
+
end
|
227
|
+
|
228
|
+
def pdf_url
|
229
|
+
"http://www.theoj.org/#{paper_repo}/#{joss_id}/#{ENV['DOI_PREFIX']}.#{joss_id}.pdf"
|
230
|
+
end
|
231
|
+
|
232
|
+
def paper_org
|
233
|
+
ENV['PAPER_REPOSITORY'].split('/').first
|
234
|
+
end
|
235
|
+
|
236
|
+
def paper_repo
|
237
|
+
ENV['PAPER_REPOSITORY'].split('/').last
|
238
|
+
end
|
239
|
+
|
240
|
+
def review_issue_url
|
241
|
+
"https://github.com/#{ENV['REVIEW_REPOSITORY']}/issues/#{review_issue_id}"
|
242
|
+
end
|
243
|
+
|
244
|
+
def directory
|
245
|
+
File.dirname(paper_path)
|
246
|
+
end
|
247
|
+
|
248
|
+
def bibtex_path
|
249
|
+
"#{directory}/#{bibliography_path}"
|
250
|
+
end
|
251
|
+
|
252
|
+
# The full DOI e.g. 10.21105/00001
|
253
|
+
def formatted_doi
|
254
|
+
"#{ENV['DOI_PREFIX']}/#{joss_id}"
|
255
|
+
end
|
256
|
+
|
257
|
+
# User when generating the citation snipped, returns either:
|
258
|
+
# 'Smith et al' for multiple authors or 'Smith' for a single author
|
259
|
+
def citation_author
|
260
|
+
surname = authors.first.last_name
|
261
|
+
initials = authors.first.initials
|
262
|
+
|
263
|
+
if authors.size > 1
|
264
|
+
return "#{surname} et al."
|
265
|
+
else
|
266
|
+
return "#{surname}, #{initials}"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def authors_string
|
271
|
+
authors_array = []
|
272
|
+
|
273
|
+
authors.each_with_index do |author, index|
|
274
|
+
authors_array << "#{author.name}"
|
275
|
+
end
|
276
|
+
|
277
|
+
return authors_array.join(', ')
|
278
|
+
end
|
279
|
+
|
280
|
+
def jats_authors
|
281
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
282
|
+
xml.send(:'contrib-group', "content-type" => "authors") {
|
283
|
+
authors.each_with_index do |author, index|
|
284
|
+
given_name = author.given_name
|
285
|
+
surname = author.last_name
|
286
|
+
orcid = author.orcid
|
287
|
+
xml.contrib("contrib-type" => "author", "id" => "author-#{index+1}") {
|
288
|
+
xml.send(:'contrib-id', orcid, "contrib-id-type" => "orcid")
|
289
|
+
xml.name {
|
290
|
+
xml.surname surname.encode(:xml => :text)
|
291
|
+
xml.send(:'given-names', given_name.encode(:xml => :text))
|
292
|
+
}
|
293
|
+
xml.xref("ref-type" => "aff", "rid" => "aff-#{index+1}")
|
294
|
+
}
|
295
|
+
end
|
296
|
+
}
|
297
|
+
end
|
298
|
+
|
299
|
+
return builder.doc.xpath('//contrib-group').to_xml
|
300
|
+
end
|
301
|
+
|
302
|
+
def jats_affiliations
|
303
|
+
affiliations = ""
|
304
|
+
authors.each_with_index do |author, index|
|
305
|
+
affiliations << "<aff id=\"aff-#{index+1}\">#{author.affiliation}</aff>\n"
|
306
|
+
end
|
307
|
+
|
308
|
+
return affiliations
|
309
|
+
end
|
310
|
+
|
311
|
+
# Returns an XML snippet to be included in the Crossref XML
|
312
|
+
def crossref_authors
|
313
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
314
|
+
xml.contributors {
|
315
|
+
authors.each_with_index do |author, index|
|
316
|
+
given_name = author.given_name
|
317
|
+
surname = author.last_name
|
318
|
+
orcid = author.orcid
|
319
|
+
if index == 0
|
320
|
+
sequence = "first"
|
321
|
+
else
|
322
|
+
sequence = "additional"
|
323
|
+
end
|
324
|
+
xml.person_name(:sequence => sequence, :contributor_role => "author") {
|
325
|
+
xml.given_name given_name.encode(:xml => :text)
|
326
|
+
if surname.nil?
|
327
|
+
xml.surname "No Last Name".encode(:xml => :text)
|
328
|
+
else
|
329
|
+
xml.surname surname.encode(:xml => :text)
|
330
|
+
end
|
331
|
+
xml.ORCID "http://orcid.org/#{author.orcid}" if !orcid.nil?
|
332
|
+
}
|
333
|
+
end
|
334
|
+
}
|
335
|
+
end
|
336
|
+
|
337
|
+
return builder.doc.xpath('//contributors').to_xml
|
338
|
+
end
|
339
|
+
|
340
|
+
def google_scholar_authors
|
341
|
+
authors_string = ""
|
342
|
+
|
343
|
+
authors.each_with_index do |author, index|
|
344
|
+
given_name = author.given_name
|
345
|
+
surname = author.last_name
|
346
|
+
|
347
|
+
authors_string << "<meta name=\"citation_author\" content=\"#{surname}, #{given_name}\">"
|
348
|
+
end
|
349
|
+
|
350
|
+
return authors_string
|
351
|
+
end
|
352
|
+
|
353
|
+
# A slightly modified DOI string for writing out files
|
354
|
+
# 10.21105/00001 -> 10.21105.00001
|
355
|
+
def filename_doi
|
356
|
+
formatted_doi.gsub('/', '.')
|
357
|
+
end
|
358
|
+
|
359
|
+
# The JOSS site url for a paper
|
360
|
+
# e.g. http://joss.theoj.org/papers/10.21105/00001
|
361
|
+
def joss_resource_url
|
362
|
+
"#{ENV['JOURNAL_URL']}/papers/#{formatted_doi}"
|
363
|
+
end
|
364
|
+
|
365
|
+
# Return the Review object associated with the Paper
|
366
|
+
def review_issue
|
367
|
+
review = Whedon::Review.new(review_issue_id, review_repository)
|
368
|
+
@review_issue_body = review.issue_body
|
369
|
+
return review
|
370
|
+
end
|
371
|
+
|
372
|
+
def audit
|
373
|
+
review_issue if review_issue_body.nil?
|
374
|
+
Whedon::Auditor.new(review_issue_body).audit
|
375
|
+
end
|
376
|
+
|
377
|
+
def download
|
378
|
+
review_issue if review_issue_body.nil?
|
379
|
+
Whedon::Processor.new(review_issue_id, review_issue_body, custom_path).clone
|
380
|
+
end
|
381
|
+
|
382
|
+
def compile
|
383
|
+
review_issue if review_issue_body.nil?
|
384
|
+
processor = Whedon::Processor.new(review_issue_id, review_issue_body)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
FROM pandoc/latex:2.11.2
|
2
|
+
|
3
|
+
# Update tlmgr
|
4
|
+
RUN tlmgr update --self
|
5
|
+
|
6
|
+
# Install additional LaTeX packages
|
7
|
+
RUN tlmgr install \
|
8
|
+
algorithmicx \
|
9
|
+
algorithms \
|
10
|
+
biblatex \
|
11
|
+
booktabs \
|
12
|
+
caption \
|
13
|
+
collection-xetex \
|
14
|
+
draftwatermark \
|
15
|
+
environ \
|
16
|
+
etoolbox \
|
17
|
+
everypage \
|
18
|
+
fancyvrb \
|
19
|
+
float \
|
20
|
+
fontspec \
|
21
|
+
latexmk \
|
22
|
+
lineno \
|
23
|
+
listings \
|
24
|
+
logreq \
|
25
|
+
marginnote \
|
26
|
+
mathspec \
|
27
|
+
pgf \
|
28
|
+
preprint \
|
29
|
+
seqsplit \
|
30
|
+
tcolorbox \
|
31
|
+
titlesec \
|
32
|
+
trimspaces \
|
33
|
+
xcolor \
|
34
|
+
xkeyval \
|
35
|
+
xstring
|
36
|
+
|
37
|
+
# Copy templates, images, and other resources
|
38
|
+
ARG openjournals_path=/usr/local/share/openjournals
|
39
|
+
COPY ./resources $openjournals_path
|
40
|
+
COPY ./resources/docker-entrypoint.sh /usr/local/bin/paperdraft
|
41
|
+
|
42
|
+
ENV JOURNAL=joss
|
43
|
+
ENV OPENJOURNALS_PATH=$openjournals_path
|
44
|
+
|
45
|
+
# Input is read from `paper.md` by default, but can be overridden. Output is
|
46
|
+
# written to `paper.pdf`
|
47
|
+
ENTRYPOINT ["/usr/local/bin/paperdraft"]
|
48
|
+
CMD ["paper.md"]
|
Binary file
|
data/resources/.DS_Store
ADDED
Binary file
|
Binary file
|
Binary file
|