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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.env-example +12 -0
  4. data/.env.test +8 -0
  5. data/.github/workflows/docker-image.yml +63 -0
  6. data/.github/workflows/tests.yml +28 -0
  7. data/.gitignore +3 -0
  8. data/Gemfile +3 -0
  9. data/Gemfile.lock +135 -0
  10. data/LICENSE +21 -0
  11. data/README.md +190 -0
  12. data/Rakefile +13 -0
  13. data/bin/whedon +131 -0
  14. data/fixtures/latex_paper/paper.tex +0 -0
  15. data/fixtures/paper/10.21105.jcon.00017.crossref.xml +88 -0
  16. data/fixtures/paper/crossref-metadata.yaml +34 -0
  17. data/fixtures/paper/paper-bib.md +27 -0
  18. data/fixtures/paper/paper-single-author.md +24 -0
  19. data/fixtures/paper/paper-with-harder-names.md +27 -0
  20. data/fixtures/paper/paper-with-missing-affiliations.md +21 -0
  21. data/fixtures/paper/paper.bib +65 -0
  22. data/fixtures/paper/paper.html +14 -0
  23. data/fixtures/paper/paper.md +27 -0
  24. data/fixtures/paper/paper.pdf +0 -0
  25. data/fixtures/paper/paper.xml +28 -0
  26. data/fixtures/paper/paper_with_missing_title.md +26 -0
  27. data/fixtures/review_body.txt +48 -0
  28. data/fixtures/test_paper/nested/paper.bib +0 -0
  29. data/fixtures/test_paper/nested/paper.md +0 -0
  30. data/fixtures/test_paper/paper.bib +0 -0
  31. data/fixtures/test_paper/paper.md +0 -0
  32. data/fixtures/vcr_cassettes/joss-lookup.yml +58 -0
  33. data/fixtures/vcr_cassettes/review.yml +237 -0
  34. data/fixtures/vcr_cassettes/reviews.yml +270 -0
  35. data/lib/whedon/auditor.rb +39 -0
  36. data/lib/whedon/author.rb +81 -0
  37. data/lib/whedon/bibtex_parser.rb +103 -0
  38. data/lib/whedon/compilers.rb +379 -0
  39. data/lib/whedon/github.rb +12 -0
  40. data/lib/whedon/orcid_validator.rb +83 -0
  41. data/lib/whedon/processor.rb +178 -0
  42. data/lib/whedon/review.rb +25 -0
  43. data/lib/whedon/reviews.rb +29 -0
  44. data/lib/whedon/version.rb +3 -0
  45. data/lib/whedon.rb +387 -0
  46. data/paperdraft.Dockerfile +48 -0
  47. data/pkg/roboneuro-0.1.0.gem +0 -0
  48. data/resources/.DS_Store +0 -0
  49. data/resources/NeuroLibre/.DS_Store +0 -0
  50. data/resources/NeuroLibre/aas-logo.png +0 -0
  51. data/resources/NeuroLibre/apa.csl +1919 -0
  52. data/resources/NeuroLibre/defaults.yaml +14 -0
  53. data/resources/NeuroLibre/latex.template +541 -0
  54. data/resources/NeuroLibre/logo.png +0 -0
  55. data/resources/NeuroLibre/logo_link.png +0 -0
  56. data/resources/apa.csl +1919 -0
  57. data/resources/crossref.template +89 -0
  58. data/resources/docker-defaults.yaml +42 -0
  59. data/resources/docker-entrypoint.sh +37 -0
  60. data/resources/jats.csl +204 -0
  61. data/resources/jats.template +105 -0
  62. data/resources/jose/apa.csl +1919 -0
  63. data/resources/jose/defaults.yaml +14 -0
  64. data/resources/jose/latex.template +486 -0
  65. data/resources/jose/logo.png +0 -0
  66. data/resources/joss/aas-logo.png +0 -0
  67. data/resources/joss/apa.csl +1919 -0
  68. data/resources/joss/defaults.yaml +14 -0
  69. data/resources/joss/latex.template +525 -0
  70. data/resources/joss/logo.png +0 -0
  71. data/resources/latex.template +485 -0
  72. data/resources/time.lua +8 -0
  73. data/roboneuro.gemspec +39 -0
  74. data/spec/auditor_spec.rb +22 -0
  75. data/spec/author_spec.rb +17 -0
  76. data/spec/bibtex_spec.rb +31 -0
  77. data/spec/orcid_validator_spec.rb +33 -0
  78. data/spec/processor_spec.rb +66 -0
  79. data/spec/review_spec.rb +12 -0
  80. data/spec/reviews_spec.rb +18 -0
  81. data/spec/spec_helper.rb +22 -0
  82. data/spec/whedon_spec.rb +93 -0
  83. 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
@@ -0,0 +1,3 @@
1
+ module Whedon
2
+ VERSION = "0.1.1"
3
+ 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
Binary file
Binary file
Binary file