├── papers2bib ├── bibhtmltable ├── bibmerge ├── bibdump ├── README ├── papers_library.rb └── bib_library.rb /papers2bib: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby1.9 2 | 3 | $:.unshift(File.dirname(__FILE__)) 4 | 5 | require 'bib_library.rb' 6 | require 'papers_library.rb' 7 | require 'getoptlong' 8 | require 'kconv' 9 | 10 | $stdout.set_encoding("EUC-JP", "UTF-8") 11 | 12 | # 13 | # main 14 | # 15 | 16 | if __FILE__ == $0 17 | require 'optparse' 18 | 19 | opts = { # default options 20 | :mode => "plain", 21 | :draft => false, 22 | :encoding => "UTF-8", 23 | :key_file => "" # if it's null string, try to generate file by basename 24 | 25 | } 26 | 27 | ARGV.options do |o| 28 | o.banner = "ruby #{$0} [options] Papers_BIB_File" 29 | o.separator "Options:" 30 | 31 | o.on("-K KEY_FILE", "--key-file KEY_FILE", "Specify keywords file") {|x| opts[:key_file] = x } 32 | 33 | o.parse! 34 | 35 | end 36 | 37 | if ARGV.size == 1 38 | bl = PapersBibLibrary.new(ARGV[0], opts) 39 | bl.out($stdout) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /bibhtmltable: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Read bib file and create link output for 4 | # 5 | # 6 | 7 | $:.unshift(File.dirname(__FILE__)) 8 | 9 | require 'bib_library.rb' 10 | require 'papers_library.rb' 11 | require 'getoptlong' 12 | require 'kconv' 13 | require 'date' 14 | require 'ap' 15 | 16 | $stdout.set_encoding("UTF-8") 17 | 18 | # 19 | # main 20 | # 21 | 22 | if __FILE__ == $0 23 | require 'optparse' 24 | 25 | opts = { # default options 26 | :mode => "plain", 27 | :draft => false, 28 | :encoding => "UTF-8", 29 | :output_encoding => "UTF-8", 30 | :key_file => "", # if it's null string, try to generate file by basename 31 | :bib_dir => "./bib", 32 | :force => false, 33 | :timestamp => true, 34 | :write => false, 35 | :list => false, 36 | :mismatch_dump => false 37 | } 38 | 39 | ARGV.options do |o| 40 | o.banner = "ruby #{$0} [options] BIB_File {cite_keys..}" 41 | o.separator "Options:" 42 | o.parse! 43 | end 44 | 45 | if ARGV.size >= 1 46 | blib = BibLibrary.new(ARGV.shift, opts) 47 | cite_keys = blib.keys.sort 48 | if ARGV.size > 0 49 | cite_keys = ARGV 50 | end 51 | 52 | cite_keys.each do |ck| 53 | bib = blib[ck] 54 | bib.prep 55 | au = bib.authors || [ ] 56 | f = "#{ck}.pdf" 57 | f.gsub!(/:/, '_') 58 | puts "" 59 | puts "#{ck}" 60 | puts "#{bib.title}" 61 | puts "#{au.join(', ')}" 62 | puts "" 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /bibmerge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Read several bib files and merge into one 4 | # 5 | # check the values of: 6 | # date-written-via-bibdump 7 | # date-modified 8 | # 9 | 10 | $:.unshift(File.dirname(__FILE__)) 11 | 12 | require 'bib_library.rb' 13 | require 'papers_library.rb' 14 | require 'getoptlong' 15 | require 'kconv' 16 | require 'date' 17 | 18 | $stdout.set_encoding("EUC-JP", "UTF-8") 19 | 20 | # 21 | # main 22 | # 23 | 24 | if __FILE__ == $0 25 | require 'optparse' 26 | 27 | opts = { # default options 28 | :mode => "plain", 29 | :draft => false, 30 | :encoding => "UTF-8", 31 | :output_encoding => "UTF-8", 32 | :key_file => "", # if it's null string, try to generate file by basename 33 | :bib_dir => "./bib", 34 | :force => false, 35 | :timestamp => true, 36 | :add => false, 37 | :overwrite => false, 38 | :group => nil, 39 | :dep_dump => false 40 | } 41 | 42 | ARGV.options do |o| 43 | o.banner = "ruby #{$0} [options] BIB_File bib_keys..." 44 | o.separator "Options:" 45 | o.on("--utf-8", "-u", "Set both i/o encoding to UTF-8") {|x| opts[:encoding] = "UTF-8" ; opts[:output_encoding] = opts[:encoding] } 46 | o.on("--euc-jp", "-e", "Set both i/o encoding to EUC-JP") {|x| opts[:encoding] = "EUC-JP" ; opts[:output_encoding] = opts[:encoding] } 47 | o.on("--sjis", "-s", "Set both i/o encoding to Shift_JIS") {|x| opts[:encoding] = "Shift_JIS" ; opts[:output_encoding] = opts[:encoding] } 48 | 49 | o.on("--output-utf-8", "Set output encoding to UTF-8") {|x| opts[:output_encoding] = "UTF-8" } 50 | o.on("--output-euc-jp", "Set output encoding to EUC-JP") {|x| opts[:output_encoding] = "EUC-JP" } 51 | o.on("--output-sjis", "Set output encoding to Shift_JIS") {|x| opts[:output_encoding] = "Shift_JIS" } 52 | 53 | o.on("--bib-dir", "-B DIR", "Set bib dir (currently ./bib)") {|x| opts[:bib_dir] = x } 54 | 55 | o.on("--force", "-f", "Force to overwrite files") {|x| opts[:force] = true } 56 | o.on("--no-time-stamp", "-t", "don't add bibdump date") {|x| opts[:timestamp] = false } 57 | o.on("--add", "-a", "Add to primary if missing") {|x| opts[:add] = true } 58 | o.on("--overwrite", "-o", "Do overwrite if needed") {|x| opts[:overwrite] = true } 59 | o.on("--dump-mismatch", "-d", "Dump mismatched entries") {|x| opts[:dump_mismatch] = true } 60 | 61 | o.on("--group GROUP", "-G GROUP", "Configure group mode") {|x| opts[:group] = x } 62 | 63 | o.on("--dep-dump", "-p", "Dump dependencies") {|x| opts[:dep_dump] = true } 64 | 65 | o.parse! 66 | end 67 | 68 | 69 | if ARGV.size >= 1 70 | if opts[:dep_dump] == true 71 | blib = BibLibrary.new(nil, opts) 72 | ARGV.each do |f| 73 | bibfile = blib.mkbibpath(f) 74 | if bibfile == nil 75 | STDERR.puts "#{f} not found" 76 | else 77 | puts bibfile 78 | end 79 | end 80 | else 81 | blib = BibLibrary.new(ARGV.shift, opts) 82 | ARGV.each do |f| 83 | bibfile = blib.mkbibpath(f) 84 | if bibfile == nil 85 | STDERR.puts "#{f} not found" 86 | else 87 | new_blib = BibLibrary.new(bibfile, opts) 88 | blib.merge(new_blib) 89 | end 90 | end 91 | blib.out(STDOUT) 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /bibdump: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Read bib file and check the difference for merge 4 | # 5 | 6 | $:.unshift(File.dirname(__FILE__)) 7 | 8 | require 'bib_library.rb' 9 | require 'papers_library.rb' 10 | require 'getoptlong' 11 | require 'kconv' 12 | require 'date' 13 | require 'ap' 14 | 15 | $stdout.set_encoding("EUC-JP", "UTF-8") 16 | 17 | # 18 | # main 19 | # 20 | 21 | if __FILE__ == $0 22 | require 'optparse' 23 | 24 | opts = { # default options 25 | :mode => "plain", 26 | :draft => false, 27 | :encoding => "UTF-8", 28 | :output_encoding => "UTF-8", 29 | :key_file => "", # if it's null string, try to generate file by basename 30 | :bib_dir => "./bib", 31 | :force => false, 32 | :timestamp => true, 33 | :write => false, 34 | :list => false, 35 | :mismatch_dump => false 36 | } 37 | 38 | ARGV.options do |o| 39 | o.banner = "ruby #{$0} [options] BIB_File {cite_keys..}" 40 | o.separator "Options:" 41 | o.on("--utf-8", "-u", "Set both i/o encoding to UTF-8") {|x| opts[:encoding] = "UTF-8" ; opts[:output_encoding] = opts[:encoding] } 42 | o.on("--euc-jp", "-e", "Set both i/o encoding to EUC-JP") {|x| opts[:encoding] = "EUC-JP" ; opts[:output_encoding] = opts[:encoding] } 43 | o.on("--sjis", "-s", "Set both i/o encoding to Shift_JIS") {|x| opts[:encoding] = "Shift_JIS" ; opts[:output_encoding] = opts[:encoding] } 44 | 45 | o.on("--output-utf-8", "Set output encoding to UTF-8") {|x| opts[:output_encoding] = "UTF-8" } 46 | o.on("--output-euc-jp", "Set output encoding to EUC-JP") {|x| opts[:output_encoding] = "EUC-JP" } 47 | o.on("--output-sjis", "Set output encoding to Shift_JIS") {|x| opts[:output_encoding] = "Shift_JIS" } 48 | 49 | o.on("--bib-dir", "-B DIR", "Set bib dir (currently ./bib)") {|x| opts[:bib_dir] = x } 50 | 51 | o.on("--force", "-f", "Force to overwrite files") {|x| opts[:force] = true } 52 | o.on("--no-time-stamp", "-t", "don't add bibdump date") {|x| opts[:timestamp] = false } 53 | o.on("--write", "-w", "Write out each entries in each file in the output-dir") {|x| opts[:write] = true } 54 | o.on("--list", "-l", "List bib cite keys") {|x| opts[:list] = true } 55 | 56 | o.parse! 57 | end 58 | 59 | if ARGV.size >= 1 60 | blib = BibLibrary.new(ARGV.shift, opts) 61 | cite_keys = blib.keys.sort 62 | if ARGV.size > 0 63 | cite_keys = ARGV 64 | end 65 | 66 | # List cite keys. If parameters are given, only list that key if exists 67 | if opts[:list] 68 | cite_keys.each do |k| 69 | puts k if blib.has_key?(k) 70 | end 71 | 72 | # Write out each entries in the dir, 73 | elsif opts[:write] 74 | marker_lines = [] 75 | if opts[:timestamp] 76 | marker_lines = [ ", date-written-via-bibdump = {#{DateTime.now.to_s}}" ] 77 | end 78 | unless File.directory?(opts[:bib_dir]) 79 | puts "Directory #{opts[:bib_dir]} does not exists" 80 | else 81 | cite_keys.each do |c| 82 | unless blib.has_key?(c) 83 | puts "cite key <#{c}> does not exist" 84 | else 85 | e = blib[c] 86 | file = blib.mkbibpath(c, true) 87 | if File.file?(file) and opts[:force] == false 88 | puts "File #{file} alrady exists -- don't overwrite" 89 | else 90 | e.write_to_file(file, opts, marker_lines) 91 | end 92 | end 93 | end 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Uitlity scripts for Papers 2 | 3 | Requires Ruby 1.9 for internationalized code handling. 4 | 5 | Papers is available from 6 | http://mekentosj.com/papers/ 7 | 8 | 9 | * papers2bib 10 | 11 | Modify bib library exported from Papers which can be handled by 12 | bibtex. 13 | 14 | To use: Export from Papers, then feed into this script, like: 15 | 16 | papers2bib exported.bib > converted.bib 17 | 18 | Note: the output code is EUC at this moment. Import code is assumed 19 | to be UTF-8. 20 | 21 | 22 | If keywords file given, the names listed in the file (one per each 23 | line) will be treated as keywords, and quote with brances in any of 24 | journal, title or authors field. 25 | 26 | Options: 27 | 28 | -K KEY_FILE or --key-file KEY_FILE 29 | Specifying keywords file. If not specified, keywords file 30 | will be consulted with the name "BIB_FILE_BASENAME-keywords.txt" 31 | 32 | 33 | * bibdump 34 | 35 | Read bib file and dump. 36 | 37 | Options: 38 | -u, --utf-8 Set both i/o encoding to UTF-8 39 | -e, --euc-jp Set both i/o encoding to EUC-JP 40 | -s, --sjis Set both i/o encoding to Shift_JIS 41 | --output-utf-8 Set output encoding to UTF-8 42 | --output-euc-jp Set output encoding to EUC-JP 43 | --output-sjis Set output encoding to Shift_JIS 44 | -B, --bib-dir DIR Set output dir (currently ./bib) 45 | -f, --force Force to overwrite files 46 | -t, --no-time-stamp don't add bibdump date 47 | -w, --write Write out each entries in each file in the output-dir 48 | -l, --list List bib cite keys 49 | 50 | Usage: 51 | 52 | To write out per-bib entry file from BIBFILE.bib (in EUC-JP): 53 | bibdump -w -e BIBFILE.bib 54 | 55 | To write out per-bib entry file from BIBFILE.bib (in EUC-JP), output in UTF-8 56 | bibdump -w -e --output-utf-8 BIBFILE.bib 57 | 58 | To list the citation key in the file (you must give correct encoding): 59 | bibdump -l BIBFILE.bib 60 | 61 | Note: output filename is same as the citation key with suffix ".bib" 62 | 63 | 64 | * bibmerge 65 | 66 | Merge given bibs onto primary bib file BIB_FILE 67 | 68 | ruby ./bibmerge [options] BIB_File {cite_keys|bib_files}.. 69 | Options: 70 | -u, --utf-8 Set both i/o encoding to UTF-8 71 | -e, --euc-jp Set both i/o encoding to EUC-JP 72 | -s, --sjis Set both i/o encoding to Shift_JIS 73 | --output-utf-8 Set output encoding to UTF-8 74 | --output-euc-jp Set output encoding to EUC-JP 75 | --output-sjis Set output encoding to Shift_JIS 76 | -B, --bib-dir DIR Set bib dir (currently ./bib) 77 | -f, --force Force to overwrite files 78 | -t, --no-time-stamp don't add bibdump date 79 | -a, --add Add to primary if missing 80 | -o, --overwrite Do overwrite if needed 81 | -d, --dump-mismatch Dump mismatched entries 82 | -p, --dep-dump Dump the list of BIB files bibmerge actually found and merged 83 | -G GROUP, --group GROUP add GROUP as part of suffix (using .{GROUP}-bib prior to .bib) 84 | 85 | 86 | * Japanese Name Hacks 87 | 88 | If there is "JapaneseAutorMap:.*" string in a note of the entry in the 89 | Papers applications' paper entries, strings following it is treated as 90 | mapping between English name into Japanese name. For example, 91 | 92 | JapanesAuthorMap: Yamada Taro: _YAMADA_ _TARO_, Yamada Jiro: _YAMADA_ _JIRO_ 93 | 94 | # _NAME_ denotes kanji characters 95 | 96 | Maps english napes into Japanese name, respectively. 97 | -------------------------------------------------------------------------------- /papers_library.rb: -------------------------------------------------------------------------------- 1 | # Bibliography entry handler for Papers 2 | 3 | require 'bib_library' 4 | 5 | class PapersBibEntry < BibEntry 6 | def initialize(lib, tag, citekey, lines) 7 | super(lib, tag, citekey, lines) 8 | @author_map = { } 9 | end 10 | 11 | def add_author_map(e, j) 12 | @author_map[e] = j 13 | end 14 | 15 | def apply_author_map(r) 16 | if @author_map.size != 0 17 | old_r = r 18 | @author_map.each do |e, j| 19 | r.sub!(/#{e}/, j) 20 | end 21 | end 22 | r 23 | end 24 | 25 | def prep 26 | super 27 | @article_type = :none 28 | @lines.each do |line| 29 | if line =~ /\s*([\w-]+)\s*=\s*(.*)$/ 30 | l, r = $1, $2 31 | if l=~/keywords/ 32 | if r =~/specification/ 33 | @article_type = :specification 34 | elsif l=~/keywords/ and r =~/site/ 35 | @article_type = :site 36 | end 37 | end 38 | end 39 | end 40 | papers2bibtex 41 | end 42 | 43 | def out(o) 44 | o.puts "% " + "-"*(76 - @citekey.size) + " #{@citekey}" 45 | o.puts "" 46 | l, r = "", "" 47 | 48 | # preparation passes.. 49 | @lines.each do |line| 50 | if line =~ /\s*([\w-]+)\s*=\s*(.*)$/ 51 | l, r = $1, $2 52 | pn = l.gsub(/\-/,"_").downcase 53 | begin 54 | l, r = eval("do_rec_#{pn}_prep(l, r)") 55 | rescue NoMethodError 56 | # can't find processing lib 57 | end 58 | end 59 | end 60 | 61 | # output passes. 62 | @lines.each do |line| 63 | if line =~ /\s*([\w-]+)\s*=\s*(.*)$/ 64 | l, r = $1, $2 65 | pn = l.gsub(/\-/,"_").downcase 66 | begin 67 | l, r = eval("do_rec_#{pn}(l, r)") 68 | rescue NoMethodError 69 | # can't find processing lib 70 | end 71 | begin 72 | o.puts " #{l} = #{r}" 73 | rescue Encoding::UndefinedConversionError 74 | STDERR.puts "!!! Can't Convert: #{l} = #{r}" 75 | end 76 | else 77 | o.puts line 78 | end 79 | end 80 | o.puts "" 81 | end 82 | 83 | def papers2bibtex 84 | begin 85 | eval("do_#{tag}") 86 | rescue 87 | # can't find pre-processing func for a tag. 88 | end 89 | end 90 | 91 | 92 | ## 93 | ## Per record processing 94 | ## 95 | 96 | # If there are entries contain http://, quote it. 97 | # If there are entries contain JapanesAuthorMaps, use it as author name mapping 98 | def do_rec_note_prep(l, r) 99 | if r =~ /JapaneseAuthorsMap:\s*(.*)\},/ 100 | mapstr = $1 101 | maplist = mapstr.split(/,\s*/) 102 | maplist.each do |a| 103 | e, j = a.split(/:\s*/) 104 | add_author_map(e, j) 105 | f, l = e.split(/\s+/) 106 | add_author_map("#{l}, #{f}", j) 107 | end 108 | end 109 | [l, r] 110 | end 111 | 112 | # Paper's note appear sometimes as "annote" instead of "note". Map this. 113 | def do_rec_annote_prep(l, r) 114 | do_rec_note_prep(l, r) 115 | [l, r] 116 | end 117 | 118 | # 119 | 120 | def do_rec_note(l, r) 121 | r.gsub!(/{(http:\/\/\S+)(.*)}/, '{\\url{\1}\2}') 122 | r.gsub!(/JapaneseAuthorsMap:\s*(.*)\},/, "},") 123 | [l, r] 124 | end 125 | 126 | # Paper's note appear sometimes as "annote" instead of "note". Map this. 127 | def do_rec_annote(l, r) 128 | l.sub!(/annote/, "note") 129 | do_rec_note(l, r) 130 | end 131 | 132 | def do_rec_uri(l, r) 133 | [l, r] 134 | end 135 | 136 | def do_rec_url(l, r) 137 | r.gsub!(/{(.*)}/, '{\\url\&}') 138 | [l, r] 139 | end 140 | 141 | def do_rec_title(l, r) 142 | r.gsub!(/\{.*\}/, '{\&}') # always quote it 143 | [l, r] 144 | end 145 | 146 | def do_rec_author(l, r) 147 | r.gsub!(@lib.keywords_re, '{\&}') 148 | r = apply_author_map(r) 149 | [l, r] 150 | end 151 | 152 | def do_rec_journal(l, r) 153 | r.gsub!(@lib.keywords_re, '{\&}') 154 | [l, r] 155 | end 156 | 157 | ## * article 158 | ## 159 | ## - Specification from LaTeX Companion: 160 | ## 161 | ## article An article from a journal or magazine. 162 | ## 163 | ## Required: author, title, journal, year. 164 | ## Optional: volume, number, pages, month, note 165 | 166 | def do_article 167 | # if it's a specification, force to @manual 168 | # if it's a site description, force to @misc 169 | # I couldn't determin condition whether output will be @manual or @article. 170 | if @article_type == :specification 171 | @lines.each {|l| l.sub!(/@article{/, "@manual{") } 172 | do_manual 173 | elsif @article_type == :site 174 | @lines.each {|l| l.sub!(/@article{/, "@misc{") } 175 | do_misc 176 | end 177 | end 178 | 179 | ## * manual 180 | def do_manual 181 | end 182 | 183 | ## * misc 184 | def do_misc 185 | end 186 | 187 | ## * inproceedings 188 | ## 189 | ## - Specification from LaTeX Companion: 190 | ## 191 | ## inproceedings An article in a conference proceedings. 192 | ## 193 | ## Required: author, title, booktitle, year. 194 | ## Optional: editor, volume or number, series, pages, address, month, 195 | ## organization, publisher, note. 196 | ## 197 | ## 198 | ## @inproceedings should use booktitle 199 | ## 200 | ## convert: journal -> booktitle 201 | 202 | def do_inproceedings 203 | @lines.each {|l| l.sub!(/journal =/, "booktitle =") } 204 | end 205 | 206 | # OTHERs, including but not limited to: 207 | # manual, masterthesis, misc, phdthesis, techreport 208 | # book, inbook 209 | 210 | end 211 | 212 | class PapersBibLibrary < BibLibrary 213 | def new_bib(tag, citekey, lines) 214 | PapersBibEntry.new(self, tag, citekey, lines) 215 | end 216 | 217 | def postread 218 | super 219 | end 220 | 221 | end 222 | -------------------------------------------------------------------------------- /bib_library.rb: -------------------------------------------------------------------------------- 1 | # Bibliography entry handler 2 | 3 | require 'date' 4 | require 'digest/md5' 5 | 6 | class BibEntry < Array 7 | attr_reader :tag, :citekey, :lines, :bib_hash, :title, :author, :authors 8 | attr_reader :date_added, :date_processed, :date_modified 9 | 10 | def initialize(lib, tag, citekey, lines) 11 | @lib = lib 12 | @tag = tag 13 | @citekey = citekey 14 | @lines = lines 15 | @date_added = 0 16 | @date_modified = 0 17 | @date_processed = 0 18 | @bib_hash = 0 19 | @title = "" 20 | @author = "" 21 | end 22 | 23 | def prep 24 | # create digest of the object, ignoring date related fields 25 | @bib_hash = Digest::MD5.new 26 | 27 | # Parsing several dates and some important fields 28 | @lines ||= [ ] 29 | @lines.each do |line| 30 | if line =~ /\s*booktitle\s*=\s*{(.*)},?\s*$/ 31 | @booktitle = $1 32 | @booktitle.gsub!(/\{*/, '') 33 | @booktitle.gsub!(/\},/, '') 34 | elsif line =~ /\s*title\s*=\s*{(.*)},?\s*$/ 35 | @title = $1 36 | @title.gsub!(/\{/, '') 37 | @title.gsub!(/\}/, '') 38 | elsif line =~ /\s*author\s*=\s*{(.*)},?\s*$/ 39 | @author = $1 40 | @author.gsub!(/\{/, '') 41 | @author.gsub!(/\}/, '') 42 | @authors = @author.split(/ and /) 43 | elsif line =~ /\s*date-(\S+)\s*=\s*{\s*(.+)\s*}/ 44 | k, d = $1, DateTime.parse($2) 45 | if k == "added" 46 | @date_added = d 47 | elsif k == "modified" 48 | @date_modified = d 49 | elsif k == "written-via-bibdump" 50 | @date_processed = d 51 | end 52 | else 53 | @bib_hash.update(line) 54 | end 55 | end 56 | end 57 | 58 | def out(o) 59 | o.puts "% " + "-"*(76 - @citekey.size) + " #{@citekey}" 60 | o.puts "" 61 | @lines.each {|l| o.puts l} 62 | o.puts "" 63 | end 64 | 65 | def to_s 66 | @lines.join("") 67 | end 68 | 69 | def write_opt(opts) 70 | "w" + ( opts.has_key?(:output_encoding) ? ":"+opts[:output_encoding] : "") 71 | end 72 | 73 | def write_to_file(file, opts, marker_lines = []) 74 | STDERR.puts "Writing citation <#{@citekey}> to #{file}" 75 | open(file, write_opt(opts)) do |f| 76 | @lines.each {|l| f.puts l unless l =~ /^}\s*$/ } if @lines != nil 77 | marker_lines.each {|l| f.puts l } 78 | f.puts "}" 79 | end 80 | end 81 | 82 | # equality means: contents other than date fields match exactly 83 | def ==(x) 84 | if @bib_hash == x.bib_hash 85 | if @date_added == x.date_added and 86 | @date_modified == x.date_modified 87 | return true 88 | else 89 | end 90 | else 91 | STDERR.puts "<#{citekey}> Bib_Hash Mismatch: #{bib_hash} #{x.bib_hash}" 92 | return false 93 | end 94 | 95 | false 96 | end 97 | 98 | # Minimum sanity check here. 99 | # if it is not equal, there must be some date mismatch.. 100 | # returns if paper's entry is newer then locally edited bibs, 101 | # added > modified, and date added should be same. 102 | def inconsistent?(x) 103 | if @date_added > @date_modified or 104 | x.date_added > x.date_modified 105 | # @date_added != x.date_added or 106 | # @date_modified > x.date_modified 107 | 108 | return true 109 | end 110 | false 111 | end 112 | 113 | end 114 | 115 | class BibLibrary < Hash 116 | attr_reader :keywords_re 117 | 118 | def read_opt(opts) 119 | "r" + ( opts.has_key?(:encoding) ? ":"+opts[:encoding] : "") 120 | end 121 | 122 | def initialize(file = nil, opts) 123 | @opts = opts 124 | ropt = read_opt(opts) 125 | if file != nil 126 | kfile = opts[:key_file] 127 | if kfile == "" 128 | kfile = File.basename(file, ".bib")+"-keywords.txt" 129 | end 130 | if File.file?(kfile) 131 | read_key(kfile, ropt) 132 | end 133 | read(file, ropt) 134 | end 135 | end 136 | 137 | def read_key(file, encoding = "r") 138 | @keywords = Array.new 139 | open(file, encoding) do |f| 140 | f.each do |l| 141 | l.chomp! 142 | unless l =~ /^\s*(#|%)/ || l =~ /^\s*$/ 143 | @keywords.push(l) 144 | end 145 | end 146 | end 147 | @keywords_re = Regexp.new("("+@keywords.join("|")+")") 148 | end 149 | 150 | def read(file, read_opt) 151 | lines = nil 152 | inside = false 153 | tag = "?" 154 | citekey = "?" 155 | 156 | begin 157 | open(bibpathnormalize(file), read_opt) do |f| 158 | f.each do |l| 159 | if l =~ /^%/ 160 | # comments 161 | elsif l =~ /^\s+$/ 162 | # blank lines 163 | else 164 | if l =~ /^\@([A-Za-z]+)\{([^,]+),$/ 165 | tag, citekey = $1, $2 166 | lines = [ ] 167 | inside = true 168 | end 169 | 170 | lines.push(l) if inside 171 | 172 | if l =~ /^\}$/ 173 | self[citekey] = new_bib(tag, citekey, lines) 174 | lines = nil 175 | inside = false 176 | end 177 | end 178 | end 179 | end 180 | rescue ArgumentError 181 | STDERR.puts "invalid byte sequence in #{file}" 182 | exit 1 183 | end 184 | postread if self.size != 0 185 | end 186 | 187 | def new_bib(tag, citekey, lines) 188 | BibEntry.new(self,tag, citekey, lines) 189 | end 190 | 191 | def postread 192 | prep 193 | end 194 | 195 | def prep 196 | each {|k, v| v.prep } 197 | end 198 | 199 | def out(o) 200 | keys.sort.each {|k| self[k].out(o) } 201 | end 202 | 203 | def to_s 204 | self.keys.sort.each{|k| self[k].to_s }.join("\n") 205 | end 206 | 207 | 208 | # Merge entire BibLibrary onto self 209 | def merge(xlib) 210 | xlib.each_value { |v| merge_one(v) } 211 | end 212 | 213 | # Merge one entry onto self 214 | def merge_one(n) 215 | 216 | # if the primary library (self) does not contain the entry, don't merge 217 | # unless --merge option given 218 | citekey = n.citekey 219 | unless self.has_key?(citekey) and @opts[:add] == false 220 | STDERR.puts "<#{n.citekey}> do not exist in primary bib file. use --add to force adding them" 221 | else 222 | old = self[citekey] 223 | if old.bib_hash == n.bib_hash # if contents is same.. don't replace 224 | STDERR.puts "Skiping: <#{citekey}>" 225 | elsif old.inconsistent?(n) 226 | STDERR.puts "Inconsistent: <#{citekey}>" 227 | if @opts[:dump_mismatch] 228 | STDERR.puts "----- #{old.bib_hash}" 229 | STDERR.puts old.to_s 230 | STDERR.puts "----- #{n.bib_hash}" 231 | STDERR.puts n.to_s 232 | end 233 | else 234 | if @opts[:dump_mismatch] 235 | STDERR.puts "----- #{old.bib_hash}" 236 | STDERR.puts old.to_s 237 | STDERR.puts "----- #{n.bib_hash}" 238 | STDERR.puts n.to_s 239 | end 240 | 241 | if @opts[:overwrite] # dont overwrite unless specified 242 | STDERR.puts "Replacing: <#{citekey}>" 243 | self[citekey] = n 244 | else 245 | STDERR.puts "*NOT* Replacing: <#{citekey}> -- use --overwrite to replace" 246 | end 247 | end 248 | end 249 | end 250 | 251 | def bibpathnormalize(c) 252 | c.gsub(/:/, "_") 253 | end 254 | 255 | def bibdirpath(s) 256 | @opts[:bib_dir] + "/" + s + ".bib" 257 | end 258 | 259 | def mkbibpath_1(c, suffix, wflag = false) 260 | return nil if c == nil 261 | fn = @opts[:bib_dir] + "/" + c + "." + suffix 262 | if wflag == nil 263 | return fn if File.file?(fn) 264 | else # only check for existing of directory 265 | return fn if File.directory?(File.dirname(fn)) 266 | end 267 | 268 | return nil 269 | end 270 | 271 | 272 | def mkbibpath(c, wflag = false) 273 | s = bibpathnormalize(c) 274 | r = nil 275 | if @opts[:group] != nil 276 | p = mkbibpath_1(s, "#{@opts[:group]}-bib", wflag) 277 | if File.file?(p) 278 | STDERR.puts "Found: group <#{@opts[:group]}> variant for #{s}" 279 | r = p 280 | end 281 | end 282 | r = mkbibpath_1(s, "bib", wflag) if r == nil 283 | return r 284 | end 285 | 286 | end 287 | --------------------------------------------------------------------------------