bibsonomy 0.4.0

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.
@@ -0,0 +1,121 @@
1
+ # BibSonomy
2
+
3
+ BibSonomy client for Ruby
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'bibsonomy'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install bibsonomy
20
+
21
+ ## Usage
22
+
23
+ Getting posts from BibSonomy:
24
+
25
+ ```ruby
26
+ require 'bibsonomy'
27
+ api = BibSonomy::API.new('yourusername', 'yourapikey', 'ruby')
28
+ posts = api.get_posts_for_user('jaeschke', 'publication', ['myown'], 0, 20)
29
+ ```
30
+
31
+ Rendering posts with [CSL](http://citationstyles.org/):
32
+
33
+ ```ruby
34
+ require 'bibsonomy/csl'
35
+ csl = BibSonomy::CSL.new('yourusername', 'yourapikey')
36
+ html = csl.render('jaeschke', ['myown'], 100)
37
+ print html
38
+ ```
39
+
40
+ A command line wrapper to the CSL renderer:
41
+
42
+ ```ruby
43
+ #!/usr/bin/ruby
44
+ require 'bibsonomy/csl'
45
+ print BibSonomy::main(ARGV)
46
+ ```
47
+
48
+ ## Jekyll
49
+
50
+ A [Jekyll](http://jekyllrb.com/) plugin:
51
+
52
+ ```ruby
53
+ # coding: utf-8
54
+ require 'time'
55
+ require 'bibsonomy/csl'
56
+
57
+ module Jekyll
58
+
59
+ class BibSonomyPostList < Liquid::Tag
60
+ def initialize(tag_name, text, tokens)
61
+ super
62
+ parts = text.split(/\s+/)
63
+ @user = parts[0]
64
+ @tag = parts[1]
65
+ @count = Integer(parts[2])
66
+ end
67
+
68
+ def render(context)
69
+ site = context.registers[:site]
70
+
71
+ # user name and API key for BibSonomy
72
+ user_name = site.config['bibsonomy_user']
73
+ api_key = site.config['bibsonomy_apikey']
74
+ csl = BibSonomy::CSL.new(user_name, api_key)
75
+
76
+ # target directory for PDF documents
77
+ pdf_dir = site.config['bibsonomy_document_directory']
78
+ csl.pdf_dir = pdf_dir
79
+
80
+ # CSL style for rendering
81
+ style = site.config['bibsonomy_style']
82
+ csl.style = style
83
+
84
+ html = csl.render(@user, [@tag], @count)
85
+
86
+ # set date to now
87
+ context.registers[:page]["date"] = Time.new
88
+
89
+ return html
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+ Liquid::Template.register_tag('bibsonomy', Jekyll::BibSonomyPostList)
96
+ ```
97
+
98
+ The plugin can be used inside Markdown files as follows:
99
+
100
+ ```
101
+ {% bibsonomy jaeschke myown 100 %}
102
+ ```
103
+
104
+ Add the following options to your `_config.yml`:
105
+
106
+ ```
107
+ bibsonomy_user: yourusername
108
+ bibsonomy_apikey: yourapikey
109
+ bibsonomy_document_directory: pdf
110
+ # other: apa, acm-siggraph
111
+ bibsonomy_style: springer-lecture-notes-in-computer-science
112
+ ```
113
+
114
+
115
+ ## Contributing
116
+
117
+ 1. Fork it ( https://github.com/rjoberon/bibsonomy-ruby/fork )
118
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
119
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
120
+ 4. Push to the branch (`git push origin my-new-feature`)
121
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bibsonomy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bibsonomy"
8
+ spec.version = BibSonomy::VERSION
9
+ spec.authors = ["Robert Jäschke"]
10
+ spec.email = ["jaeschke@l3s.de"]
11
+ spec.summary = %q{Wraps the BibSonomy REST API.}
12
+ spec.description = %q{Enables calls to the BibSonomy REST API with Ruby.}
13
+ spec.homepage = "https://github.com/rjoberon/bibsonomy-ruby"
14
+ spec.license = "LGPL 2.1"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest", "~> 5.4"
24
+ spec.add_development_dependency "vcr", "~> 2.9"
25
+ spec.add_development_dependency "webmock", "~> 1.19"
26
+
27
+ spec.add_dependency "faraday", "~> 0.9"
28
+ spec.add_dependency "json", "~> 1.8"
29
+ spec.add_dependency "citeproc", "~> 1.0"
30
+ spec.add_dependency "csl-styles", "~> 1.0"
31
+ spec.add_dependency "bibtex-ruby", "~> 4.0"
32
+
33
+ end
@@ -0,0 +1,7 @@
1
+ require_relative "bibsonomy/version"
2
+ require_relative "bibsonomy/post"
3
+ require_relative "bibsonomy/api"
4
+
5
+ module BibSonomy
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,150 @@
1
+ # coding: utf-8
2
+ require 'faraday'
3
+ require 'json'
4
+
5
+ #
6
+ # TODO:
7
+ # - error handling
8
+ # - getting more than 1000 posts
9
+ #
10
+
11
+ # configuration options
12
+ $API_URL = "https://www.bibsonomy.org/"
13
+ $MAX_POSTS_PER_REQUEST = 20
14
+
15
+ #
16
+ # allowed shortcuts for resource types
17
+ #
18
+ $resource_types_bookmark = ['bookmark', 'bookmarks', 'book', 'link', 'links', 'url']
19
+ $resource_types_bibtex = ['bibtex', 'pub', 'publication', 'publications', 'publ']
20
+
21
+ #
22
+ # The BibSonomy REST client for Ruby.
23
+ #
24
+ module BibSonomy
25
+ class API
26
+
27
+ # Initializes the client with the given credentials.
28
+ #
29
+ # @param user_name [String] The name of the user account used for
30
+ # accessing the API
31
+ #
32
+ # @param api_key [String] The API key corresponding to the user
33
+ # account - can be obtained from
34
+ # http://www.bibsonomy.org/settings?selTab=1
35
+ #
36
+ # @param format [String] The requested return format. One of:
37
+ # 'xml', 'json', 'ruby', 'csl', 'bibtex'. The default is 'ruby'
38
+ # which returns Ruby objects defined by this library. Currently,
39
+ # 'csl' and 'bibtex' are only available for publications.
40
+ #
41
+ def initialize(user_name, api_key, format = 'ruby')
42
+
43
+ # configure output format
44
+ if format == 'ruby'
45
+ @format = 'json'
46
+ @parse = true
47
+ else
48
+ @format = format
49
+ @parse = false
50
+ end
51
+
52
+ @conn = Faraday.new(:url => $API_URL) do |faraday|
53
+ faraday.request :url_encoded # form-encode POST params
54
+ #faraday.response :logger
55
+ faraday.adapter Faraday.default_adapter # make requests with
56
+ # Net::HTTP
57
+ end
58
+
59
+ @conn.basic_auth(user_name, api_key)
60
+
61
+ end
62
+
63
+
64
+ #
65
+ # Get a single post
66
+ #
67
+ # @param user_name [String] The name of the post's owner.
68
+ # @param intra_hash [String] The intrag hash of the post.
69
+ # @return [BibSonomy::Post] the requested post
70
+ #
71
+ def get_post(user_name, intra_hash)
72
+ response = @conn.get "/api/users/" + CGI.escape(user_name) + "/posts/" + CGI.escape(intra_hash), { :format => @format }
73
+
74
+ if @parse
75
+ attributes = JSON.parse(response.body)
76
+ return Post.new(attributes["post"])
77
+ end
78
+ return response.body
79
+ end
80
+
81
+ #
82
+ # Get posts owned by a user, optionally filtered by tags.
83
+ #
84
+ # @param user_name [String] The name of the posts' owner.
85
+ # @resource_type [String] The type of the post. Currently
86
+ # supported are 'bookmark' and 'publication'.
87
+ #
88
+ def get_posts_for_user(user_name, resource_type, tags = nil, start = 0, endc = $MAX_POSTS_PER_REQUEST)
89
+ params = {
90
+ :format => @format,
91
+ :resourcetype => self.get_resource_type(resource_type),
92
+ :start => start,
93
+ :end => endc
94
+ }
95
+ # add tags, if requested
96
+ if tags != nil
97
+ params[:tags] = tags.join(" ")
98
+ end
99
+ response = @conn.get "/api/users/" + CGI.escape(user_name) + "/posts", params
100
+
101
+ if @parse
102
+ posts = JSON.parse(response.body)["posts"]["post"]
103
+ return posts.map { |attributes| Post.new(attributes) }
104
+ end
105
+ return response.body
106
+ end
107
+
108
+ def get_document_href(user_name, intra_hash, file_name)
109
+ return "/api/users/" + CGI.escape(user_name) + "/posts/" + CGI.escape(intra_hash) + "/documents/" + CGI.escape(file_name)
110
+ end
111
+
112
+ #
113
+ # get a document belonging to a post
114
+ #
115
+ def get_document(user_name, intra_hash, file_name)
116
+ response = @conn.get get_document_href(user_name, intra_hash, file_name)
117
+ if response.status == 200
118
+ return [response.body, response.headers['content-type']]
119
+ end
120
+ return nil, nil
121
+ end
122
+
123
+ def get_document_preview(user_name, intra_hash, file_name, size)
124
+ response = get_document_href(user_name, intra_hash, file_name), { :preview => size }
125
+ if response.status = 200
126
+ return [response.body, 'image/jpeg']
127
+ end
128
+ return nil, nil
129
+ end
130
+
131
+ #
132
+ # Convenience method to allow sloppy specification of the resource
133
+ # type.
134
+ #
135
+ # @private
136
+ #
137
+ def get_resource_type(resource_type)
138
+ if $resource_types_bookmark.include? resource_type.downcase()
139
+ return "bookmark"
140
+ end
141
+
142
+ if $resource_types_bibtex.include? resource_type.downcase()
143
+ return "bibtex"
144
+ end
145
+
146
+ raise ArgumentError.new("Unknown resource type: #{resource_type}. Supported resource types are ")
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,395 @@
1
+ # coding: utf-8
2
+
3
+ require 'optparse'
4
+ require 'citeproc'
5
+ require 'csl/styles'
6
+ require 'bibtex'
7
+ require 'json'
8
+ require 'bibsonomy'
9
+
10
+ #
11
+ # Generates a list of publication posts from BibSonomy
12
+ #
13
+ # required parameters:
14
+ # - user name
15
+ # - api key
16
+ # optional parameters:
17
+ # - user name
18
+ # - tags
19
+ # - number of posts
20
+ # - style
21
+ # - directory
22
+ #
23
+ # Changes:
24
+ # 2015-02-24
25
+ # - initial version
26
+ #
27
+ # TODO:
28
+ # - escape data
29
+ # - make sorting, etc. configurable
30
+ # - add link to BibSonomy
31
+ # - automatically rename files (TODO: CSL lacks BibTeX key)
32
+ # - add intra_hash, user_name, DOI to CSL
33
+ # - integrate AJAX abstract
34
+ # - make all options available via command line
35
+
36
+ module BibSonomy
37
+ class CSL
38
+
39
+ #
40
+ # Create a new BibSonomy instance
41
+ # Params:
42
+ # +user_name+:: BibSonomy user name
43
+ # +api_key+:: API key of the given user (get at http://www.bibsonomy.org/settings?selTab=1)
44
+ def initialize(user_name, api_key)
45
+ super()
46
+ @bibsonomy = BibSonomy::API.new(user_name, api_key, 'csl')
47
+ # setting some defaults
48
+ @style = 'apa.csl'
49
+ @pdf_dir = nil
50
+ @css_class = 'publications'
51
+ @year_headings = true
52
+ @public_doc_postfix = '_oa.pdf'
53
+
54
+ # optional parts to be rendered (or not)
55
+ @doi_link = true
56
+ @url_link = true
57
+ @bibtex_link = true
58
+ @bibsonomy_link = true
59
+ @opt_sep = ' | '
60
+ end
61
+
62
+ #
63
+ # Download +count+ posts for the given +user+ and +tag(s)+ and render them with CSL.
64
+ # Params:
65
+ # +user+:: user name
66
+ # +tags+:: an array of tags
67
+ # +count+:: number of posts to download
68
+ def render(user, tags, count)
69
+ # get posts from BibSonomy
70
+ posts = JSON.parse(@bibsonomy.get_posts_for_user(user, 'publication', tags, 0, count))
71
+
72
+ # render them with citeproc
73
+ cp = CiteProc::Processor.new style: @style, format: 'html'
74
+ cp.import posts
75
+
76
+ # to check for duplicate file names
77
+ file_names = []
78
+
79
+ # sort posts by year
80
+ sorted_keys = posts.keys.sort { |a,b| get_sort_posts(posts[b], posts[a]) }
81
+
82
+ result = ""
83
+
84
+ # print first heading
85
+ last_year = 0
86
+
87
+ if @year_headings and sorted_keys.length > 0
88
+ last_year = get_year(posts[sorted_keys[0]])
89
+ result += "<h3>" + last_year + "</h3>"
90
+ end
91
+
92
+ result += "<ul class='#{@publications}'>\n"
93
+ for post_id in sorted_keys
94
+ post = posts[post_id]
95
+
96
+ # print heading
97
+ if @year_headings
98
+ year = get_year(post)
99
+ if year != last_year
100
+ last_year = year
101
+ result += "</ul>\n<h3>" + last_year + "</h3>\n<ul class='#{@publications}'>\n"
102
+ end
103
+ end
104
+
105
+ # render metadata
106
+ csl = cp.render(:bibliography, id: post_id)
107
+ result += "<li class='" + post["type"] + "'>#{csl[0]}"
108
+
109
+ # extract the post's id
110
+ intra_hash, user_name = get_intra_hash(post_id)
111
+
112
+ # optional parts
113
+ options = []
114
+ # attach documents
115
+ if @pdf_dir
116
+ for doc in get_public_docs(post["documents"])
117
+ # fileHash, fileName, md5hash, userName
118
+ file_path = get_document(@bibsonomy, intra_hash, user_name, doc, @pdf_dir, file_names)
119
+ options << "<a href='#{file_path}'>PDF</a>"
120
+ end
121
+ end
122
+ # attach DOI
123
+ doi = post["DOI"]
124
+ if @doi_link and doi != ""
125
+ options << "DOI:<a href='http://dx.doi.org/#{doi}'>#{doi}</a>"
126
+ end
127
+ # attach URL
128
+ url = post["URL"]
129
+ if @url_link and url != ""
130
+ options << "<a href='#{url}'>URL</a>"
131
+ end
132
+ # attach BibTeX
133
+ if @bibtex_link
134
+ options << "<a href='http://www.bibsonomy.org/bib/publication/#{intra_hash}/#{user_name}'>BibTeX</a>"
135
+ end
136
+ # attach link to BibSonomy
137
+ if @bibsonomy_link
138
+ options << "<a href='http://www.bibsonomy.org/publication/#{intra_hash}/#{user_name}'>BibSonomy</a>"
139
+ end
140
+
141
+ # attach options
142
+ if options.length > 0
143
+ result += " <span class='opt'>[" + options.join(@opt_sep) + "]</span>"
144
+ end
145
+
146
+ result += "</li>\n"
147
+ end
148
+ result += "</ul>\n"
149
+
150
+ return result
151
+ end
152
+
153
+ def get_year(post)
154
+ return post["issued"]["literal"]
155
+ end
156
+
157
+ def get_sort_posts(a, b)
158
+ person_a = a["author"]
159
+ if person_a.length == 0
160
+ person_a = a["editor"]
161
+ end
162
+ person_b = b["author"]
163
+ if person_b.length == 0
164
+ person_b = b["editor"]
165
+ end
166
+ return [get_year(a), a["type"], person_b[0]["family"]] <=> [get_year(b), b["type"], person_a[0]["family"]]
167
+ end
168
+
169
+ #
170
+ # only show PDF files and if
171
+ #
172
+ def get_public_docs(documents)
173
+ result = []
174
+ for doc in documents
175
+ file_name = doc["fileName"]
176
+ if file_name.end_with? ".pdf"
177
+ if documents.length < 2 or file_name.end_with? @public_doc_postfix
178
+ result << doc
179
+ end
180
+ end
181
+ end
182
+ return result
183
+ end
184
+
185
+ def warn(m)
186
+ print("WARN: " + m + "\n")
187
+ end
188
+
189
+ #
190
+ # downloads the documents for the posts (if necessary)
191
+ #
192
+ def get_document(bib, intra_hash, user_name, doc, dir, file_names)
193
+ # fileHash, fileName, md5hash, userName
194
+ file_name = doc["fileName"]
195
+ # strip doc prefix for public documents
196
+ if file_name.end_with? @public_doc_postfix
197
+ file_name = file_name[0, file_name.length - @public_doc_postfix.length] + ".pdf"
198
+ end
199
+ # check for possible duplicate file names
200
+ if file_names.include? file_name
201
+ warn "duplicate file name " + file_name + " for post " + intra_hash
202
+ end
203
+ # remember file name
204
+ file_names << file_name
205
+ # produce file path
206
+ file_path = dir + "/" + file_name
207
+ # download PDF if it not already exists
208
+ if not File.exists? file_path
209
+ pdf, mime = bib.get_document(user_name, intra_hash, doc["fileName"])
210
+ if pdf == nil
211
+ warn "could not download file " + intra_hash + "/" + user_name + "/" + file_name
212
+ else
213
+ File.binwrite(file_path, pdf)
214
+ end
215
+ end
216
+ return file_path
217
+ end
218
+
219
+ # format of the post ID for CSL: [0-9a-f]{32}USERNAME
220
+ def get_intra_hash(post_id)
221
+ return [post_id[0, 32], post_id[32, post_id.length]]
222
+ end
223
+
224
+ #
225
+ # setters
226
+ #
227
+
228
+ #
229
+ # Set the output directory for downloaded PDF files (default: +nil+)
230
+ # Params:
231
+ # +pdf_dir+:: directory for downloaded PDF files. If set to +nil+, no documents are downloaded.
232
+ def pdf_dir=(pdf_dir)
233
+ @pdf_dir = pdf_dir
234
+ end
235
+
236
+ #
237
+ # Set the CSL style used for rendering (default: +apa.csl+)
238
+ # Params:
239
+ # +style+:: CSL style used for rendering
240
+ def style=(style)
241
+ @style = style
242
+ end
243
+
244
+ #
245
+ # Enable/disable headings for years (default: enabled)
246
+ # Params:
247
+ # +year_headings+:: boolean indicating whether year headings shall be rendered
248
+ def year_headings=(year_headings)
249
+ @year_headings = year_headings
250
+ end
251
+
252
+ #
253
+ # The CSS class used to render the surrounding +<ul>+ list (default: 'publications')
254
+ # Params:
255
+ # +css_class+:: string indicating the CSS class for rendering the publication list
256
+ def css_class=(css_class)
257
+ @css_class = css_class
258
+ end
259
+
260
+ #
261
+ # Shall links for DOIs be rendered? (default: true)
262
+ # Params:
263
+ # +doi_link+:: render DOI link
264
+ def doi_link=(doi_link)
265
+ @doi_link = doi_link
266
+ end
267
+
268
+ #
269
+ # Shall links for URLs of posts be rendered? (default: true)
270
+ # Params:
271
+ # +url_link+:: render URL link
272
+ def url_link=(url_link)
273
+ @url_link = url_link
274
+ end
275
+
276
+ #
277
+ # Shall links to the BibTeX of a post (in BibSonomy) be rendered? (default: true)
278
+ # Params:
279
+ # +bibtex_link+:: render BibTeX link
280
+ def bibtex_link=(bibtex_link)
281
+ @bibtex_link = bibtex_link
282
+ end
283
+
284
+ #
285
+ # Shall links to BibSonomy be rendered? (default: true)
286
+ # Params:
287
+ # +bibsonomy_link+:: render BibSonomy link
288
+ def bibsonomy_link=(bibsonomy_link)
289
+ @bibsonomy_link = bibsonomy_link
290
+ end
291
+
292
+ #
293
+ # Separator between options (default: ' | ')
294
+ # Params:
295
+ # +opt_sep+:: option separator
296
+ def opt_sep=(opt_sep)
297
+ @opt_sep = opt_sep
298
+ end
299
+
300
+ #
301
+ # When a post has several documents and the filename of one of
302
+ # them ends with +public_doc_postfix+, only this document is
303
+ # downloaded and linked, all other are ignored. (default:
304
+ # '_oa.pdf')
305
+ # Params:
306
+ # +public_doc_postfix+:: postfix to check at document filenames
307
+ def public_doc_postfix=(public_doc_postfix)
308
+ @public_doc_postfix = public_doc_postfix
309
+ end
310
+
311
+ end
312
+
313
+
314
+ # parse command line options
315
+ def self.main(args)
316
+
317
+ # setting default options
318
+ options = OpenStruct.new
319
+ options.documents = false
320
+ options.directory = nil
321
+ options.tags = []
322
+ options.style = "apa.csl"
323
+ options.posts = 1000
324
+
325
+ opt_parser = OptionParser.new do |opts|
326
+ opts.banner = "Usage: csl.rb [options] user_name api_key"
327
+
328
+ opts.separator ""
329
+ opts.separator "Specific options:"
330
+
331
+ # mandatory arguments are handled separately
332
+
333
+ # optional arguments
334
+ opts.on('-u', '--user USER', 'return posts for USER instead of user') { |v| options[:user] = v }
335
+ opts.on('-t', '--tags TAG,TAG,...', Array, 'return posts with the given tags') { |v| options[:tags] = v }
336
+ opts.on('-s', '--style STYLE', 'use CSL style STYLE for rendering') { |v| options[:style] = v }
337
+ opts.on('-n', '--number-of-posts [COUNT]', Integer, 'number of posts to download') { |v| options[:posts] = v }
338
+ opts.on('-d', '--directory DIR', 'target directory', ' (if not given, no documents are downloaed)') { |v| options[:directory] = v }
339
+
340
+ opts.separator ""
341
+ opts.separator "Common options:"
342
+
343
+ opts.on('-h', '--help', 'show this help message and exit') do
344
+ puts opts
345
+ exit
346
+ end
347
+
348
+ opts.on_tail('-v', "--version", "show version") do
349
+ puts BibSonomy::VERSION
350
+ exit
351
+ end
352
+
353
+ end
354
+
355
+ opt_parser.parse!(args)
356
+
357
+ # handle mandatory arguments
358
+ begin
359
+ mandatory = [:user_name, :api_key]
360
+ missing = []
361
+
362
+ options[:api_key] = args.pop
363
+ missing << :api_key unless options[:api_key]
364
+
365
+ options[:user_name] = args.pop
366
+ missing << :user_name unless options[:user_name]
367
+
368
+ if not missing.empty?
369
+ puts "Missing options: #{missing.join(', ')}"
370
+ puts opt_parser
371
+ exit
372
+ end
373
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
374
+ puts $!.to_s
375
+ puts opt_parser
376
+ exit
377
+ end
378
+
379
+ # set defaults for optional arguments
380
+ options[:user] = options[:user_name] unless options[:user]
381
+
382
+ #
383
+ # do the actual work
384
+ #
385
+ csl = BibSonomy::CSL.new(options[:user_name], options[:api_key])
386
+ csl.pdf_dir(options[:directory])
387
+ csl.style(options[:style])
388
+
389
+ html = csl.render(options[:user], options[:tags], options[:posts])
390
+
391
+ return html
392
+
393
+ end
394
+
395
+ end