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,81 @@
1
+ module Whedon
2
+ class Author
3
+ require 'nameable'
4
+
5
+ attr_accessor :name, :affiliation, :orcid
6
+
7
+ AUTHOR_FOOTNOTE_REGEX = /^[^\^]*/
8
+
9
+ # Initialized with authors & affiliations block in the YAML header from
10
+ # a JOSS paper e.g. http://joss.theoj.org/about#paper_structure
11
+ #
12
+ def initialize(name, orcid, index, affiliations_yaml)
13
+ name = strip_footnotes(name)
14
+ @parsed_name = Nameable::Latin.new.parse(name)
15
+ @name = @parsed_name.to_nameable
16
+ @orcid = orcid
17
+ @affiliation = build_affiliation_string(index, affiliations_yaml)
18
+ end
19
+
20
+ def to_h
21
+ {
22
+ :given_name => given_name,
23
+ :middle_name => @parsed_name.middle,
24
+ :last_name => last_name,
25
+ :orcid => orcid,
26
+ :affiliation => affiliation.strip
27
+ }
28
+ end
29
+
30
+ # Input: Arfon Smith^[Corresponding author: arfon@example.com]
31
+ # Output: Arfon Smith
32
+ def strip_footnotes(name)
33
+ name[AUTHOR_FOOTNOTE_REGEX]
34
+ end
35
+
36
+ # Use the Nameable gem to return last name
37
+ def last_name
38
+ @parsed_name.last
39
+ end
40
+
41
+ # Use the Nameable gem to return first name
42
+ def given_name
43
+ @parsed_name.first
44
+ end
45
+
46
+ def initials
47
+ [@parsed_name.first, @parsed_name.middle].compact.map {|v| v[0]}.zip(['.', '.', '.']).map(&:join) * ' '
48
+ end
49
+
50
+ # Takes the author affiliation index and a hash of all affiliations and
51
+ # associates them. Then builds (and assigns) the author affiliation string.
52
+ def build_affiliation_string(index, affiliations_yaml)
53
+ # Some people have two affiliations, if this is the case then we need
54
+ # to parse each one and build the affiliation string.
55
+ author_affiliations = []
56
+
57
+ return nil if index.nil? # Some authors don't have an affiliation
58
+
59
+ affiliations = if index.to_s.include?(',')
60
+ index.split(',').map { |a| a.strip }
61
+ else
62
+ [ index.to_s ]
63
+ end
64
+
65
+ # We need to turn all of the affiliation YAML keys into strings
66
+ # So that mixed integer and string affiliations work
67
+ affiliations_yaml = affiliations_yaml.inject({}) {|hash, (key, val)| hash.merge(key.to_s => val) }
68
+
69
+ # Raise if we can't parse the string, might be because of this bug :-(
70
+ # https://bugs.ruby-lang.org/issues/12451
71
+ affiliations.each do |a|
72
+ raise "Problem with affiliations for #{self.name}, perhaps the \
73
+ affiliations index need quoting?" unless affiliations_yaml.has_key?(a)
74
+
75
+ author_affiliations << affiliations_yaml[a]
76
+ end
77
+
78
+ return author_affiliations.join(', ')
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,103 @@
1
+ # Whedon::Bibtex is used to generate the citation string used in the Crossref
2
+ # metadata. It uses the bibtex RubyGem.
3
+
4
+ require 'bibtex'
5
+ require 'nokogiri'
6
+ require 'uri'
7
+
8
+ # => bib = Whedon::BibtexParser.new('paper.bib').generate_citations
9
+ module Whedon
10
+ class BibtexParser
11
+ # Initialize the Bibtex generator
12
+ # Takes a path to bibtex file
13
+ def initialize(bib_file)
14
+ @bib_file = bib_file
15
+ @ref_count = 1
16
+ @citation_string = ""
17
+ end
18
+
19
+ def bibtex_keys
20
+ entries = BibTeX.open(@bib_file, :filter => :latex)
21
+
22
+ keys = []
23
+ entries.each do |entry|
24
+ next if entry.comment?
25
+ next if entry.preamble?
26
+
27
+ keys << "@#{entry.key}"
28
+ end
29
+
30
+ return keys
31
+ end
32
+
33
+ # Generates the <citations></citations> XML block for Crossref
34
+ # Returns an XML fragment <citations></citations> with or
35
+ # without citations within
36
+ # TODO: should probably use Ruby builder templates here
37
+ def generate_citations(citations=nil)
38
+ entries = BibTeX.open(@bib_file, :filter => :latex)
39
+
40
+ if entries.empty?
41
+ @citation_string = ""
42
+ else
43
+ entries.each do |entry|
44
+ next if entry.comment?
45
+ next if entry.preamble?
46
+
47
+ if citations
48
+ next unless citations.include?("@#{entry.key}")
49
+ end
50
+ @citation_string << make_citation(entry)
51
+ @ref_count += 1
52
+ end
53
+ end
54
+
55
+ # This Nokogiri step is simply to pretty-print the XML of the citations
56
+ doc = Nokogiri::XML("<citation_list>#{@citation_string}</citation_list>")
57
+ return doc.root.to_xml
58
+ end
59
+
60
+ # Chooses what sort of citation to make based upon whether there is a DOI
61
+ # present in the bibtex entry
62
+ def make_citation(entry)
63
+ if entry.has_field?('doi') && !entry.doi.empty?
64
+ return doi_citation(entry)
65
+ else
66
+ return general_citation(entry)
67
+ end
68
+ end
69
+
70
+ # Returns a simple <citation> XML snippet with the DOI
71
+ def doi_citation(entry)
72
+ # Crossref DOIs need to be strings like 10.21105/joss.01461 rather
73
+ # than https://doi.org/10.21105/joss.01461
74
+ bare_doi = entry.doi.to_s[/\b(10[.][0-9]{4,}(?:[.][0-9]+)*\/(?:(?!["&\'<>])\S)+)\b/]
75
+
76
+ # Check for shortDOI formatted DOIs http://shortdoi.org
77
+ if bare_doi.nil?
78
+ bare_doi = entry.doi.to_s[/\b(10\/[a-bA-z0-9]+)\b/]
79
+ end
80
+
81
+ # Sometimes there are weird characters in the DOI. This escapes
82
+ escaped_doi = bare_doi.encode(:xml => :text)
83
+ "<citation key=\"ref#{@ref_count}\"><doi>#{escaped_doi}</doi></citation>"
84
+ end
85
+
86
+ # Returns a more complex <citation> XML snippet with keys for each of the
87
+ # bibtex fields
88
+ def general_citation(entry)
89
+ citation = "<citation key=\"ref#{@ref_count}\"><unstructured_citation>"
90
+ values = []
91
+ entry.each_pair do |name, value|
92
+ # Ultimately we should call entry.to_citeproce here to parse this properly.
93
+ # https://github.com/inukshuk/bibtex-ruby/pull/139/files
94
+ value = value.gsub('\urlhttp', 'http')
95
+ values << value.encode(:xml => :text)
96
+ end
97
+ citation << values.join(', ')
98
+ citation << "</unstructured_citation></citation>"
99
+
100
+ return citation
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,379 @@
1
+ # This module has methods to compile PDFs and Crossref XML depending upon
2
+ # the content type of the paper (Markdown or LaTeX)
3
+ module Compilers
4
+ require 'date'
5
+ require 'yaml'
6
+
7
+ # Generate the paper PDF
8
+ # Optionally pass in a custom branch name as first param
9
+ def generate_pdf(custom_branch=nil, draft=true, paper_issue=nil, paper_volume=nil, paper_year=nil)
10
+ if paper.latex_source?
11
+ pdf_from_latex(custom_branch, draft, paper_issue, paper_volume, paper_year)
12
+ elsif paper.markdown_source?
13
+ pdf_from_markdown(custom_branch, draft, paper_issue, paper_volume, paper_year)
14
+ end
15
+ end
16
+
17
+ def generate_crossref(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
18
+ if paper.latex_source?
19
+ crossref_from_latex(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
20
+ elsif paper.markdown_source?
21
+ crossref_from_markdown(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
22
+ end
23
+ end
24
+
25
+ def generate_jats(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
26
+ if paper.latex_source?
27
+ jats_from_latex(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
28
+ elsif paper.markdown_source?
29
+ jats_from_markdown(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
30
+ end
31
+ end
32
+
33
+ def pdf_from_latex(custom_branch=nil, draft=true, paper_issue=nil, paper_volume=nil, paper_year=nil)
34
+ # Optionally pass a custom branch name
35
+ `cd #{paper.directory} && git checkout #{custom_branch} --quiet` if custom_branch
36
+
37
+ metadata = YAML.load_file("#{paper.directory}/paper.yml")
38
+
39
+ for k in ["title", "authors", "affiliations", "keywords"]
40
+ raise "Key #{k} not present in metadata" unless metadata.keys().include?(k)
41
+ end
42
+
43
+ # Remove everything that shouldn't be there before processing
44
+
45
+ `cd #{paper.directory} && rm -f *.aux \
46
+ && rm -f *.blg && rm -f *.fls && rm -f *.log\
47
+ && rm -f *.fdb_latexmk`
48
+
49
+ open("#{paper.directory}/header.tex", 'w') do |f|
50
+ f << "% **************GENERATED FILE, DO NOT EDIT**************\n\n"
51
+ f << "\\title{#{metadata["title"]}}\n\n"
52
+ for auth in metadata["authors"]
53
+ f << "\\author[#{auth["affiliation"]}]{#{auth["name"]}}\n"
54
+ end
55
+ for aff in metadata["affiliations"]
56
+ f << "\\affil[#{aff["index"]}]{#{aff["name"]}}\n"
57
+ end
58
+ f << "\n\\keywords{"
59
+ for i in 0...metadata["keywords"].length-1
60
+ f << "#{metadata["keywords"][i]}, "
61
+ end
62
+ f << metadata["keywords"].last
63
+ f << "}\n"
64
+
65
+ # draft mode by default.
66
+ if draft
67
+ f << "\\usepackage{draftwatermark}\n\n"
68
+ end
69
+ end
70
+
71
+ `cd #{paper.directory} && latexmk -f -bibtex -pdf paper.tex`
72
+
73
+ if File.exists?("#{paper.directory}/paper.pdf")
74
+ `mv #{paper.directory}/paper.pdf #{paper.directory}/#{paper.filename_doi}.pdf`
75
+ puts "#{paper.directory}/#{paper.filename_doi}.pdf"
76
+ else
77
+ abort("Looks like we failed to compile the PDF")
78
+ end
79
+ end
80
+
81
+ def generate_issue(date)
82
+ parsed = Date.parse(date)
83
+ return 1 + ((parsed.year * 12 + parsed.month) - (Time.parse(ENV['JOURNAL_LAUNCH_DATE']).year * 12 + Time.parse(ENV['JOURNAL_LAUNCH_DATE']).month))
84
+ end
85
+
86
+ def generate_volume(date)
87
+ parsed = Date.parse(date)
88
+ return parsed.year - (Date.parse(ENV['JOURNAL_LAUNCH_DATE']).year - 1)
89
+ end
90
+
91
+ def generate_year(date)
92
+ parsed = Date.parse(date)
93
+ return parsed.year
94
+ end
95
+
96
+ def generate_month(date)
97
+ parsed = Date.parse(date)
98
+ return parsed.month
99
+ end
100
+
101
+ def generate_day(date)
102
+ parsed = Date.parse(date)
103
+ return parsed.day
104
+ end
105
+
106
+ def pdf_from_markdown(custom_branch=nil, draft=true, paper_issue=nil, paper_volume=nil, paper_year=nil)
107
+ latex_template_path = "#{Whedon.resources}/#{ENV['JOURNAL_ALIAS']}/latex.template"
108
+ csl_file = "#{Whedon.resources}/#{ENV['JOURNAL_ALIAS']}/apa.csl"
109
+
110
+ url = "#{ENV['JOURNAL_URL']}/papers/lookup/#{@review_issue_id}"
111
+ response = RestClient.get(url)
112
+ parsed = JSON.parse(response)
113
+ submitted = parsed['submitted']
114
+ published = parsed['accepted']
115
+
116
+ # TODO - remove this once JOSE has their editors hooked up in the system
117
+ if ENV['JOURNAL_ALIAS'] == "joss" && !paper.editor.nil?
118
+ editor_lookup_url = "#{ENV['JOURNAL_URL']}/editors/lookup/#{paper.editor}"
119
+ response = RestClient.get(editor_lookup_url)
120
+ parsed = JSON.parse(response)
121
+ editor_name = parsed['name']
122
+ editor_url = parsed['url']
123
+ else
124
+ editor_name = "Pending Editor"
125
+ editor_url = "http://example.com"
126
+ end
127
+
128
+ # If we have already published the paper then overwrite the year, volume, issue
129
+ if published
130
+ paper_year = generate_year(published)
131
+ paper_issue = generate_issue(published)
132
+ paper_volume = generate_volume(published)
133
+ else
134
+ published = Time.now.strftime('%d %B %Y')
135
+ paper_year ||= @current_year
136
+ paper_issue ||= @current_issue
137
+ paper_volume ||= @current_volume
138
+ end
139
+
140
+ # Optionally pass a custom branch name
141
+ `cd #{paper.directory} && git checkout #{custom_branch} --quiet` if custom_branch
142
+
143
+ metadata = {
144
+ "repository" => repository_address,
145
+ "archive_doi" => archive_doi,
146
+ "paper_url" => paper.pdf_url,
147
+ "journal_name" => ENV['JOURNAL_NAME'],
148
+ "review_issue_url" => paper.review_issue_url,
149
+ "issue" => paper_issue,
150
+ "volume" => paper_volume,
151
+ "page" => paper.review_issue_id,
152
+ "logo_path" => "#{Whedon.resources}/#{ENV['JOURNAL_ALIAS']}/logo.png",
153
+ "aas_logo_path" => "#{Whedon.resources}/#{ENV['JOURNAL_ALIAS']}/aas-logo.png",
154
+ "year" => paper_year,
155
+ "submitted" => submitted,
156
+ "published" => published,
157
+ "formatted_doi" => paper.formatted_doi,
158
+ "citation_author" => paper.citation_author,
159
+ "editor_name" => editor_name,
160
+ "reviewers" => paper.reviewers_without_handles,
161
+ "link-citations" => true
162
+ }
163
+
164
+ metadata.merge!({"draft" => true}) if draft
165
+
166
+ File.open("#{paper.directory}/markdown-metadata.yaml", 'w') { |file| file.write(metadata.to_yaml) }
167
+
168
+ `cd #{paper.directory} && pandoc \
169
+ -V repository="#{repository_address}" \
170
+ -V archive_doi="#{archive_doi}" \
171
+ -V review_issue_url="#{paper.review_issue_url}" \
172
+ -V editor_url="#{editor_url}" \
173
+ -V graphics="true" \
174
+ -o #{paper.filename_doi}.pdf -V geometry:margin=1in \
175
+ --pdf-engine=xelatex \
176
+ --citeproc #{File.basename(paper.paper_path)} \
177
+ --from markdown+autolink_bare_uris \
178
+ --csl=#{csl_file} \
179
+ --template #{latex_template_path} \
180
+ --metadata-file=markdown-metadata.yaml`
181
+
182
+ if File.exists?("#{paper.directory}/#{paper.filename_doi}.pdf")
183
+ puts "#{paper.directory}/#{paper.filename_doi}.pdf"
184
+ else
185
+ abort("Looks like we failed to compile the PDF")
186
+ end
187
+ end
188
+
189
+ def crossref_from_latex(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
190
+ cross_ref_template_path = "#{Whedon.resources}/crossref.template"
191
+ bibtex = Whedon::BibtexParser.new(paper.bibtex_path)
192
+
193
+ # Pass the citations that are actually in the paper to the CrossRef
194
+ # citations generator.
195
+
196
+ citations_in_paper = File.read(paper.paper_path).scan(/(?<=\\cite\{)\w+/)
197
+ # FIXME
198
+ # Because of the way citations are handled in Pandoc, we need to prepend and @
199
+ # to the front of each of the citation strings.
200
+ citations_in_paper = citations_in_paper.map {|c| c.prepend("@")}
201
+
202
+ citations = bibtex.generate_citations(citations_in_paper)
203
+ authors = paper.crossref_authors
204
+ # TODO fix this when we update the DOI URLs
205
+ # crossref_doi = archive_doi.gsub("http://dx.doi.org/", '')
206
+
207
+ paper_day ||= Time.now.strftime('%d')
208
+ paper_month ||= Time.now.strftime('%m')
209
+ paper_year ||= @current_year
210
+ paper_issue ||= @current_issue
211
+ paper_volume ||= @current_volume
212
+
213
+ `cd #{paper.directory} && pandoc \
214
+ -V timestamp=#{Time.now.strftime('%Y%m%d%H%M%S')} \
215
+ -V doi_batch_id=#{generate_doi_batch_id} \
216
+ -V formatted_doi=#{paper.formatted_doi} \
217
+ -V archive_doi=#{archive_doi} \
218
+ -V review_issue_url=#{paper.review_issue_url} \
219
+ -V paper_url=#{paper.pdf_url} \
220
+ -V joss_resource_url=#{paper.joss_resource_url} \
221
+ -V journal_alias=#{ENV['JOURNAL_ALIAS']} \
222
+ -V journal_abbrev_title=#{ENV['JOURNAL_ALIAS'].upcase} \
223
+ -V journal_url=#{ENV['JOURNAL_URL']} \
224
+ -V journal_name='#{ENV['JOURNAL_NAME']}' \
225
+ -V journal_issn=#{ENV['JOURNAL_ISSN']} \
226
+ -V citations='#{citations}' \
227
+ -V authors='#{authors}' \
228
+ -V month=#{paper_month} \
229
+ -V day=#{paper_day} \
230
+ -V year=#{paper_year} \
231
+ -V issue=#{paper_issue} \
232
+ -V volume=#{paper_volume} \
233
+ -V page=#{paper.review_issue_id} \
234
+ -V title='#{paper.plain_title}' \
235
+ -f markdown #{File.basename(paper.paper_path)} -o #{paper.filename_doi}.crossref.xml \
236
+ --template #{cross_ref_template_path}`
237
+
238
+ if File.exists?("#{paper.directory}/#{paper.filename_doi}.crossref.xml")
239
+ "#{paper.directory}/#{paper.filename_doi}.crossref.xml"
240
+ else
241
+ abort("Looks like we failed to compile the Crossref XML")
242
+ end
243
+ end
244
+
245
+ def crossref_from_markdown(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
246
+ cross_ref_template_path = "#{Whedon.resources}/crossref.template"
247
+ bibtex = Whedon::BibtexParser.new(paper.bibtex_path)
248
+
249
+ # Pass the citations that are actually in the paper to the CrossRef
250
+ # citations generator.
251
+
252
+ citations_in_paper = File.read(paper.paper_path).scan(/@[\w|\-|:|_|\/|\+]+/)
253
+ citations = bibtex.generate_citations(citations_in_paper)
254
+ authors = paper.crossref_authors
255
+ # TODO fix this when we update the DOI URLs
256
+ # crossref_doi = archive_doi.gsub("http://dx.doi.org/", '')
257
+
258
+ url = "#{ENV['JOURNAL_URL']}/papers/lookup/#{@review_issue_id}"
259
+ response = RestClient.get(url)
260
+ parsed = JSON.parse(response)
261
+ submitted = parsed['submitted']
262
+ published = parsed['accepted']
263
+
264
+ # If we have already published the paper then overwrite the year, volume, issue
265
+ if published
266
+ paper_day = generate_day(published)
267
+ paper_month = generate_month(published)
268
+ paper_year = generate_year(published)
269
+ paper_issue = generate_issue(published)
270
+ paper_volume = generate_volume(published)
271
+ else
272
+ paper_issue ||= @current_issue
273
+ paper_volume ||= @current_volume
274
+ paper_day ||= Time.now.strftime('%d')
275
+ paper_month ||= Time.now.strftime('%m')
276
+ paper_year ||= Time.now.strftime('%Y')
277
+ end
278
+
279
+ metadata = {
280
+ "timestamp" => Time.now.strftime('%Y%m%d%H%M%S'),
281
+ "doi_batch_id" => generate_doi_batch_id,
282
+ "formatted_doi" => paper.formatted_doi,
283
+ "archive_doi" => archive_doi,
284
+ "review_issue_url" => paper.review_issue_url,
285
+ "paper_url" => paper.pdf_url,
286
+ "joss_resource_url" => paper.joss_resource_url,
287
+ "journal_alias" => ENV['JOURNAL_ALIAS'],
288
+ "journal_abbrev_title" => ENV['JOURNAL_ALIAS'].upcase,
289
+ "journal_url" => ENV['JOURNAL_URL'],
290
+ "journal_name" => ENV['JOURNAL_NAME'],
291
+ "journal_issn"=> ENV['JOURNAL_ISSN'],
292
+ "month" => paper_month,
293
+ "day" => paper_day,
294
+ "year" => paper_year,
295
+ "issue" => paper_issue,
296
+ "volume" => paper_volume,
297
+ "page" => paper.review_issue_id,
298
+ "title" => paper.plain_title,
299
+ "crossref_authors" => authors,
300
+ "citations" => citations
301
+ }
302
+
303
+ File.open("#{paper.directory}/crossref-metadata.yaml", 'w') { |file| file.write(metadata.to_yaml) }
304
+
305
+ `cd #{paper.directory} && pandoc \
306
+ -V title="#{paper.plain_title}" \
307
+ -f markdown #{File.basename(paper.paper_path)} -o #{paper.filename_doi}.crossref.xml \
308
+ --template #{cross_ref_template_path} \
309
+ --metadata-file=crossref-metadata.yaml`
310
+
311
+ if File.exists?("#{paper.directory}/#{paper.filename_doi}.crossref.xml")
312
+ doc = Nokogiri::XML(File.open("#{paper.directory}/#{paper.filename_doi}.crossref.xml", "r"), &:noblanks)
313
+ File.open("#{paper.directory}/#{paper.filename_doi}.crossref.xml", 'w') {|f| f.write(doc.to_xml(:indent => 2))}
314
+ "#{paper.directory}/#{paper.filename_doi}.crossref.xml"
315
+ else
316
+ abort("Looks like we failed to compile the Crossref XML")
317
+ end
318
+ end
319
+
320
+ def jats_from_latex(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
321
+ "JATS from LaTeX"
322
+ end
323
+
324
+ def jats_from_markdown(paper_issue=nil, paper_volume=nil, paper_year=nil, paper_month=nil, paper_day=nil)
325
+ latex_template_path = "#{Whedon.resources}/latex.template"
326
+ jats_template_path = "#{Whedon.resources}/jats.template"
327
+ csl_file = "#{Whedon.resources}/jats.csl"
328
+
329
+
330
+ url = "#{ENV['JOURNAL_URL']}/papers/lookup/#{@review_issue_id}"
331
+ response = RestClient.get(url)
332
+ parsed = JSON.parse(response)
333
+ submitted = parsed['submitted']
334
+ published = parsed['accepted']
335
+
336
+ # If we have already published the paper then overwrite the year, volume, issue
337
+ if published
338
+ paper_day = Date.parse(published).strftime('%d')
339
+ paper_year = generate_year(published)
340
+ paper_issue = generate_issue(published)
341
+ paper_volume = generate_volume(published)
342
+ else
343
+ paper_day ||= Time.now.strftime('%d')
344
+ paper_month ||= Time.now.strftime('%m')
345
+ paper_year ||= Time.now.strftime('%Y')
346
+ end
347
+
348
+ # TODO: may eventually want to swap out the latex template
349
+ `cd #{paper.directory} && pandoc \
350
+ -V repository="#{repository_address}" \
351
+ -V archive_doi="#{archive_doi}" \
352
+ -V paper_url="#{paper.pdf_url}" \
353
+ -V journal_name='#{ENV['JOURNAL_NAME']}' \
354
+ -V journal_issn=#{ENV['JOURNAL_ISSN']} \
355
+ -V journal_abbrev_title=#{ENV['JOURNAL_ALIAS']} \
356
+ -V graphics="true" \
357
+ -V issue="#{paper_issue}" \
358
+ -V volume="#{paper_volume}" \
359
+ -V page="#{paper.review_issue_id}" \
360
+ -V logo_path="#{Whedon.resources}/#{ENV['JOURNAL_ALIAS']}-logo.png" \
361
+ -V month=#{paper_month} \
362
+ -V day=#{paper_day} \
363
+ -V year="#{paper_year}" \
364
+ -V jats_authors='#{paper.jats_authors}' \
365
+ -V jats_affiliations='#{paper.jats_affiliations}' \
366
+ -t jats \
367
+ -s \
368
+ --citeproc \
369
+ -o #{paper.filename_doi}.jats.xml \
370
+ #{File.basename(paper.paper_path)} \
371
+ --template #{jats_template_path}`
372
+
373
+ if File.exists?("#{paper.directory}/#{paper.filename_doi}.jats.xml")
374
+ "#{paper.directory}/#{paper.filename_doi}.jats.xml"
375
+ else
376
+ abort("Looks like we failed to compile the JATS XML")
377
+ end
378
+ end
379
+ end
@@ -0,0 +1,12 @@
1
+ # How we connect to GitHub.
2
+
3
+ module GitHub
4
+ # Authenticated Octokit
5
+ # TODO remove license preview media type when this ships
6
+ MEDIA_TYPE = "application/vnd.github.drax-preview+json"
7
+
8
+ def client
9
+ @client ||= Octokit::Client.new(:access_token => ENV['GH_TOKEN'],
10
+ :default_media_type => MEDIA_TYPE)
11
+ end
12
+ end
@@ -0,0 +1,83 @@
1
+ class String
2
+ def numeric?
3
+ Float(self) != nil rescue false
4
+ end
5
+ end
6
+
7
+ module Whedon
8
+ class OrcidValidator
9
+ attr_reader :orcid
10
+
11
+ def initialize(orcid)
12
+ @orcid = orcid.strip
13
+ end
14
+
15
+ # Returns true or false
16
+ def validate
17
+ return false unless check_structure
18
+ return false unless check_length
19
+ return false unless check_chars
20
+
21
+ if checksum_char == "X" || checksum_char == "x"
22
+ return checksum == 10
23
+ else
24
+ return checksum == checksum_char.to_i
25
+ end
26
+ end
27
+
28
+ def packed_orcid
29
+ orcid.gsub('-', '')
30
+ end
31
+
32
+ # Returns the last character of the string
33
+ def checksum_char
34
+ packed_orcid[-1]
35
+ end
36
+
37
+ def first_11
38
+ packed_orcid.chop
39
+ end
40
+
41
+ def check_structure
42
+ groups = orcid.split('-')
43
+ if groups.size == 4
44
+ return true
45
+ else
46
+ warn("ORCID looks malformed") and return false
47
+ end
48
+ end
49
+
50
+ def check_length
51
+ if packed_orcid.length == 16
52
+ return true
53
+ else
54
+ warn("ORCID looks to be the wrong length") and return false
55
+ end
56
+ end
57
+
58
+ def check_chars
59
+ valid = true
60
+ first_11.each_char do |c|
61
+ if !c.numeric?
62
+ warn("Invalid ORDIC digit (#{c})")
63
+ valid = false
64
+ end
65
+ end
66
+
67
+ return valid
68
+ end
69
+
70
+ # https://support.orcid.org/knowledgebase/articles/116780-structure-of-the-orcid-identifier
71
+ def checksum
72
+ total = 0
73
+ first_11.each_char do |c|
74
+ total = (total + c.to_i) * 2
75
+ end
76
+
77
+ remainder = total % 11
78
+ result = (12 - remainder) % 11
79
+
80
+ return result
81
+ end
82
+ end
83
+ end