rbibtex 0.0.2 → 0.1.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.
- data/CHANGELOG +18 -0
- data/README +5 -3
- data/bin/rbib2bib +101 -131
- data/bin/rbib2html +68 -15
- data/bin/rbib2txt +101 -31
- data/lib/rbibtex/pre-setup.rb +1 -1
- data/lib/rbibtex/rbibtex.y +58 -54
- data/lib/rbibtex/string.rb +64 -0
- data/lib/rbibtex/transform.rb +75 -0
- data/lib/rbibtex/types.rb +344 -0
- data/lib/rbibtex.rb +7 -0
- data/papers.bib +218 -165
- data/papers.html +116 -118
- data/papers.new.bib +257 -0
- data/test/test.bib +0 -0
- data/test/test_bib.rb +22 -0
- data/test/test_string.rb +88 -0
- metadata +13 -4
- data/test.bib +0 -5
data/CHANGELOG
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
0.1.0
|
2
|
+
* ! Incompatible API change. Removed entry.properties property. The data can
|
3
|
+
now be accessed as described below. !
|
4
|
+
* ! More incompatible changes. Renamed File to BibTeXFile, though I'm not yet
|
5
|
+
satisfied with the name. More changes can be expected. !
|
6
|
+
* Now parsing and interpreting strings. See BibTeXFile#interpolate.
|
7
|
+
* Moved common transformation logic into the library
|
8
|
+
* Made entry directly accessible like a hash. That allows to sort also by key
|
9
|
+
and type
|
10
|
+
* Made entry access case / type insensitive.
|
11
|
+
(I.e. entry["Title"] = entry["title"] = entry[:TITLE])
|
12
|
+
* Made entry accessible by method also. (I.e. entry[:Title] = entry.title
|
13
|
+
* Made entry use Enumerable for all the goodness of it. Each iterates about
|
14
|
+
all properties except key and type.
|
15
|
+
* Allow second line to be indented
|
16
|
+
* Added some documentation
|
17
|
+
* Added some unit tests (fixing a few layout bugs on the way. Should have done TDD).
|
18
|
+
|
1
19
|
0.0.2
|
2
20
|
* Simplified Grammar a bit
|
3
21
|
* Now parsing @comment correctly
|
data/README
CHANGED
@@ -53,12 +53,14 @@ Brian
|
|
53
53
|
* The binaries are only quick hacks for my specific applications right now,
|
54
54
|
they should be rewritten and extended.
|
55
55
|
* I should transfer functionality from rbib2bib into a library usable by all
|
56
|
-
programs.
|
56
|
+
programs. (Partially done)
|
57
57
|
* The full bibtex format is not parsed, I only allow for a specific subset
|
58
|
-
right now.
|
59
|
-
*
|
58
|
+
right now. (May be done, I'm not quite sure what is the exact standard)
|
59
|
+
* Add automatic abbreviation creation as a fun quiz.
|
60
60
|
* Create the documentation after having racc'ed the .y files, then cleanup
|
61
61
|
again before packaging.
|
62
|
+
* Move string handling code into module, such that we no longer trample around
|
63
|
+
in the standard library.
|
62
64
|
|
63
65
|
## Authors
|
64
66
|
This was implemented by Brian Schroeder.
|
data/bin/rbib2bib
CHANGED
@@ -1,147 +1,117 @@
|
|
1
1
|
#! /home/bschroed//bin/ruby
|
2
|
-
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def
|
22
|
-
|
2
|
+
#
|
3
|
+
# Pretty print a bibtex file.
|
4
|
+
#
|
5
|
+
# (c) 2005 Brian Schroeder
|
6
|
+
#
|
7
|
+
# http://ruby.brian-schroeder.de/rbibtex/
|
8
|
+
#
|
9
|
+
|
10
|
+
require "rbibtex"
|
11
|
+
require "rbibtex/transform"
|
12
|
+
require 'optparse'
|
13
|
+
|
14
|
+
include BibTeX
|
15
|
+
# Define Interface
|
16
|
+
class B2BOptions < OptionParser
|
17
|
+
VERSION = "0.0.3"
|
18
|
+
|
19
|
+
attr_reader :wrap, :wrap_width, :comment, :interpolate
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
super()
|
23
|
+
|
24
|
+
# Defaults
|
25
|
+
@comment = true
|
26
|
+
@wrap = true
|
27
|
+
@wrap_width = 78
|
28
|
+
|
29
|
+
# Parser Actions
|
30
|
+
self.banner = "Usage: #{ __FILE__ } [-o OUTPUT] [OPTIONS] INPUT [INPUT_1 ...]"
|
31
|
+
self.separator ""
|
32
|
+
self.separator "Formatting"
|
33
|
+
self.on("-C", "--no-comment",
|
34
|
+
"Suppress generation of 'Created by' comment.") { @comment = false }
|
35
|
+
self.on("-w", "--wrap [WIDTH]", Integer,
|
36
|
+
"Reformat and wrap text (the default).") { | w | @wrap_width = w || @wrap_width; @wrap = true }
|
37
|
+
self.on("-W", "--no-wrap",
|
38
|
+
"Do not reformat and wrap text.") { @wrap = false }
|
39
|
+
self.on("-S", "--interpolate-strings",
|
40
|
+
"Replace abbreviations by their full string.") { @interpolate = true }
|
23
41
|
end
|
24
42
|
|
25
|
-
def
|
26
|
-
|
43
|
+
def parse!(*args)
|
44
|
+
super
|
27
45
|
end
|
28
46
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
def unwrap
|
34
|
-
self.split(/\n\n+/).map { | paragraph |
|
35
|
-
paragraph.gsub(/((?:\n|^)\S[^\n]*)\n(?=\S)/, "\\1 ")
|
36
|
-
}.join("\n\n")
|
37
|
-
end
|
47
|
+
include Options::ApplicationOptions
|
48
|
+
include Options::SortOptions
|
49
|
+
include Options::SortFieldsOptions
|
38
50
|
end
|
39
51
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
# Defaults
|
52
|
-
@top_fields = [:author, :title, :year]
|
53
|
-
@bottom_fields = [:note, :pdf, :ps, :www]
|
54
|
-
@comment = true
|
55
|
-
@output = STDOUT
|
56
|
-
|
57
|
-
# Parser Actions
|
58
|
-
self.banner = "Usage: #{ __FILE__ } [-o OUTPUT] [OPTIONS] INPUT [INPUT_1 ...]"
|
59
|
-
self.on("-o FILENAME", "--output FILENAME", String,
|
60
|
-
"Set the output filename. Writes to stdout if nothing is given") { | v | @output = File.open(v, 'w') }
|
61
|
-
self.on("-s", "--sort-by", "--sort_by Field_1,Field2,...", Array,
|
62
|
-
"Specify fields that shall be used for sorting of the file") { | v | @sort_by = v.map { | e | e.downcase.to_sym } }
|
63
|
-
self.on("-t", "--top_fields Field_1,Field2,...", Array,
|
64
|
-
"Specify a list of fields that are written first in the output") { | v | @top_fields = v.map { | e | e.downcase.to_sym }
|
65
|
-
@bottom_fields -= @top_fields }
|
66
|
-
self.on("-b", "--bottom_fields Field_1,Field2,...", Array,
|
67
|
-
"Specify a list of fields that are written last in the output") { | v | @bottom_fields = v.map { | e | e.downcase.to_sym }
|
68
|
-
@top_fields -= @bottom_fields }
|
69
|
-
self.on("-C", "--no-comment",
|
70
|
-
"Suppress generation of 'Created by' comment") { @comment = false }
|
71
|
-
self.on("-?", "--help",
|
72
|
-
"Show this help") { @help = true }
|
73
|
-
self.on("-v", "--version",
|
74
|
-
"Output version number and exit") { @show_version = true }
|
75
|
-
end
|
76
|
-
|
77
|
-
def parse!(*args)
|
78
|
-
super
|
79
|
-
raise "You can't specify an empty list of fields for sorting" if @sort_by and @sort_by.empty?
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
options = Options.new
|
84
|
-
begin
|
85
|
-
options.parse!(ARGV)
|
86
|
-
rescue => e
|
87
|
-
puts "Invalid Commandline Arguments"
|
88
|
-
puts e
|
89
|
-
puts options
|
90
|
-
exit
|
91
|
-
end
|
92
|
-
|
93
|
-
if options.show_version
|
94
|
-
puts VERSION
|
95
|
-
exit
|
96
|
-
end
|
97
|
-
|
98
|
-
if options.help
|
99
|
-
puts options
|
100
|
-
exit
|
101
|
-
end
|
52
|
+
# Parse the options
|
53
|
+
options = B2BOptions.new
|
54
|
+
begin
|
55
|
+
options.parse!(ARGV)
|
56
|
+
rescue => e
|
57
|
+
puts "Invalid Commandline Arguments"
|
58
|
+
puts e
|
59
|
+
puts options
|
60
|
+
exit
|
61
|
+
end
|
102
62
|
|
103
|
-
|
63
|
+
file = BibTeXFile.parse( ARGF.read )
|
64
|
+
file.normalize!
|
65
|
+
file.interpolate! if options.interpolate
|
66
|
+
O = options.output
|
104
67
|
|
68
|
+
if @comment
|
69
|
+
O.puts "@comment { Pretty Printed using rbib2bib }"
|
70
|
+
end
|
105
71
|
|
106
|
-
|
107
|
-
|
72
|
+
# Calculate maximum key length
|
73
|
+
max_key_length = file.entries.inject(0) { | r, entry |
|
74
|
+
entry.inject(r) { | r, (k, _) |
|
75
|
+
[r, k.to_s.length].max
|
76
|
+
}
|
77
|
+
}
|
108
78
|
|
109
|
-
|
110
|
-
|
111
|
-
|
79
|
+
def extract_properties(properties, fields)
|
80
|
+
fields.map { | k | [k, properties.delete(k)] }.select { | (_, v) | v }
|
81
|
+
end
|
112
82
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
[r, k.to_s.length].max
|
117
|
-
}
|
118
|
-
}
|
83
|
+
def rewrap(text, indent, width)
|
84
|
+
text.to_str.deindent_hanging.unwrap.wrap(width - indent).indent_hanging(indent)
|
85
|
+
end
|
119
86
|
|
120
|
-
|
121
|
-
|
122
|
-
|
87
|
+
if options.sort_by
|
88
|
+
file.sort_by(*options.sort_by)
|
89
|
+
end
|
123
90
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
91
|
+
reformat = if options.wrap
|
92
|
+
lambda do | text, indent | rewrap(text, indent, options.wrap_width).remove_trailing_whitespace end
|
93
|
+
else
|
94
|
+
lambda do | text, indent | text.remove_trailing_whitespace end
|
95
|
+
end
|
96
|
+
|
97
|
+
puts file.elements.map { | element |
|
98
|
+
|
99
|
+
if element.is_a?Comment
|
100
|
+
"@comment { #{reformat[element, 11]} }"
|
101
|
+
elsif element.is_a?Abbreviation
|
102
|
+
%(@string{#{element.key} = #{reformat[element.string.to_bib, max_key_length + 6]}})
|
103
|
+
else
|
104
|
+
properties = element.inject({}) { | r, (k, v) | r[k] = v; r }
|
105
|
+
top = extract_properties(properties, options.top_fields)
|
106
|
+
bottom = extract_properties(properties, options.bottom_fields)
|
107
|
+
middle = properties.to_a.map { | (k, v) | [k.to_s, v] }.sort
|
129
108
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
"@#{entry.type}{#{entry.key},\n" +
|
139
|
-
(
|
140
|
-
(top + middle + bottom).map { | (k, v) |
|
141
|
-
%( #{k}).ljust(max_key_length+2) + " = {#{v.deindent_hanging.unwrap.wrap(78-(max_key_length+6)).indent_hanging(max_key_length+6)}}"
|
142
|
-
}.join(",\n")
|
143
|
-
) + "\n}"
|
144
|
-
end
|
145
|
-
}.join("\n\n\n")
|
109
|
+
"@#{element.type}{#{element.key},\n" +
|
110
|
+
(
|
111
|
+
(top + middle + bottom).map { | (k, v) |
|
112
|
+
%( #{Entry::CAPITALIZATION[k]}).ljust(max_key_length+2) + " = " + reformat[v.to_bib, max_key_length + 6]
|
113
|
+
}.join(",\n")
|
114
|
+
) + "\n}"
|
115
|
+
end
|
116
|
+
}.join("\n\n\n")
|
146
117
|
|
147
|
-
end
|
data/bin/rbib2html
CHANGED
@@ -1,13 +1,61 @@
|
|
1
1
|
#! /home/bschroed//bin/ruby
|
2
2
|
|
3
|
-
$:.unshift("~/lib/ruby")
|
4
|
-
|
5
3
|
require "rbibtex"
|
4
|
+
require "rbibtex/transform"
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
include BibTeX
|
8
|
+
# Define Interface
|
9
|
+
class B2HTMLOptions < OptionParser
|
10
|
+
VERSION = "0.0.3"
|
11
|
+
|
12
|
+
attr_reader :wrap, :wrap_width, :comment, :interpolate
|
6
13
|
|
7
|
-
|
8
|
-
|
14
|
+
def initialize
|
15
|
+
super()
|
9
16
|
|
10
|
-
|
17
|
+
# Defaults
|
18
|
+
@comment = true
|
19
|
+
@wrap = true
|
20
|
+
@wrap_width = 78
|
21
|
+
|
22
|
+
# Parser Actions
|
23
|
+
self.banner = "Usage: #{ __FILE__ } [-o OUTPUT] [OPTIONS] INPUT [INPUT_1 ...]"
|
24
|
+
self.separator ""
|
25
|
+
self.separator "Formatting"
|
26
|
+
self.on("-C", "--no-comment",
|
27
|
+
"Suppress generation of 'Created by' comment.") { @comment = false }
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse!(*args)
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
include Options::ApplicationOptions
|
35
|
+
include Options::SortOptions
|
36
|
+
end
|
37
|
+
|
38
|
+
# Parse the options
|
39
|
+
options = B2HTMLOptions.new
|
40
|
+
begin
|
41
|
+
options.parse!(ARGV)
|
42
|
+
rescue => e
|
43
|
+
puts "Invalid Commandline Arguments"
|
44
|
+
puts e
|
45
|
+
puts options
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
|
49
|
+
file = BibTeXFile.parse( ARGF.read )
|
50
|
+
file.normalize!
|
51
|
+
file.interpolate!
|
52
|
+
O = options.output
|
53
|
+
|
54
|
+
if options.sort_by
|
55
|
+
file.sort_by(*options.sort_by)
|
56
|
+
end
|
57
|
+
|
58
|
+
O.puts <<HTML_HEADER
|
11
59
|
<html>
|
12
60
|
<head>
|
13
61
|
<style type="text/css">
|
@@ -19,33 +67,38 @@ puts <<HTML_HEADER
|
|
19
67
|
</style>
|
20
68
|
</head>
|
21
69
|
<body>
|
70
|
+
<h1>Bibliography created from #{ARGV.inspect}</h1>
|
22
71
|
HTML_HEADER
|
23
72
|
|
73
|
+
|
24
74
|
first = [:author, :title, :year]
|
25
75
|
last = [:note]
|
26
76
|
links = [:pdf, :ps]
|
27
77
|
|
28
|
-
file.each do | entry |
|
78
|
+
file.entries.each do | entry |
|
29
79
|
|
30
|
-
p = entry.
|
80
|
+
p = entry.inject({}) { | r, (k, v) | r[k] = v; r }
|
31
81
|
f = first.map { | k | [k, p.delete(k)] }
|
32
82
|
l = last.map { | k | [k, p.delete(k)] }
|
33
83
|
a = links.map { | k | [k, p.delete(k)] }
|
34
84
|
p = p.to_a.map{ | (k, v) | [k.to_s, v] }.sort
|
35
85
|
|
36
|
-
puts %(<div class="entry">)
|
86
|
+
O.puts %(<div class="entry">)
|
37
87
|
(f + p).each do | (k, v) |
|
38
|
-
puts %(<span class="#{k}">#{v}</span>)
|
88
|
+
O.puts %(<span class="#{k}">#{v}</span>)
|
39
89
|
end
|
40
|
-
puts %(<span class="links")
|
90
|
+
O.puts %(<span class="links")
|
41
91
|
a.each do | k, v |
|
42
|
-
puts %(<a href="#{v}">#{k}</a>)
|
92
|
+
O.puts %(<a href="#{v}">#{k}</a>)
|
43
93
|
end
|
44
|
-
puts %(</span)
|
94
|
+
O.puts %(</span)
|
45
95
|
(l).each do | (k, v) |
|
46
|
-
puts %(<span class="#{k}">#{v}</span>)
|
96
|
+
O.puts %(<span class="#{k}">#{v}</span>)
|
47
97
|
end
|
48
|
-
puts %(</div>)
|
98
|
+
O.puts %(</div>)
|
49
99
|
end
|
50
100
|
|
51
|
-
|
101
|
+
if @comment
|
102
|
+
O.puts %(<div class="comment">Pretty Printed using rbib2bib</div>)
|
103
|
+
end
|
104
|
+
O.puts "</body></html>"
|
data/bin/rbib2txt
CHANGED
@@ -1,41 +1,111 @@
|
|
1
1
|
#! /home/bschroed//bin/ruby
|
2
|
-
|
3
|
-
|
2
|
+
#
|
3
|
+
# Convert a bibtex file to plain text
|
4
|
+
#
|
5
|
+
# (c) 2005 Brian Schroeder
|
6
|
+
#
|
7
|
+
# http://ruby.brian-schroeder.de/rbibtex/
|
8
|
+
#
|
4
9
|
|
5
10
|
require "rbibtex"
|
11
|
+
require "rbibtex/transform"
|
12
|
+
require 'optparse'
|
6
13
|
|
7
|
-
|
8
|
-
|
14
|
+
include BibTeX
|
15
|
+
# Define Interface
|
16
|
+
class B2TXTOptions < OptionParser
|
17
|
+
VERSION = "0.0.3"
|
18
|
+
|
19
|
+
attr_reader :wrap, :wrap_width, :comment, :interpolate
|
9
20
|
|
10
|
-
|
11
|
-
|
12
|
-
links = [:pdf, :ps]
|
21
|
+
def initialize
|
22
|
+
super()
|
13
23
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
next unless v
|
30
|
-
print %( #{k}: ).ljust(20)
|
31
|
-
puts v.gsub(/\n\s*/, "\n" + (" " * 20))
|
24
|
+
# Defaults
|
25
|
+
@comment = true
|
26
|
+
@wrap = true
|
27
|
+
@wrap_width = 78
|
28
|
+
|
29
|
+
# Parser Actions
|
30
|
+
self.banner = "Usage: #{ __FILE__ } [-o OUTPUT] [OPTIONS] INPUT [INPUT_1 ...]"
|
31
|
+
self.separator ""
|
32
|
+
self.separator "Formatting"
|
33
|
+
self.on("-C", "--no-comment",
|
34
|
+
"Suppress generation of 'Created by' comment.") { @comment = false }
|
35
|
+
self.on("-w", "--wrap [WIDTH]", Integer,
|
36
|
+
"Reformat and wrap text (the default).") { | w | @wrap_width = w || @wrap_width; @wrap = true }
|
37
|
+
self.on("-W", "--no-wrap",
|
38
|
+
"Do not reformat and wrap text.") { @wrap = false }
|
32
39
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
puts v.gsub(/\n\s*/, "\n" + (" " * 20))
|
40
|
+
|
41
|
+
def parse!(*args)
|
42
|
+
super
|
37
43
|
end
|
38
|
-
|
39
|
-
|
44
|
+
|
45
|
+
include Options::ApplicationOptions
|
46
|
+
include Options::SortOptions
|
47
|
+
include Options::SortFieldsOptions
|
48
|
+
end
|
49
|
+
|
50
|
+
# Parse the options
|
51
|
+
options = B2TXTOptions.new
|
52
|
+
begin
|
53
|
+
options.parse!(ARGV)
|
54
|
+
rescue => e
|
55
|
+
puts "Invalid Commandline Arguments"
|
56
|
+
puts e
|
57
|
+
puts options
|
58
|
+
exit
|
40
59
|
end
|
41
60
|
|
61
|
+
file = BibTeXFile.parse( ARGF.read )
|
62
|
+
file.normalize!
|
63
|
+
file.interpolate!
|
64
|
+
O = options.output
|
65
|
+
|
66
|
+
if @comment
|
67
|
+
O.puts "# Pretty Printed using rbib2bib"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Calculate maximum key length
|
71
|
+
max_key_length = file.entries.inject(0) { | r, entry |
|
72
|
+
entry.inject(r) { | r, (k, _) |
|
73
|
+
[r, k.to_s.length].max
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
def extract_properties(properties, fields)
|
78
|
+
fields.map { | k | [k, properties.delete(k)] }.select { | (_, v) | v }
|
79
|
+
end
|
80
|
+
|
81
|
+
def rewrap(text, indent, width)
|
82
|
+
text.to_str.deindent_hanging.unwrap.wrap(width - indent).indent_hanging(indent)
|
83
|
+
end
|
84
|
+
|
85
|
+
if options.sort_by
|
86
|
+
file.sort_by(*options.sort_by)
|
87
|
+
end
|
88
|
+
|
89
|
+
reformat = if options.wrap
|
90
|
+
lambda do | text, indent | rewrap(text, indent, options.wrap_width).remove_trailing_whitespace end
|
91
|
+
else
|
92
|
+
lambda do | text, indent | text.remove_trailing_whitespace end
|
93
|
+
end
|
94
|
+
|
95
|
+
puts file.elements.map { | element |
|
96
|
+
|
97
|
+
if element.is_a?Comment
|
98
|
+
reformat[element, 11].gsub(/^/, "# ")
|
99
|
+
elsif element.is_a?Abbreviation
|
100
|
+
else
|
101
|
+
properties = element.inject({}) { | r, (k, v) | r[k] = v; r }
|
102
|
+
top = extract_properties(properties, options.top_fields)
|
103
|
+
bottom = extract_properties(properties, options.bottom_fields)
|
104
|
+
middle = properties.to_a.map { | (k, v) | [k.to_s, v] }.sort
|
105
|
+
|
106
|
+
(top + middle + bottom).map { | (k, v) |
|
107
|
+
%(#{Entry::CAPITALIZATION[k]}:).ljust(max_key_length) + " " + reformat[v.to_s, max_key_length + 2]
|
108
|
+
}.join("\n") + "\n" + \
|
109
|
+
(%(Type:).ljust(max_key_length) + " " + reformat[element.type, max_key_length + 2])
|
110
|
+
end
|
111
|
+
}.join("\n\n" + ("-" * options.wrap_width) + "\n\n")
|
data/lib/rbibtex/pre-setup.rb
CHANGED