├── test ├── regression │ ├── recurse.TAGS.expect │ ├── recurse.tags.expect │ ├── code01.txt.TAGS.expect │ ├── code01.txt.tags.expect │ ├── code02.rb.TAGS.expect │ ├── code02.rb.tags.expect │ ├── fileops.rb.TAGS.expect │ ├── fileops.rb.tags.expect │ ├── report.rb.TAGS.expect │ └── report.rb.tags.expect ├── data │ ├── code01.txt │ ├── code02.rb │ ├── report.rb │ └── fileops.rb └── runner.rb ├── Rakefile ├── lib ├── rtags.rb └── rtags │ └── version.rb ├── Gemfile ├── README ├── .gitignore ├── doc └── html │ └── index.html ├── scripts └── release.sh ├── TODO ├── rtags.gemspec ├── RELEASENOTES ├── LICENSE.txt ├── install.sh └── bin └── rtags /test/regression/recurse.TAGS.expect: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/regression/recurse.tags.expect: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /lib/rtags.rb: -------------------------------------------------------------------------------- 1 | require "rtags/version" 2 | -------------------------------------------------------------------------------- /lib/rtags/version.rb: -------------------------------------------------------------------------------- 1 | module Rtags 2 | VERSION = '0.99.2' 3 | end 4 | 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rtags.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | rtags: like ctags, but for ruby. 2 | 3 | For more information, see ./bin/rtags 4 | 5 | Originally by Keiju ISHITSUKA 6 | Was maintained by Pjotr Prins 7 | Currently maintained by no-one 8 | 9 | -------------------------------------------------------------------------------- /test/data/code01.txt: -------------------------------------------------------------------------------- 1 | module That 2 | class Sothere 3 | def mymy 4 | end 5 | 6 | end 7 | class Ritz 8 | MY_CONST = 4 9 | def mymy 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/regression/code01.txt.TAGS.expect: -------------------------------------------------------------------------------- 1 | 2 | ./data/code01.txt,187 3 | module That::That1,0 4 | class Sothere::That::Sothere2,20 5 | def mymy::That::Sothere#mymy3,58 6 | class Ritz::That::Ritz7,92 7 | def mymy::That::Ritz#mymy9,140 8 | -------------------------------------------------------------------------------- /test/regression/code01.txt.tags.expect: -------------------------------------------------------------------------------- 1 | 2 | ./data/code01.txt,187 3 | module That::That1,0 4 | class Sothere::That::Sothere2,20 5 | def mymy::That::Sothere#mymy3,58 6 | class Ritz::That::Ritz7,92 7 | def mymy::That::Ritz#mymy9,140 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | test/regression/*.tags 18 | test/regression/*.TAGS 19 | tmp 20 | -------------------------------------------------------------------------------- /test/data/code02.rb: -------------------------------------------------------------------------------- 1 | # Multi line accessor fails in rtags < 0.93 2 | # 3 | class Hit 4 | def initialize 5 | @hsps = [] 6 | end 7 | attr_reader :hsps 8 | attr_accessor :query_id, :query_def, :query_len, 9 | :num, :hit_id, :len, :definition, :accession 10 | 11 | def each 12 | @hsps.each do |x| 13 | yield x 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /doc/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | rtags.rubyforge.org 6 | 7 | 8 | Click [ here ] if your 9 | browser does not automatically jump. 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | progname=rtags 4 | tmpdir=/tmp 5 | version=`cat bin/VERSION` 6 | pkgname=$progname-$version 7 | pkgdir=$tmpdir/$pkgname 8 | cwd=`pwd` 9 | 10 | if [ ! -d test ]; then echo "Run from base directory!" ; exit ; fi 11 | 12 | echo -n "Release " 13 | echo $version 14 | cd test 15 | ruby runner.rb 16 | cd .. 17 | echo "hit enter to continue" 18 | read 19 | 20 | rm *.gem 21 | rm *.tgz 22 | 23 | # Make the gem 24 | ruby ./scripts/rtags.gemspec 25 | 26 | # Make the tgz 27 | mkdir $pkgdir 28 | cp -va bin test doc install.sh README TODO LICENSE.txt RELEASENOTES $pkgdir 29 | cd $tmpdir 30 | tar cvzf $pkgname.tgz --exclude *.svn* $pkgname/* 31 | cd $cwd 32 | cp $tmpdir/$pkgname.tgz . 33 | -------------------------------------------------------------------------------- /test/regression/code02.rb.TAGS.expect: -------------------------------------------------------------------------------- 1 | 2 | ./data/code02.rb,764 3 | class Hit::Hit3,53 4 | def initialize::Hit#Hit.new4,85 5 | attr_reader :hsps::Hit#hsps7,127 6 | attr_accessor :query_id, :query_def, :query_len,::Hit#query_id8,153 7 | attr_accessor :query_id, :query_def, :query_len,::Hit#query_def8,153 8 | attr_accessor :query_id, :query_def, :query_len,::Hit#query_len8,153 9 | attr_accessor :query_id, :query_def, :query_len,::Hit#num8,153 10 | attr_accessor :query_id, :query_def, :query_len,::Hit#hit_id8,153 11 | attr_accessor :query_id, :query_def, :query_len,::Hit#len8,153 12 | attr_accessor :query_id, :query_def, :query_len,::Hit#definition8,153 13 | attr_accessor :query_id, :query_def, :query_len,::Hit#accession8,153 14 | def each::Hit#each11,274 15 | -------------------------------------------------------------------------------- /test/regression/code02.rb.tags.expect: -------------------------------------------------------------------------------- 1 | 2 | ./data/code02.rb,764 3 | class Hit::Hit3,53 4 | def initialize::Hit#Hit.new4,85 5 | attr_reader :hsps::Hit#hsps7,127 6 | attr_accessor :query_id, :query_def, :query_len,::Hit#query_id8,153 7 | attr_accessor :query_id, :query_def, :query_len,::Hit#query_def8,153 8 | attr_accessor :query_id, :query_def, :query_len,::Hit#query_len8,153 9 | attr_accessor :query_id, :query_def, :query_len,::Hit#num8,153 10 | attr_accessor :query_id, :query_def, :query_len,::Hit#hit_id8,153 11 | attr_accessor :query_id, :query_def, :query_len,::Hit#len8,153 12 | attr_accessor :query_id, :query_def, :query_len,::Hit#definition8,153 13 | attr_accessor :query_id, :query_def, :query_len,::Hit#accession8,153 14 | def each::Hit#each11,274 15 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | For version 1.0: 4 | 5 | - Recognise 'alias :name' tokens 6 | - include the argument list and an end of line $ pattern (at least the 7 | first opening parenthesis should be included in the pattern) 8 | - indicate whether the entry is a c(lass), a m(odule), or a f (method) 9 | like ctags 10 | 11 | Later: 12 | 13 | - Add switch for not following symlinks 14 | - Fix class Foo::Bar to show as such in the TAGS file 15 | - Find modules with double colon notation like "BIO::MODULE::IO" 16 | - Cache class names so classes can be found that lack an initialize 17 | method 18 | - Provide - command line switch, allowing for 19 | find . -name '*.rb' -print | rtags - 20 | 21 | MUSINGS 22 | 23 | Apart from adding the - switch at some point in the future it would 24 | be neat to have smarter tags. For one, if a class has not initialize 25 | method we would like to jump to the class definition on 26 | Classname.new. If we were to store the tags we find in memory 27 | (instead of just writing them out, as is the current behaviour) it 28 | would be trivial to implement. 29 | 30 | Also some tokens (alias) and multilines are not handled correctly. 31 | 32 | Try: 33 | 34 | cd ./test ruby ../bin/rtags -R 35 | 36 | and you get 37 | 38 | Warn: parse error in <./data/report.rb> in line 170, pos 4 39 | 40 | which is an unrecognised alias. 41 | -------------------------------------------------------------------------------- /test/runner.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | Dir.chdir(File.dirname(__FILE__)) 4 | 5 | TERMINAL_COLUMNS = `stty size`.split(" ").last.to_i 6 | 7 | require 'test/unit' 8 | 9 | # Re-initialise regression files (copy current to expect) 10 | if ARGV.shift=='-i' 11 | Dir.chdir('regression') 12 | print `rm -v *.expect` 13 | print `rename 's/tags$/tags.expect/g' *.tags` 14 | print `rename 's/TAGS$/TAGS.expect/g' *.TAGS` 15 | exit 16 | end 17 | 18 | # run regression tests 19 | 20 | def regression_test basedir,file,switches='' 21 | rtags = 'ruby '+basedir+'/bin/rtags'+' '+switches 22 | datafn = basedir+'/test/regression/'+File.basename(file) 23 | cmd = "#{rtags} #{file}" 24 | puts 25 | puts 26 | puts "=" * TERMINAL_COLUMNS 27 | puts "Difference between (emacs) #{datafn}.TAGS and #{datafn}.TAGS.expect:" 28 | print `#{cmd} --quiet -f #{datafn}.TAGS` 29 | print `colordiff -u --minimal #{datafn}.TAGS.expect #{datafn}.TAGS` 30 | puts 31 | puts "=" * TERMINAL_COLUMNS 32 | puts "Difference between (vi) #{datafn}.tags and #{datafn}.tags.expect:" 33 | print `#{cmd} --vi --quiet -f #{datafn}.tags` 34 | print `colordiff -u --minimal #{datafn}.tags.expect #{datafn}.tags` 35 | puts 36 | puts 37 | 38 | end 39 | 40 | $stderr.print "Begin regression tests..." 41 | basedir = '..' 42 | files = Dir.glob("data/*") 43 | files.each do | file | 44 | regression_test basedir,file 45 | end 46 | regression_test basedir,'recurse','-R' 47 | $stderr.print "\nFinalised regression tests\n" 48 | -------------------------------------------------------------------------------- /rtags.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | Kernel.load File.expand_path('../lib/rtags/version.rb', __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'bloopletech-rtags' 6 | s.version = Rtags::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.summary = "rtags is a Ruby replacement for ctags - allowing for name navigation in source code using vim, emacs and others" 9 | s.description = "This is the original commit of the rtags source code as written by Keiju ISHITSUKA as part of the irb project. Now irb has moved into the main Ruby source tree rtags has become an independent project" 10 | 11 | s.files = [ 12 | 'RELEASENOTES','TODO','README','LICENSE.txt' 13 | ] 14 | # libraries 15 | s.files = s.files + Dir.glob(File.dirname(__FILE__) + "/../lib/*") 16 | 17 | # tests 18 | # s.files = s.files + Dir.glob(File.dirname(__FILE__) + "/../test/*") 19 | # s.files = s.files + Dir.glob(File.dirname(__FILE__) + "/../test/data/*") 20 | # s.files = s.files + Dir.glob(File.dirname(__FILE__) + "/../test/regression/*") 21 | # s.test_file = File.dirname(__FILE__) + '/../test/runner.rb' 22 | 23 | # binaries 24 | s.files = s.files + Dir.glob(File.dirname(__FILE__) + "/../bin/*") 25 | s.bindir = 'bin' 26 | s.executable = 'rtags' 27 | 28 | s.required_ruby_version = '>= 1.8.1' 29 | s.autorequire = 'irb' 30 | # s.add_dependency( "irb", ">= 0.9" ) 31 | s.author = "Pjotr Prins, Keiju Ishitsuka" 32 | s.email = "pjotr.public02@thebird.nl" 33 | s.rubyforge_project = "rtags" 34 | s.homepage = "http://rtags.rubyforge.org" 35 | s.has_rdoc = false 36 | end 37 | -------------------------------------------------------------------------------- /RELEASENOTES: -------------------------------------------------------------------------------- 1 | rtags.rb - 2 | 3 | Release Version: 0.96 (April 2006) by Pjotr Prins 4 | Improved filtering on repository files (skip _darcs, CVS, .SVN) 5 | Test for hash bang Ruby - if no .rb extension 6 | 7 | Release Version: 0.95 (December 2006) by Pjotr Prins 8 | Added -R/--recurse switch by Chetan Patil 9 | Added regression tests for recursion 10 | 11 | Release Version: 0.94 (December 2006) by Pjotr Prins 12 | Added support for ClassName.new tags - which point to the 13 | Class def initialize methods 14 | 15 | Release Version: 0.93 (August 2006) by Pjotr Prins 16 | Fixed bug where multiple lines were written to tags file - which 17 | broke them for vi 18 | Fixed infinite loop for specific cases 19 | Added -a switch (append mode) 20 | Turned warnings off by default (-w switch) 21 | Included RELEASENOTES in gem 22 | More regression testing (-a, tags, TAGS) 23 | Run regression test before making a release 24 | 25 | Release Version: 0.92 (August 2006) by Pjotr Prins 26 | Added catching of parse errors 27 | Fixed exit on 'alias' token error (`parse_alias': undefined method `name') 28 | Added support for command line arguments with optparse 29 | Added debug switches (--debug, --debug_tokens) 30 | Print to stderr on debug 31 | Added support for alternate tags filename (-f switch) 32 | Added --quiet switch 33 | Added regression files to ./test 34 | 35 | Release Version: 0.91 (July 2006) by Pjotr Prins 36 | First independent public release on RubyForge 37 | Depends on irb 0.9 38 | Wrote install.sh and gem packaging 39 | 40 | Release Version: 0.9 (2002/07/09) by Keiju ISHITSUKA 41 | Released as part of irb source tree 42 | 43 | 44 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | rtags is copyrighted free software. 2 | 3 | You can redistribute it and/or modify it under either the terms of the GPL 4 | (see COPYING.txt file), or the conditions below: 5 | 6 | 1. You may make and give away verbatim copies of the source form of the 7 | software without restriction, provided that you duplicate all of the 8 | original copyright notices and associated disclaimers. 9 | 10 | 2. You may modify your copy of the software in any way, provided that 11 | you do at least ONE of the following: 12 | 13 | a) place your modifications in the Public Domain or otherwise 14 | make them Freely Available, such as by posting said 15 | modifications to Usenet or an equivalent medium, or by allowing 16 | the author to include your modifications in the software. 17 | 18 | b) use the modified software only within your corporation or 19 | organization. 20 | 21 | c) rename any non-standard executables so the names do not conflict 22 | with standard executables, which must also be provided. 23 | 24 | d) make other distribution arrangements with the author. 25 | 26 | 3. You may distribute the software in object code or executable 27 | form, provided that you do at least ONE of the following: 28 | 29 | a) distribute the executables and library files of the software, 30 | together with instructions (in the manual page or equivalent) 31 | on where to get the original distribution. 32 | 33 | b) accompany the distribution with the machine-readable source of 34 | the software. 35 | 36 | c) give non-standard executables non-standard names, with 37 | instructions on where to get the original software distribution. 38 | 39 | d) make other distribution arrangements with the author. 40 | 41 | 4. You may modify and include the part of the software into any other 42 | software (possibly commercial). But some files in the distribution 43 | are not written by the author, so that they are not under this terms. 44 | 45 | 5. The scripts and library files supplied as input to or produced as 46 | output from the software do not automatically fall under the 47 | copyright of the software, but belong to whomever generated them, 48 | and may be sold commercially, and may be aggregated with this 49 | software. 50 | 51 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 52 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 53 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 54 | PURPOSE. 55 | 56 | -------------------------------------------------------------------------------- /test/regression/fileops.rb.TAGS.expect: -------------------------------------------------------------------------------- 1 | 2 | ./data/fileops.rb,2667 3 | module Cfruby::Cfruby3,77 4 | module FileOps::Cfruby::FileOps5,94 5 | class FileOpsError::Cfruby::FileOps::FileOpsError11,260 6 | class FileOpsUnknownProtocolError::Cfruby::FileOps::FileOpsUnknownProtocolError15,383 7 | class FileOpsFileExistError::Cfruby::FileOps::FileOpsFileExistError19,512 8 | class FileOpsOverwriteError::Cfruby::FileOps::FileOpsOverwriteError23,639 9 | class FileOpsWrongFiletypeError::Cfruby::FileOps::FileOpsWrongFiletypeError27,756 10 | class FileOps::Cfruby::FileOps::FileOps33,964 11 | def move::Cfruby::FileOps::FileOps#move40,1306 12 | def copy::Cfruby::FileOps::FileOps#copy48,1658 13 | class FileOps::Cfruby::FileOps::FileOps55,1783 14 | def move::Cfruby::FileOps::FileOps#move62,2123 15 | def copy::Cfruby::FileOps::FileOps#copy111,3886 16 | def copy_single::Cfruby::FileOps::FileOps#copy_single148,5277 17 | class FileOps::Cfruby::FileOps::FileOps204,6909 18 | def move::Cfruby::FileOps::FileOps#move211,7176 19 | def copy::Cfruby::FileOps::FileOps#copy219,7408 20 | class FileOps::Cfruby::FileOps::FileOps243,7917 21 | def move::Cfruby::FileOps::FileOps#move245,7952 22 | def copy::Cfruby::FileOps::FileOps#copy252,8170 23 | def FileOps.get_protocol::Cfruby::FileOps.get_protocol275,8822 24 | def FileOps.move::Cfruby::FileOps.move304,9566 25 | def FileOps.copy::Cfruby::FileOps.copy312,9921 26 | def FileOps.touch::Cfruby::FileOps.touch319,10193 27 | def FileOps.unlink::Cfruby::FileOps.unlink338,10689 28 | def FileOps.mkdir::Cfruby::FileOps.mkdir350,11109 29 | def FileOps.rmdir::Cfruby::FileOps.rmdir383,11933 30 | def FileOps.link::Cfruby::FileOps.link419,13026 31 | def FileOps.create::Cfruby::FileOps.create450,14170 32 | def FileOps.flock::Cfruby::FileOps.flock488,15373 33 | def FileOps.set_backup::Cfruby::FileOps.set_backup514,16027 34 | def FileOps.backup::Cfruby::FileOps.backup523,16472 35 | def FileOps.delete_nonalpha::Cfruby::FileOps.delete_nonalpha595,18830 36 | def FileOps.delete::Cfruby::FileOps.delete610,19380 37 | def FileOps.chown_mod::Cfruby::FileOps.chown_mod634,20196 38 | def FileOps.disable::Cfruby::FileOps.disable661,21049 39 | def FileOps.chown::Cfruby::FileOps.chown676,21458 40 | def FileOps.chmod::Cfruby::FileOps.chmod703,22413 41 | module FileOps::Cfruby::FileOps::FileOps738,23612 42 | def SymlinkHandler.stat::Cfruby::FileOps::FileOps::SymlinkHandler.stat742,23774 43 | def SymlinkHandler.unlink::Cfruby::FileOps::FileOps::SymlinkHandler.unlink757,24184 44 | def SymlinkHandler.broken?::Cfruby::FileOps::FileOps::SymlinkHandler.broken?770,24482 45 | def SymlinkHandler.points_to?::Cfruby::FileOps::FileOps::SymlinkHandler.points_to?801,25367 46 | def FileOps.strip_protocol::Cfruby::FileOps.strip_protocol821,25789 47 | -------------------------------------------------------------------------------- /test/regression/fileops.rb.tags.expect: -------------------------------------------------------------------------------- 1 | 2 | ./data/fileops.rb,2667 3 | module Cfruby::Cfruby3,77 4 | module FileOps::Cfruby::FileOps5,94 5 | class FileOpsError::Cfruby::FileOps::FileOpsError11,260 6 | class FileOpsUnknownProtocolError::Cfruby::FileOps::FileOpsUnknownProtocolError15,383 7 | class FileOpsFileExistError::Cfruby::FileOps::FileOpsFileExistError19,512 8 | class FileOpsOverwriteError::Cfruby::FileOps::FileOpsOverwriteError23,639 9 | class FileOpsWrongFiletypeError::Cfruby::FileOps::FileOpsWrongFiletypeError27,756 10 | class FileOps::Cfruby::FileOps::FileOps33,964 11 | def move::Cfruby::FileOps::FileOps#move40,1306 12 | def copy::Cfruby::FileOps::FileOps#copy48,1658 13 | class FileOps::Cfruby::FileOps::FileOps55,1783 14 | def move::Cfruby::FileOps::FileOps#move62,2123 15 | def copy::Cfruby::FileOps::FileOps#copy111,3886 16 | def copy_single::Cfruby::FileOps::FileOps#copy_single148,5277 17 | class FileOps::Cfruby::FileOps::FileOps204,6909 18 | def move::Cfruby::FileOps::FileOps#move211,7176 19 | def copy::Cfruby::FileOps::FileOps#copy219,7408 20 | class FileOps::Cfruby::FileOps::FileOps243,7917 21 | def move::Cfruby::FileOps::FileOps#move245,7952 22 | def copy::Cfruby::FileOps::FileOps#copy252,8170 23 | def FileOps.get_protocol::Cfruby::FileOps.get_protocol275,8822 24 | def FileOps.move::Cfruby::FileOps.move304,9566 25 | def FileOps.copy::Cfruby::FileOps.copy312,9921 26 | def FileOps.touch::Cfruby::FileOps.touch319,10193 27 | def FileOps.unlink::Cfruby::FileOps.unlink338,10689 28 | def FileOps.mkdir::Cfruby::FileOps.mkdir350,11109 29 | def FileOps.rmdir::Cfruby::FileOps.rmdir383,11933 30 | def FileOps.link::Cfruby::FileOps.link419,13026 31 | def FileOps.create::Cfruby::FileOps.create450,14170 32 | def FileOps.flock::Cfruby::FileOps.flock488,15373 33 | def FileOps.set_backup::Cfruby::FileOps.set_backup514,16027 34 | def FileOps.backup::Cfruby::FileOps.backup523,16472 35 | def FileOps.delete_nonalpha::Cfruby::FileOps.delete_nonalpha595,18830 36 | def FileOps.delete::Cfruby::FileOps.delete610,19380 37 | def FileOps.chown_mod::Cfruby::FileOps.chown_mod634,20196 38 | def FileOps.disable::Cfruby::FileOps.disable661,21049 39 | def FileOps.chown::Cfruby::FileOps.chown676,21458 40 | def FileOps.chmod::Cfruby::FileOps.chmod703,22413 41 | module FileOps::Cfruby::FileOps::FileOps738,23612 42 | def SymlinkHandler.stat::Cfruby::FileOps::FileOps::SymlinkHandler.stat742,23774 43 | def SymlinkHandler.unlink::Cfruby::FileOps::FileOps::SymlinkHandler.unlink757,24184 44 | def SymlinkHandler.broken?::Cfruby::FileOps::FileOps::SymlinkHandler.broken?770,24482 45 | def SymlinkHandler.points_to?::Cfruby::FileOps::FileOps::SymlinkHandler.points_to?801,25367 46 | def FileOps.strip_protocol::Cfruby::FileOps.strip_protocol821,25789 47 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Installation script for rtags (works on Unix) - requires ruby and 4 | # irb. 5 | # 6 | # Usage: install.sh [prefix] 7 | # 8 | 9 | function fail() 10 | { 11 | echo $1 12 | echo "INSTALLATION FAILED!" 13 | exit 1 14 | } 15 | 16 | ############################################################################### 17 | # 18 | # CONFIG SECTION - EDIT TO SUIT YOUR SITE 19 | # 20 | ############################################################################### 21 | # 22 | # prefix : everything will be installed under this 23 | # 24 | prefix="$1" 25 | if [ "$prefix" == "" ] ; then prefix='/usr/local' ; fi 26 | 27 | # 28 | # sudo : set to 'sudo' if you will need sudo to install into prefix 29 | # 30 | sudo="" 31 | #sudo="sudo" 32 | # 33 | # libdir : libs will installed here 34 | # 35 | libdir="${prefix}/lib" 36 | # 37 | # bindir : executables and scripts will installed here 38 | # 39 | bindir="${prefix}/bin" 40 | # 41 | # full path of dir containing packages to be installed 42 | # 43 | packagedir="`pwd`/packages" 44 | # 45 | # full path of dir where packages will be built 46 | # 47 | builddir="`pwd`/build" 48 | 49 | ############################################################################### 50 | # 51 | # END OF CONFIG SECTION - do not edit below 52 | # 53 | ############################################################################### 54 | 55 | # listing of all packages to install into $prefix 56 | # 57 | # info 58 | # 59 | printf "\n$div\n" 60 | printf "CONFIG\n" 61 | printf -- "$line\n" 62 | printf "prefix <$prefix>\n" 63 | printf "libdir <$libdir>\n" 64 | printf "bindir <$bindir>\n" 65 | # printf "packagedir <$packagedir>\n" 66 | # printf "builddir <$builddir>\n" 67 | printf -- "$line\n" 68 | # 69 | # important env settings for proper compilation 70 | # 71 | export PATH="${bindir}:$PATH" 72 | export LD_RUN_PATH="${libdir}" 73 | export LD_LIBRARY_PATH="${libdir}" 74 | # 75 | # important aliases for proper complilation and installation 76 | # 77 | make="env LD_RUN_PATH=${libdir} LD_LIBRARY_PATH=${libdir} make" 78 | ruby=`which ruby` 79 | irb=`which irb` 80 | rtags='./bin/rtags' 81 | # 82 | # Test dependencies 83 | # 84 | 85 | if [ -z $ruby ]; then 86 | fail "The Ruby interpreter can not be found. Please install Ruby first." 87 | fi 88 | 89 | if [ -z $irb ]; then 90 | fail "The irb (interactive Ruby) interpreter can not be found. Please install irb first." 91 | fi 92 | 93 | if [ ! -f $rtags ] ; then 94 | fail "Run this script from the root of the unpacked rtags package" 95 | fi 96 | 97 | target=$bindir/rtags 98 | if [ -f $target ] ; then 99 | fail "$target already exists, please remove first!" 100 | fi 101 | 102 | cp $rtags $target 103 | chmod a+x $target 104 | echo "rtags installed as $target. Try $target --help" 105 | 106 | exit 0 107 | 108 | 109 | -------------------------------------------------------------------------------- /test/regression/report.rb.TAGS.expect: -------------------------------------------------------------------------------- 1 | 2 | ./data/report.rb,3763 3 | module Bio::Bio4,126 4 | class HMMER::Bio::HMMER6,138 5 | def self.reports::Bio::HMMER.reports21,433 6 | class Report::Bio::HMMER::Report63,1436 7 | attr_reader :program::Bio::HMMER::Report#program71,1667 8 | attr_reader :parameter::Bio::HMMER::Report#parameter75,1785 9 | attr_reader :query_info::Bio::HMMER::Report#query_info79,1932 10 | attr_reader :hits::Bio::HMMER::Report#hits82,1968 11 | attr_reader :hsps::Bio::HMMER::Report#hsps88,2193 12 | attr_reader :histogram::Bio::HMMER::Report#histogram91,2247 13 | attr_reader :statistical_detail::Bio::HMMER::Report#statistical_detail94,2375 14 | attr_reader :total_seq_searched::Bio::HMMER::Report#total_seq_searched97,2447 15 | attr_reader :whole_seq_top_hits::Bio::HMMER::Report#whole_seq_top_hits100,2585 16 | attr_reader :domain_top_hits::Bio::HMMER::Report#domain_top_hits103,2723 17 | def initialize::Bio::HMMER::Report#Bio::HMMER::Report.new115,3074 18 | def each::Bio::HMMER::Report#each165,4637 19 | def get_subdata::Bio::HMMER#get_subdata174,4769 20 | def parse_header_data::Bio::HMMER#parse_header_data219,6368 21 | def parse_query_info::Bio::HMMER#parse_query_info241,6910 22 | def parse_hit_data::Bio::HMMER#parse_hit_data256,7224 23 | def parse_hsp_data::Bio::HMMER#parse_hsp_data269,7526 24 | def parse_stat_data::Bio::HMMER#parse_stat_data282,7862 25 | class Hit::Bio::HMMER::Hit324,9039 26 | attr_reader :hsps::Bio::HMMER::Hit#hsps327,9110 27 | attr_reader :accession::Bio::HMMER::Hit#accession330,9144 28 | alias target_id::Bio::HMMER::Hit#target_id331,9173 29 | alias hit_id::Bio::HMMER::Hit#hit_id332,9206 30 | alias entry_id::Bio::HMMER::Hit#entry_id333,9239 31 | attr_reader :description::Bio::HMMER::Hit#description336,9288 32 | alias definition::Bio::HMMER::Hit#definition337,9319 33 | attr_reader :score::Bio::HMMER::Hit#score340,9400 34 | alias bit_score::Bio::HMMER::Hit#bit_score341,9425 35 | attr_reader :evalue::Bio::HMMER::Hit#evalue344,9471 36 | attr_reader :num::Bio::HMMER::Hit#num347,9524 37 | def initialize::Bio::HMMER::Hit#Bio::HMMER::Hit.new350,9579 38 | def each::Bio::HMMER::Hit#each360,9904 39 | def target_def::Bio::HMMER#target_def369,10060 40 | def append_hsp::Bio::HMMER#append_hsp378,10272 41 | class Hsp::Bio::Hsp386,10401 42 | attr_reader :accession::Bio::Hsp#accession389,10433 43 | alias target_id::Bio::Hsp#target_id390,10462 44 | attr_reader :domain::Bio::Hsp#domain393,10503 45 | attr_reader :seq_f::Bio::Hsp#seq_f396,10544 46 | attr_reader :seq_t::Bio::Hsp#seq_t399,10578 47 | attr_reader :seq_ft::Bio::Hsp#seq_ft402,10612 48 | attr_reader :hmm_f::Bio::Hsp#hmm_f405,10647 49 | attr_reader :hmm_t::Bio::Hsp#hmm_t408,10681 50 | attr_reader :hmm_ft::Bio::Hsp#hmm_ft411,10715 51 | attr_reader :score::Bio::Hsp#score414,10756 52 | alias bit_score::Bio::Hsp#bit_score415,10781 53 | attr_reader :evalue::Bio::Hsp#evalue418,10826 54 | attr_reader :midline::Bio::Hsp#midline421,10885 55 | attr_reader :hmmseq::Bio::Hsp#hmmseq424,10927 56 | attr_reader :flatseq::Bio::Hsp#flatseq427,10962 57 | attr_reader :query_frame::Bio::Hsp#query_frame430,10998 58 | attr_reader :target_frame::Bio::Hsp#target_frame433,11038 59 | attr_reader :csline::Bio::Hsp#csline436,11087 60 | attr_reader :rfline::Bio::Hsp#rfline439,11138 61 | def initialize::Bio::Hsp#Bio::Hsp.new442,11184 62 | def set_alignment::Bio::Hsp#set_alignment464,11779 63 | def query_seq::Bio::Hsp#query_seq489,12836 64 | def target_seq::Bio::Hsp#target_seq494,12920 65 | def target_from::Bio::Hsp#target_from499,13010 66 | def target_to::Bio::Hsp#target_to504,13091 67 | def query_from::Bio::Hsp#query_from509,13173 68 | def query_to::Bio::Hsp#query_to514,13253 69 | -------------------------------------------------------------------------------- /test/regression/report.rb.tags.expect: -------------------------------------------------------------------------------- 1 | 2 | ./data/report.rb,3763 3 | module Bio::Bio4,126 4 | class HMMER::Bio::HMMER6,138 5 | def self.reports::Bio::HMMER.reports21,433 6 | class Report::Bio::HMMER::Report63,1436 7 | attr_reader :program::Bio::HMMER::Report#program71,1667 8 | attr_reader :parameter::Bio::HMMER::Report#parameter75,1785 9 | attr_reader :query_info::Bio::HMMER::Report#query_info79,1932 10 | attr_reader :hits::Bio::HMMER::Report#hits82,1968 11 | attr_reader :hsps::Bio::HMMER::Report#hsps88,2193 12 | attr_reader :histogram::Bio::HMMER::Report#histogram91,2247 13 | attr_reader :statistical_detail::Bio::HMMER::Report#statistical_detail94,2375 14 | attr_reader :total_seq_searched::Bio::HMMER::Report#total_seq_searched97,2447 15 | attr_reader :whole_seq_top_hits::Bio::HMMER::Report#whole_seq_top_hits100,2585 16 | attr_reader :domain_top_hits::Bio::HMMER::Report#domain_top_hits103,2723 17 | def initialize::Bio::HMMER::Report#Bio::HMMER::Report.new115,3074 18 | def each::Bio::HMMER::Report#each165,4637 19 | def get_subdata::Bio::HMMER#get_subdata174,4769 20 | def parse_header_data::Bio::HMMER#parse_header_data219,6368 21 | def parse_query_info::Bio::HMMER#parse_query_info241,6910 22 | def parse_hit_data::Bio::HMMER#parse_hit_data256,7224 23 | def parse_hsp_data::Bio::HMMER#parse_hsp_data269,7526 24 | def parse_stat_data::Bio::HMMER#parse_stat_data282,7862 25 | class Hit::Bio::HMMER::Hit324,9039 26 | attr_reader :hsps::Bio::HMMER::Hit#hsps327,9110 27 | attr_reader :accession::Bio::HMMER::Hit#accession330,9144 28 | alias target_id::Bio::HMMER::Hit#target_id331,9173 29 | alias hit_id::Bio::HMMER::Hit#hit_id332,9206 30 | alias entry_id::Bio::HMMER::Hit#entry_id333,9239 31 | attr_reader :description::Bio::HMMER::Hit#description336,9288 32 | alias definition::Bio::HMMER::Hit#definition337,9319 33 | attr_reader :score::Bio::HMMER::Hit#score340,9400 34 | alias bit_score::Bio::HMMER::Hit#bit_score341,9425 35 | attr_reader :evalue::Bio::HMMER::Hit#evalue344,9471 36 | attr_reader :num::Bio::HMMER::Hit#num347,9524 37 | def initialize::Bio::HMMER::Hit#Bio::HMMER::Hit.new350,9579 38 | def each::Bio::HMMER::Hit#each360,9904 39 | def target_def::Bio::HMMER#target_def369,10060 40 | def append_hsp::Bio::HMMER#append_hsp378,10272 41 | class Hsp::Bio::Hsp386,10401 42 | attr_reader :accession::Bio::Hsp#accession389,10433 43 | alias target_id::Bio::Hsp#target_id390,10462 44 | attr_reader :domain::Bio::Hsp#domain393,10503 45 | attr_reader :seq_f::Bio::Hsp#seq_f396,10544 46 | attr_reader :seq_t::Bio::Hsp#seq_t399,10578 47 | attr_reader :seq_ft::Bio::Hsp#seq_ft402,10612 48 | attr_reader :hmm_f::Bio::Hsp#hmm_f405,10647 49 | attr_reader :hmm_t::Bio::Hsp#hmm_t408,10681 50 | attr_reader :hmm_ft::Bio::Hsp#hmm_ft411,10715 51 | attr_reader :score::Bio::Hsp#score414,10756 52 | alias bit_score::Bio::Hsp#bit_score415,10781 53 | attr_reader :evalue::Bio::Hsp#evalue418,10826 54 | attr_reader :midline::Bio::Hsp#midline421,10885 55 | attr_reader :hmmseq::Bio::Hsp#hmmseq424,10927 56 | attr_reader :flatseq::Bio::Hsp#flatseq427,10962 57 | attr_reader :query_frame::Bio::Hsp#query_frame430,10998 58 | attr_reader :target_frame::Bio::Hsp#target_frame433,11038 59 | attr_reader :csline::Bio::Hsp#csline436,11087 60 | attr_reader :rfline::Bio::Hsp#rfline439,11138 61 | def initialize::Bio::Hsp#Bio::Hsp.new442,11184 62 | def set_alignment::Bio::Hsp#set_alignment464,11779 63 | def query_seq::Bio::Hsp#query_seq489,12836 64 | def target_seq::Bio::Hsp#target_seq494,12920 65 | def target_from::Bio::Hsp#target_from499,13010 66 | def target_to::Bio::Hsp#target_to504,13091 67 | def query_from::Bio::Hsp#query_from509,13173 68 | def query_to::Bio::Hsp#query_to514,13253 69 | -------------------------------------------------------------------------------- /test/data/report.rb: -------------------------------------------------------------------------------- 1 | # For rtags regression testing (original report.rb file by 2 | # Hiroshi Suga and Masashi Fujita as part of the BIORUBY package) 3 | 4 | module Bio 5 | 6 | class HMMER 7 | 8 | # A reader interface for multiple reports text into a report 9 | # (Bio::HMMER::Report). 10 | # 11 | # === Examples 12 | # 13 | # # Iterator 14 | # Bio::HMMER.reports(reports_text) do |report| 15 | # report 16 | # end 17 | # 18 | # # Array 19 | # reports = Bio::HMMER.reports(reports_text) 20 | # 21 | def self.reports(multiple_report_text) 22 | ary = [] 23 | multiple_report_text.each("\n//\n") do |report| 24 | if block_given? 25 | yield Report.new(report) 26 | else 27 | ary << Report.new(report) 28 | end 29 | end 30 | return ary 31 | end 32 | 33 | 34 | # A parser class for a search report by hmmsearch or hmmpfam program in the 35 | # HMMER package. 36 | # 37 | # === Examples 38 | # 39 | # Examples 40 | # #for multiple reports in a single output file (example.hmmpfam) 41 | # Bio::HMMER.reports(File.read("example.hmmpfam")) do |report| 42 | # report.program['name'] 43 | # report.parameter['HMM file'] 44 | # report.query_info['Query sequence'] 45 | # report.hits.each do |hit| 46 | # hit.accession 47 | # hit.description 48 | # hit.score 49 | # hit.evalue 50 | # hit.hsps.each do |hsp| 51 | # hsp.accession 52 | # hsp.domain 53 | # hsp.evalue 54 | # hsp.midline 55 | # end 56 | # end 57 | # 58 | # === References 59 | # 60 | # * HMMER 61 | # http://hmmer.wustl.edu/ 62 | # 63 | class Report 64 | 65 | # Delimiter of each entry for Bio::FlatFile support. 66 | DELIMITER = RS = "\n//\n" 67 | 68 | 69 | # A Hash contains program information used. 70 | # Valid keys are 'name', 'version', 'copyright' and 'license'. 71 | attr_reader :program 72 | 73 | # A hash contains parameters used. 74 | # Valid keys are 'HMM file' and 'Sequence file'. 75 | attr_reader :parameter 76 | 77 | # A hash contains the query information. 78 | # Valid keys are 'query sequence', 'Accession' and 'Description'. 79 | attr_reader :query_info 80 | 81 | # 82 | attr_reader :hits 83 | 84 | # Returns an Array of Bio::HMMER::Report::Hsp objects. 85 | # Under special circumstances, some HSPs do not have 86 | # parent Hit objects. If you want to access such HSPs, 87 | # use this method. 88 | attr_reader :hsps 89 | 90 | # statistics by hmmsearch. 91 | attr_reader :histogram 92 | 93 | # statistics by hmmsearch. Keys are 'mu', 'lambda', 'chi-sq statistic' and 'P(chi-square)'. 94 | attr_reader :statistical_detail 95 | 96 | # statistics by hmmsearch. 97 | attr_reader :total_seq_searched 98 | 99 | # statistics by hmmsearch. Keys are 'Total memory', 'Satisfying E cutoff' and 'Total hits'. 100 | attr_reader :whole_seq_top_hits 101 | 102 | # statistics by hmmsearch. Keys are 'Total memory', 'Satisfying E cutoff' and 'Total hits'. 103 | attr_reader :domain_top_hits 104 | 105 | 106 | # Parses a HMMER search report (by hmmpfam or hmmsearch program) and 107 | # reutrns a Bio::HMMER::Report object. 108 | # 109 | # === Examples 110 | # 111 | # hmmpfam_report = Bio::HMMER::Report.new(File.read("hmmpfam.out")) 112 | # 113 | # hmmsearch_report = Bio::HMMER::Report.new(File.read("hmmsearch.out")) 114 | # 115 | def initialize(data) 116 | 117 | # The input data is divided into six data fields, i.e. header, 118 | # query infomation, hits, HSPs, alignments and search statistics. 119 | # However, header and statistics data don't necessarily exist. 120 | subdata, is_hmmsearch = get_subdata(data) 121 | 122 | # if header exists, parse it 123 | if subdata["header"] 124 | @program, @parameter = parse_header_data(subdata["header"]) 125 | else 126 | @program, @parameter = [{}, {}] 127 | end 128 | 129 | @query_info = parse_query_info(subdata["query"]) 130 | @hits = parse_hit_data(subdata["hit"]) 131 | @hsps = parse_hsp_data(subdata["hsp"], is_hmmsearch) 132 | 133 | if @hsps != [] 134 | # split alignment subdata into an array of alignments 135 | aln_ary = subdata["alignment"].split(/^\S+.*?\n/).slice(1..-1) 136 | 137 | # append alignment information to corresponding Hsp 138 | aln_ary.each_with_index do |aln, i| 139 | @hsps[i].set_alignment(aln) 140 | end 141 | end 142 | 143 | # assign each Hsp object to its parent Hit 144 | hits_hash = {} 145 | @hits.each do |hit| 146 | hits_hash[hit.accession] = hit 147 | end 148 | @hsps.each do |hsp| 149 | if hits_hash.has_key?(hsp.accession) 150 | hits_hash[hsp.accession].append_hsp(hsp) 151 | end 152 | end 153 | 154 | # parse statistics (for hmmsearch) 155 | if is_hmmsearch 156 | @histogram, @statistical_detail, @total_seq_searched, \ 157 | @whole_seq_top_hits, @domain_top_hits = \ 158 | parse_stat_data(subdata["statistics"]) 159 | end 160 | 161 | end 162 | 163 | 164 | # Iterates each hit (Bio::HMMER::Report::Hit). 165 | def each 166 | @hits.each do |hit| 167 | yield hit 168 | end 169 | end 170 | alias :each_hit :each 171 | 172 | 173 | # Bio::HMMER::Report#get_subdata 174 | def get_subdata(data) 175 | subdata = {} 176 | header_prefix = '\Ahmm(search|pfam) - search' 177 | query_prefix = '^Query (HMM|sequence): .*\nAccession: ' 178 | hit_prefix = '^Scores for (complete sequences|sequence family)' 179 | hsp_prefix = '^Parsed for domains:' 180 | aln_prefix = '^Alignments of top-scoring domains:\n' 181 | stat_prefix = '^\nHistogram of all scores:' 182 | 183 | # if header exists, get it 184 | if data =~ /#{header_prefix}/ 185 | is_hmmsearch = ($1 == "search") # hmmsearch or hmmpfam 186 | subdata["header"] = data[/(\A.+?)(?=#{query_prefix})/m] 187 | else 188 | is_hmmsearch = false # if no header, assumed to be hmmpfam 189 | end 190 | 191 | # get query, Hit and Hsp data 192 | subdata["query"] = data[/(#{query_prefix}.+?)(?=#{hit_prefix})/m] 193 | subdata["hit"] = data[/(#{hit_prefix}.+?)(?=#{hsp_prefix})/m] 194 | subdata["hsp"] = data[/(#{hsp_prefix}.+?)(?=#{aln_prefix})/m] 195 | 196 | # get alignment data 197 | if is_hmmsearch 198 | data =~ /#{aln_prefix}(.+?)#{stat_prefix}/m 199 | subdata["alignment"] = $1 200 | else 201 | data =~ /#{aln_prefix}(.+?)\/\/\n/m 202 | subdata["alignment"] = $1 203 | raise "multiple reports found" if $'.length > 0 204 | end 205 | 206 | # handle -A option of HMMER 207 | cutoff_line = '\t\[output cut off at A = \d+ top alignments\]\n\z' 208 | subdata["alignment"].sub!(/#{cutoff_line}/, '') 209 | 210 | # get statistics data 211 | subdata["statistics"] = data[/(#{stat_prefix}.+)\z/m] 212 | 213 | [subdata, is_hmmsearch] 214 | end 215 | private :get_subdata 216 | 217 | 218 | # Bio::HMMER::Report#parse_header_data 219 | def parse_header_data(data) 220 | data =~ /\A(.+? - - -$\n)(.+? - - -$\n)\n\z/m 221 | program_data = $1 222 | parameter_data = $2 223 | 224 | program = {} 225 | program['name'], program['version'], program['copyright'], \ 226 | program['license'] = program_data.split(/\n/) 227 | 228 | parameter = {} 229 | parameter_data.each do |x| 230 | if /^(.+?):\s+(.*?)\s*$/ =~ x 231 | parameter[$1] = $2 232 | end 233 | end 234 | 235 | [program, parameter] 236 | end 237 | private :parse_header_data 238 | 239 | 240 | # Bio::HMMER::Report#parse_query_info 241 | def parse_query_info(data) 242 | hash = {} 243 | data.each do |x| 244 | if /^(.+?):\s+(.*?)\s*$/ =~ x 245 | hash[$1] = $2 246 | elsif /\s+\[(.+)\]/ =~ x 247 | hash['comments'] = $1 248 | end 249 | end 250 | hash 251 | end 252 | private :parse_query_info 253 | 254 | 255 | # Bio::HMMER::Report#parse_hit_data 256 | def parse_hit_data(data) 257 | data.sub!(/.+?---\n/m, '').chop! 258 | hits = [] 259 | return hits if data == "\t[no hits above thresholds]\n" 260 | data.each do |l| 261 | hits.push(Hit.new(l)) 262 | end 263 | hits 264 | end 265 | private :parse_hit_data 266 | 267 | 268 | # Bio::HMMER::Report#parse_hsp_data 269 | def parse_hsp_data(data, is_hmmsearch) 270 | data.sub!(/.+?---\n/m, '').chop! 271 | hsps=[] 272 | return hsps if data == "\t[no hits above thresholds]\n" 273 | data.each do |l| 274 | hsps.push(Hsp.new(l, is_hmmsearch)) 275 | end 276 | return hsps 277 | end 278 | private :parse_hsp_data 279 | 280 | 281 | # Bio::HMMER::Report#parse_stat_data 282 | def parse_stat_data(data) 283 | data.sub!(/\nHistogram of all scores:\n(.+?)\n\n\n%/m, '') 284 | histogram = $1.strip 285 | 286 | statistical_detail = {} 287 | data.sub!(/(.+?)\n\n/m, '') 288 | $1.each do |l| 289 | statistical_detail[$1] = $2.to_f if /^\s*(.+?)\s*=\s*(\S+)/ =~ l 290 | end 291 | 292 | total_seq_searched = nil 293 | data.sub!(/(.+?)\n\n/m, '') 294 | $1.each do |l| 295 | total_seq_searched = $2.to_i if /^\s*(.+)\s*:\s*(\S+)/ =~ l 296 | end 297 | 298 | whole_seq_top_hits = {} 299 | data.sub!(/(.+?)\n\n/m, '') 300 | $1.each do |l| 301 | if /^\s*(.+?):\s*(\d+)\s*$/ =~ l 302 | whole_seq_top_hits[$1] = $2.to_i 303 | elsif /^\s*(.+?):\s*(\S+)\s*$/ =~ l 304 | whole_seq_top_hits[$1] = $2 305 | end 306 | end 307 | 308 | domain_top_hits = {} 309 | data.each do |l| 310 | if /^\s*(.+?):\s*(\d+)\s*$/ =~ l 311 | domain_top_hits[$1] = $2.to_i 312 | elsif /^\s*(.+?):\s*(\S+)\s*$/ =~ l 313 | domain_top_hits[$1] = $2 314 | end 315 | end 316 | 317 | [histogram, statistical_detail, total_seq_searched, \ 318 | whole_seq_top_hits, domain_top_hits] 319 | end 320 | private :parse_stat_data 321 | 322 | 323 | # Container class for HMMER search hits. 324 | class Hit 325 | 326 | # An Array of Bio::HMMER::Report::Hsp objects. 327 | attr_reader :hsps 328 | 329 | # 330 | attr_reader :accession 331 | alias target_id accession 332 | alias hit_id accession 333 | alias entry_id accession 334 | 335 | # 336 | attr_reader :description 337 | alias definition description 338 | 339 | # Matching scores (total of all HSPs). 340 | attr_reader :score 341 | alias bit_score score 342 | 343 | # E-value 344 | attr_reader :evalue 345 | 346 | # Number of domains 347 | attr_reader :num 348 | 349 | # Sets hit data. 350 | def initialize(hit_data) 351 | @hsps = Array.new 352 | if /^(\S+)\s+(.*?)\s+(\S+)\s+(\S+)\s+(\S+)$/ =~ hit_data 353 | @accession, @description, @score, @evalue, @num = \ 354 | [$1, $2, $3.to_f, $4.to_f, $5.to_i] 355 | end 356 | end 357 | 358 | 359 | # Iterates on each Hsp object (Bio::HMMER::Report::Hsp). 360 | def each 361 | @hsps.each do |hsp| 362 | yield hsp 363 | end 364 | end 365 | alias :each_hsp :each 366 | 367 | 368 | # Shows the hit description. 369 | def target_def 370 | if @hsps.size == 1 371 | "<#{@hsps[0].domain}> #{@description}" 372 | else 373 | "<#{@num.to_s}> #{@description}" 374 | end 375 | end 376 | 377 | # Appends a Bio::HMMER::Report::Hsp object. 378 | def append_hsp(hsp) 379 | @hsps << hsp 380 | end 381 | 382 | end # class Hit 383 | 384 | 385 | # Container class for HMMER search hsps. 386 | class Hsp 387 | 388 | # 389 | attr_reader :accession 390 | alias target_id accession 391 | 392 | # 393 | attr_reader :domain 394 | 395 | # 396 | attr_reader :seq_f 397 | 398 | # 399 | attr_reader :seq_t 400 | 401 | # 402 | attr_reader :seq_ft 403 | 404 | # 405 | attr_reader :hmm_f 406 | 407 | # 408 | attr_reader :hmm_t 409 | 410 | # 411 | attr_reader :hmm_ft 412 | 413 | # Score 414 | attr_reader :score 415 | alias bit_score score 416 | 417 | # E-value 418 | attr_reader :evalue 419 | 420 | # Alignment midline 421 | attr_reader :midline 422 | 423 | # 424 | attr_reader :hmmseq 425 | 426 | # 427 | attr_reader :flatseq 428 | 429 | # 430 | attr_reader :query_frame 431 | 432 | # 433 | attr_reader :target_frame 434 | 435 | # CS Line 436 | attr_reader :csline 437 | 438 | # RF Line 439 | attr_reader :rfline 440 | 441 | # Sets hsps. 442 | def initialize(hsp_data, is_hmmsearch) 443 | @is_hmmsearch = is_hmmsearch 444 | 445 | @accession, @domain, seq_f, seq_t, @seq_ft, hmm_f, hmm_t, @hmm_ft,\ 446 | score, evalue = hsp_data.split(' ') 447 | @seq_f = seq_f.to_i 448 | @seq_t = seq_t.to_i 449 | @hmm_f = hmm_f.to_i 450 | @hmm_t = hmm_t.to_i 451 | @score = score.to_f 452 | @evalue = evalue.to_f 453 | @hmmseq = '' 454 | @flatseq = '' 455 | @midline = '' 456 | @query_frame = 1 457 | @target_frame = 1 458 | # CS and RF lines are rarely used. 459 | @csline = nil 460 | @rfline = nil 461 | end 462 | 463 | # 464 | def set_alignment(alignment) 465 | # First, split the input alignment into an array of 466 | # "alignment blocks." One block usually has three lines, 467 | # i.e. hmmseq, midline and flatseq. 468 | # However, although infrequent, it can contain CS or RF lines. 469 | alignment.split(/ (?:\d+|-)\s*\n\n/).each do |blk| 470 | lines = blk.split(/\n/) 471 | cstmp = (lines[0] =~ /^ {16}CS/) ? lines.shift : nil 472 | rftmp = (lines[0] =~ /^ {16}RF/) ? lines.shift : nil 473 | aln_width = lines[0][/\S+/].length 474 | @csline = @csline.to_s + cstmp[19, aln_width] if cstmp 475 | @rfline = @rfline.to_s + rftmp[19, aln_width] if rftmp 476 | @hmmseq += lines[0][19, aln_width] 477 | @midline += lines[1][19, aln_width] 478 | @flatseq += lines[2][19, aln_width] 479 | end 480 | @csline = @csline[3...-3] if @csline 481 | @rfline = @rfline[3...-3] if @rfline 482 | @hmmseq = @hmmseq[3...-3] 483 | @midline = @midline[3...-3] 484 | @flatseq = @flatseq[3...-3] 485 | end 486 | 487 | 488 | # 489 | def query_seq 490 | @is_hmmsearch ? @hmmseq : @flatseq 491 | end 492 | 493 | # 494 | def target_seq 495 | @is_hmmsearch ? @flatseq : @hmmseq 496 | end 497 | 498 | # 499 | def target_from 500 | @is_hmmsearch ? @seq_f : @hmm_f 501 | end 502 | 503 | # 504 | def target_to 505 | @is_hmmsearch ? @seq_t : @hmm_t 506 | end 507 | 508 | # 509 | def query_from 510 | @is_hmmsearch ? @hmm_f : @seq_f 511 | end 512 | 513 | # 514 | def query_to 515 | @is_hmmsearch ? @hmm_t : @seq_t 516 | end 517 | 518 | 519 | end # class Hsp 520 | 521 | end # class Report 522 | 523 | end # class HMMER 524 | 525 | end # module Bio 526 | 527 | 528 | if __FILE__ == $0 529 | 530 | =begin 531 | 532 | # 533 | # for multiple reports in a single output file (hmmpfam) 534 | # 535 | Bio::HMMER.reports(ARGF.read) do |report| 536 | report.hits.each do |hit| 537 | hit.hsps.each do |hsp| 538 | end 539 | end 540 | end 541 | 542 | =end 543 | 544 | begin 545 | require 'pp' 546 | alias p pp 547 | rescue LoadError 548 | end 549 | 550 | rep = Bio::HMMER::Report.new(ARGF.read) 551 | p rep 552 | 553 | indent = 18 554 | 555 | puts "### hmmer result" 556 | print "name : ".rjust(indent) 557 | p rep.program['name'] 558 | print "version : ".rjust(indent) 559 | p rep.program['version'] 560 | print "copyright : ".rjust(indent) 561 | p rep.program['copyright'] 562 | print "license : ".rjust(indent) 563 | p rep.program['license'] 564 | 565 | print "HMM file : ".rjust(indent) 566 | p rep.parameter['HMM file'] 567 | print "Sequence file : ".rjust(indent) 568 | p rep.parameter['Sequence file'] 569 | 570 | print "Query sequence : ".rjust(indent) 571 | p rep.query_info['Query sequence'] 572 | print "Accession : ".rjust(indent) 573 | p rep.query_info['Accession'] 574 | print "Description : ".rjust(indent) 575 | p rep.query_info['Description'] 576 | 577 | rep.each do |hit| 578 | puts "## each hit" 579 | print "accession : ".rjust(indent) 580 | p [ hit.accession, hit.target_id, hit.hit_id, hit.entry_id ] 581 | print "description : ".rjust(indent) 582 | p [ hit.description, hit.definition ] 583 | print "target_def : ".rjust(indent) 584 | p hit.target_def 585 | print "score : ".rjust(indent) 586 | p [ hit.score, hit.bit_score ] 587 | print "evalue : ".rjust(indent) 588 | p hit.evalue 589 | print "num : ".rjust(indent) 590 | p hit.num 591 | 592 | hit.each do |hsp| 593 | puts "## each hsp" 594 | print "accession : ".rjust(indent) 595 | p [ hsp.accession, hsp.target_id ] 596 | print "domain : ".rjust(indent) 597 | p hsp.domain 598 | print "seq_f : ".rjust(indent) 599 | p hsp.seq_f 600 | print "seq_t : ".rjust(indent) 601 | p hsp.seq_t 602 | print "seq_ft : ".rjust(indent) 603 | p hsp.seq_ft 604 | print "hmm_f : ".rjust(indent) 605 | p hsp.hmm_f 606 | print "hmm_t : ".rjust(indent) 607 | p hsp.hmm_t 608 | print "hmm_ft : ".rjust(indent) 609 | p hsp.hmm_ft 610 | print "score : ".rjust(indent) 611 | p [ hsp.score, hsp.bit_score ] 612 | print "evalue : ".rjust(indent) 613 | p hsp.evalue 614 | print "midline : ".rjust(indent) 615 | p hsp.midline 616 | print "hmmseq : ".rjust(indent) 617 | p hsp.hmmseq 618 | print "flatseq : ".rjust(indent) 619 | p hsp.flatseq 620 | print "query_frame : ".rjust(indent) 621 | p hsp.query_frame 622 | print "target_frame : ".rjust(indent) 623 | p hsp.target_frame 624 | 625 | print "query_seq : ".rjust(indent) 626 | p hsp.query_seq # hmmseq, flatseq 627 | print "target_seq : ".rjust(indent) 628 | p hsp.target_seq # flatseq, hmmseq 629 | print "target_from : ".rjust(indent) 630 | p hsp.target_from # seq_f, hmm_f 631 | print "target_to : ".rjust(indent) 632 | p hsp.target_to # seq_t, hmm_t 633 | print "query_from : ".rjust(indent) 634 | p hsp.query_from # hmm_f, seq_f 635 | print "query_to : ".rjust(indent) 636 | p hsp.query_to # hmm_t, seq_t 637 | end 638 | end 639 | 640 | end 641 | 642 | 643 | -------------------------------------------------------------------------------- /bin/rtags: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # rtags is a Ruby replacement for ctags - allowing for name navigation in 4 | # source code using vim, emacs and others. 5 | # 6 | # by Keiju ISHITSUKA and Pjotr Prins 7 | # 8 | # LICENSE: RUBY LICENSE - see file LICENSE.TXT 9 | # INSTALL: see file README 10 | # RELEASE NOTES: see file RELEASENOTES 11 | 12 | RTAGS_VERSION='0.99.1 (January 2013)' 13 | 14 | require 'optparse' 15 | require 'ostruct' 16 | 17 | usage = < 53 | rtags --vi -f vim_tags *.rb 54 | rtags `find ~/src/ -name *.rb|grep -v _darcs` 55 | 56 | EXAMPLE 57 | exit() 58 | end 59 | 60 | opts.on("-f tagfile", String, "Output to tagfile") do |s| 61 | $options.tagfile = s 62 | end 63 | opts.on("-a", "append mode") do 64 | $options.tagfile_mode = 'a' 65 | end 66 | opts.on("-w level", Integer, "Warning level (default 0)") do |w| 67 | $options.warning_level = w 68 | end 69 | opts.on("--vi", "Use vi type tags (default is emacs)") do 70 | $options.vi = true 71 | end 72 | opts.on("-x", "Print cross reference file to stdout") do 73 | $options.xref = true 74 | end 75 | opts.on("-b", "When using -x, only print base name. E.g., print only 'method' and not '::Module::Class#method'") do 76 | $options.xref_only_base = true 77 | end 78 | opts.on("-B", "When using -x, only print absolute name. E.g., print only '::Module::Class#method' and not 'method'. Inverse of -b") do 79 | $options.xref_only_abs = true 80 | end 81 | opts.on("--quiet", "Quiet mode") do 82 | $options.quiet = true 83 | end 84 | opts.on("--debug", "Debug output to stderr") do 85 | $options.debug = true 86 | end 87 | opts.on("--debug_tokens", "Debug token information to stdout") do 88 | $options.debug_tokens = true 89 | end 90 | opts.on("-R", "--recurse", "Recurse path for .rb files (default '.')") do 91 | $options.recurse = true 92 | end 93 | opts.on("-L file", String, "Read from file a list of file names for which tags should be generated.") do |f| 94 | $options.files_in_list = f 95 | end 96 | end.parse!(ARGV) 97 | 98 | if $options.tagfile == nil 99 | if $options.vi 100 | $options.tagfile = 'tags' 101 | else 102 | $options.tagfile = 'TAGS' 103 | end 104 | end 105 | $options.tagfile_mode = 'w' if $options.tagfile_mode == nil 106 | $options.warning_level = -1 if $options.quiet 107 | if $options.recurse and ARGV.size == 0 108 | ARGV.push '.' 109 | end 110 | 111 | print "\nrtags.rb #{RTAGS_VERSION} writing to file '#{$options.tagfile}'\n" if (!$options.quiet && !$options.xref) 112 | 113 | require "e2mmap" 114 | require "tracer" 115 | 116 | require "irb/ruby-lex" 117 | require "irb/ruby-token" 118 | 119 | class RubyLex 120 | def identify_string_dvar 121 | end 122 | end 123 | 124 | require "stringio" 125 | 126 | module RTAGS 127 | @RCS_ID='-rtags.rb 0.95 -' 128 | 129 | class RTToken 130 | def initialize(readed, context, name, token) 131 | @readed = readed 132 | @context = context 133 | @name = name 134 | @token = token 135 | end 136 | attr :readed 137 | attr :context 138 | attr :name 139 | attr :token 140 | 141 | def line_no 142 | @token.line_no 143 | end 144 | 145 | def seek 146 | @token.seek 147 | end 148 | 149 | def to_s 150 | "#{def_name} #{abs_name} in #{token.inspect}" 151 | end 152 | end 153 | 154 | class RTModule < RTToken 155 | def abs_name 156 | (context || "") + "::" + name 157 | end 158 | 159 | def def_name 160 | "module" 161 | end 162 | end 163 | 164 | class RTClass < RTModule 165 | def abs_name 166 | (context || "") + "::" + name 167 | end 168 | 169 | def def_name 170 | "class" 171 | end 172 | end 173 | 174 | class RTSingleClass < RTClass 175 | def abs_name 176 | (context || "") + "<<" + name 177 | end 178 | 179 | def def_name 180 | "class" 181 | end 182 | end 183 | 184 | class RTMethod < RTToken 185 | def abs_name 186 | (context || "") + "#" + name 187 | end 188 | 189 | def def_name 190 | "method" 191 | end 192 | end 193 | 194 | class RTAlias < RTMethod 195 | def def_name 196 | "alias" 197 | end 198 | end 199 | 200 | class RTAttr < RTMethod 201 | def def_name 202 | "attr" 203 | end 204 | end 205 | 206 | class RTClassCall < RTMethod 207 | def abs_name 208 | (context || "") + "!" + (token.name == name ? name : "#{token.name}(#{name})") 209 | end 210 | 211 | def def_name 212 | "class call" 213 | end 214 | end 215 | 216 | class RTSingleMethod < RTToken 217 | def abs_name 218 | (context || "") + "." + name 219 | end 220 | 221 | def def_name 222 | "class method" 223 | end 224 | end 225 | 226 | class RTSingleAlias < RTSingleMethod 227 | def def_name 228 | "alias" 229 | end 230 | end 231 | 232 | class RTSingleAttr < RTSingleMethod 233 | def def_name 234 | "attr" 235 | end 236 | end 237 | 238 | class RTSingleClassCall < RTSingleMethod 239 | def abs_name 240 | (context || "") + ":" + (token.name == name ? name : "#{token.name}(#{name})") 241 | end 242 | 243 | def def_name 244 | "class call" 245 | end 246 | end 247 | 248 | class Parser 249 | include RubyToken 250 | 251 | def initialize(file_name) 252 | @size = 0 253 | @input_file_name = file_name 254 | @scanner = RubyLex.new 255 | @scanner.exception_on_syntax_error = false 256 | # @scanner.skip_space = true 257 | # @scanner.readed_auto_clean_up = true 258 | #parse_statements 259 | end 260 | 261 | # display warnings on stderr, depending on the set warning_level 262 | 263 | def warn s,level=0,token=nil,extra=nil 264 | if level <= $options.warning_level 265 | $stderr.print "\nWarn: #{s} in <#{@input_file_name}> " 266 | if token 267 | $stderr.print "in line #{token.line_no}, pos #{token.char_no}" 268 | # $stderr.print "by name <#{token.name}>" if token and token.name 269 | $stderr.print "\n" 270 | end 271 | if $options.debug 272 | $stderr.print "Token=",token.inspect,"\n" if token 273 | $stderr.print extra if extra 274 | end 275 | end 276 | end 277 | 278 | # Scan +@input_file_name+ for tags and yields each tag 279 | 280 | def scan(&block) 281 | print "\nParsing #{@input_file_name}..." if (!$options.quiet && !$options.xref) 282 | 283 | input = StringIO.new(File.read(@input_file_name) + "\n") 284 | @tokens = [] 285 | @unget_readed = [] 286 | @readed = [] 287 | @scanner.set_input(input) 288 | parse_statements(&block) 289 | end 290 | 291 | # get the next token - fetching it from the temporary +@tokens+ 292 | # stack if it is not empty 293 | 294 | def get_tk 295 | if @tokens.empty? 296 | tk = @scanner.token 297 | @readed.push @scanner.get_readed 298 | $stderr.print tk.inspect if $options.debug_tokens 299 | tk 300 | else 301 | @readed.push @unget_readed.shift 302 | tk = @tokens.shift 303 | $stderr.print tk.inspect if $options.debug_tokens 304 | tk 305 | end 306 | end 307 | 308 | # lookahead returning the next token without popping it 309 | def peek_tk 310 | unget_tk(tk = get_tk) 311 | tk 312 | end 313 | 314 | # push the token +tk+ back onto the stack 315 | def unget_tk(tk) 316 | @tokens.unshift tk 317 | @unget_readed.unshift @readed.pop 318 | end 319 | 320 | def skip_tkspace(skip_nl = true) 321 | tokens = [] 322 | while ((tk = get_tk).kind_of?(TkSPACE) || 323 | (skip_nl && tk.kind_of?(TkNL))) 324 | tokens.push tk 325 | end 326 | unget_tk(tk) 327 | tokens 328 | end 329 | 330 | # returns the actual token string as it was read and 331 | # sets the read buffer to zero length 332 | def get_tkreaded 333 | readed = @readed.join("") 334 | @readed = [] 335 | readed 336 | end 337 | 338 | NORMAL = "::" 339 | SINGLE = "<<" 340 | 341 | # The 'grunt' method splits a line into tokens and invokes the 342 | # basic parsers for class, module, method etc. 343 | 344 | def parse_statements(context = nil, single = NORMAL, &block) 345 | begin 346 | last_seekpos = -1 347 | nest = 1 348 | symbeg = false 349 | 350 | while (tk = get_tk) 351 | case tk 352 | when TkCLASS 353 | parse_class(context, single, tk, &block) 354 | when TkMODULE 355 | parse_module(context, single, tk, &block) 356 | when TkDEF 357 | nest += 1 358 | parse_method(context, single, tk, &block) 359 | when TkALIAS 360 | parse_alias(context, single, tk, &block) 361 | when TkCASE, 362 | TkDO, 363 | TkFOR, 364 | TkIF, 365 | TkUNLESS, 366 | TkUNTIL, 367 | TkWHILE, 368 | TkBEGIN 369 | nest += 1 unless symbeg 370 | when TkSYMBEG 371 | symbeg = true 372 | when TkIDENTIFIER, TkIVAR, TkCVAR, TkGVAR, TkBITNOT, TkMINUS, TkBITAND 373 | parse_identifier(context, single, nest, symbeg, tk, &block) 374 | symbeg = false 375 | when TkEND 376 | return if (nest -= 1) == 0 377 | when TkLBRACE, TkfLBRACE 378 | nest += 1 379 | when TkRBRACE 380 | nest -= 1 381 | end 382 | begin 383 | get_tkreaded 384 | skip_tkspace(false) # don't skip newlines 385 | # prevent endless loop (tokenizer does not always behave 386 | # at eof) 387 | tmp_tk = tk 388 | seekpos = tmp_tk.seek if tmp_tk and tmp_tk.seek 389 | if last_seekpos == seekpos 390 | warn('bailing out early',0,tk) 391 | return 392 | end 393 | last_seekpos = seekpos 394 | end while peek_tk == TkNL 395 | # p [@input_file_name, peek_tk] 396 | end 397 | rescue 398 | warn('parse error',0,tk,"#{$!.message}:\n#{$!.backtrace.join("\n")}") 399 | end 400 | end 401 | 402 | # Get the full name of the constant including namespaces 403 | def parse_full_constant_name(name_token) 404 | name = name_token.name 405 | if peek_tk.kind_of? TkCOLON2 406 | get_tk # skip the :: 407 | namespaced_name_token = get_tk 408 | rest = parse_full_constant_name(namespaced_name_token) 409 | name += '::' + rest unless rest.nil? 410 | end 411 | 412 | name 413 | end 414 | 415 | def parse_class(context, single, tk, &block) 416 | skip_tkspace 417 | case name_t = get_tk 418 | when TkCONSTANT 419 | name = parse_full_constant_name(name_t) 420 | if single == SINGLE 421 | yield RTSingleClass.new(get_tkreaded, context, name, tk) 422 | else 423 | yield RTClass.new(get_tkreaded, context, name, tk) 424 | end 425 | parse_statements((context || "") + single + name, &block) 426 | 427 | when TkLSHFT 428 | skip_tkspace 429 | case (name_t2 = get_tk) 430 | when TkSELF 431 | parse_statements(context, SINGLE, &block) 432 | when TkCONSTANT, TkIVAR 433 | # yield RTSingleClass.new(get_tkreaded, context, name_t2.name, tk) 434 | parse_statements((context || "") + "::" + name_t2.name, 435 | SINGLE, 436 | &block) 437 | else 438 | warn('unrecognised token',1,name_t2) 439 | end 440 | else 441 | warn('unrecognised token',1,name_t2) 442 | end 443 | end 444 | 445 | def parse_module(context, single, tk, &block) 446 | skip_tkspace 447 | name = get_tk.name 448 | yield RTModule.new(get_tkreaded, context, name, tk) 449 | parse_statements((context||"") + single + name, &block) 450 | end 451 | 452 | def parse_method(context, single, tk, &block) 453 | skip_tkspace 454 | name_t = get_tk 455 | back_tk = skip_tkspace 456 | 457 | if (dot = get_tk).kind_of?(TkDOT) 458 | # tricky tech. Not sure when this gets reached - probably 459 | # 'static' definitions as part of modules 460 | @scanner.instance_eval{@lex_state = EXPR_FNAME} 461 | skip_tkspace 462 | name_t2 = get_tk 463 | case name_t 464 | when TkSELF 465 | name = name_t2.name 466 | when TkId 467 | if context and 468 | context =~ /^#{name_t.name}$/ || context =~ /::#{name_t.name}$/ 469 | name = name_t2.name 470 | else 471 | context = (context || "") + "::" + name_t.name 472 | name = name_t2.name 473 | end 474 | else 475 | warn('unrecognised token',1,name_t2) 476 | #break 477 | end 478 | yield RTSingleMethod.new(get_tkreaded, context, name, tk) 479 | 480 | else 481 | unget_tk dot 482 | back_tk.reverse_each do |tk| 483 | unget_tk tk 484 | end 485 | name = name_t.name 486 | # ---- if the method is an initialize make sure the class 487 | # initiation (with Class.new) points to the tag 488 | if name=='initialize' 489 | name = 'new' 490 | if context and context =~ /::(\S+)$/ 491 | name = $1+'.new' 492 | end 493 | end 494 | if single == SINGLE 495 | yield RTSingleMethod.new(get_tkreaded, context, name, tk) 496 | else 497 | yield RTMethod.new(get_tkreaded, context, name, tk) 498 | end 499 | end 500 | end 501 | 502 | def parse_alias(context, single, tk, &block) 503 | skip_tkspace 504 | if (token = get_tk).is_a? TkSYMBEG 505 | # Name is in next token 506 | token = get_tk 507 | end 508 | 509 | name = token.name 510 | if context 511 | if single == SINGLE 512 | yield RTSingleAlias.new(get_tkreaded, context, name, tk) 513 | else 514 | yield RTAlias.new(get_tkreaded, context, name, tk) 515 | end 516 | else 517 | if single == SINGLE 518 | yield RTSingleAlias.new(get_tkreaded, "main", name, tk) 519 | else 520 | yield RTAlias.new(get_tkreaded, nil, name, tk) 521 | end 522 | end 523 | end 524 | 525 | def parse_attr(context, single, tk, &block) 526 | args = parse_symbol_arg(1) 527 | if args.size > 0 528 | name = args[0] 529 | if context 530 | if single == SINGLE 531 | yield RTSingleAttr.new(get_tkreaded, context, name, tk) 532 | else 533 | yield RTAttr.new(get_tkreaded, context, name, tk) 534 | end 535 | else 536 | if single == SINGLE 537 | yield RTSingleAttr.new(get_tkreaded, "main", name, tk) 538 | else 539 | yield RTAttr.new(get_tkreaded, nil, name, tk) 540 | end 541 | end 542 | else 543 | warn('token not recognized - next attr arg size == zero',1) 544 | end 545 | end 546 | 547 | def parse_attr_accessor(context, single, tk, &block) 548 | args = parse_symbol_arg 549 | readed = get_tkreaded 550 | for name in args 551 | if context 552 | if single == SINGLE 553 | yield RTSingleAttr.new(readed, context, name, tk) 554 | else 555 | yield RTAttr.new(readed, context, name, tk) 556 | end 557 | else 558 | if single == SINGLE 559 | yield RTSingleAttr.new(readed, "main", name, tk) 560 | else 561 | yield RTAttr.new(readed, nil, name, tk) 562 | end 563 | end 564 | end 565 | end 566 | 567 | BAD_KNOWN_CLASS_METHODS = %w(class_eval instance_eval public private protected) 568 | 569 | def parse_known_class_method_calls(context, single, tk, &block) 570 | args = parse_symbol_arg 571 | readed = get_tkreaded 572 | (args.empty? ? [tk.name] : args).each do |name| 573 | if context 574 | if single == SINGLE 575 | yield RTSingleClassCall.new(readed, context, name, tk) 576 | else 577 | yield RTClassCall.new(readed, context, name, tk) 578 | end 579 | else 580 | if single == SINGLE 581 | yield RTSingleClassCall.new(readed, "main", name, tk) 582 | else 583 | yield RTClassCall.new(readed, nil, name, tk) 584 | end 585 | end 586 | end 587 | end 588 | 589 | def parse_identifier(context, single, nest, symbeg, tk, &block) 590 | case tk.name 591 | when "attr" 592 | parse_attr(context, single, tk, &block) 593 | when /^attr_(reader|writer|accessor)$/ 594 | parse_attr_accessor(context, single, tk, &block) 595 | else 596 | if !context.nil? && nest == 1 && !symbeg && !BAD_KNOWN_CLASS_METHODS.include?(tk.name) 597 | parse_known_class_method_calls(context, single, tk, &block) 598 | end 599 | end 600 | end 601 | 602 | 603 | 604 | def parse_symbol_arg(no = nil) 605 | args = [] 606 | skip_tkspace 607 | case (tk = get_tk) 608 | when TkLPAREN 609 | loop do 610 | skip_tkspace 611 | if tk1 = parse_symbol_in_arg 612 | args << tk1 613 | return args if no and args.size >= no 614 | end 615 | 616 | skip_tkspace 617 | case tk2 = get_tk 618 | when TkRPAREN 619 | return args 620 | when TkCOMMA 621 | else 622 | warn('token not recognized in funargs',1,tk) 623 | return args 624 | end 625 | end 626 | else 627 | unget_tk tk 628 | 629 | loop do 630 | skip_tkspace(false) 631 | 632 | case tk1 = get_tk 633 | when TkCOMMA, TkOp 634 | skip_tkspace 635 | when TkDO, TkNL, TkASSIGN 636 | unget_tk tk1 637 | return args 638 | else 639 | unget_tk tk1 640 | if tk = parse_symbol_in_arg 641 | args << tk 642 | return args if no and args.size >= no 643 | else 644 | warn('token not recognized in funargs',1,tk1) 645 | return args 646 | end 647 | 648 | skip_tkspace(false) 649 | end 650 | end 651 | end 652 | args 653 | end 654 | 655 | def parse_symbol_in_arg 656 | tokens = [] 657 | symbeg = nil 658 | while (tk = get_tk) 659 | case tk 660 | when TkSYMBEG 661 | symbeg = tk 662 | when TkIDENTIFIER 663 | skip_tkspace(false) 664 | 665 | tk1 = get_tk 666 | unget_tk tk1 667 | 668 | if tk1.is_a?(TkASSIGN) 669 | unget_tk tk 670 | unget_tk symbeg if symbeg 671 | warn('token not recognized; next ',1,tk) 672 | return tokens.empty? ? nil : tokens.join 673 | end 674 | 675 | tokens << tk.name 676 | symbeg = nil 677 | when TkCONSTANT, TkFID 678 | tokens << tk.name 679 | symbeg = nil 680 | when TkSTRING 681 | tokens << eval(@readed[-1]) 682 | symbeg = nil 683 | when TkCOLON2 684 | tokens << "::" 685 | symbeg = nil 686 | when TkDOT 687 | tokens << "." 688 | symbeg = nil 689 | else 690 | unget_tk tk 691 | unget_tk symbeg if symbeg 692 | warn('token not recognized; next SYMBEG ',1,tk) 693 | return tokens.empty? ? nil : tokens.join 694 | end 695 | skip_tkspace(false) 696 | end 697 | end 698 | end 699 | 700 | module Filter 701 | 702 | # Test for boring repository directories that should be skipped 703 | def Filter::skipdir name 704 | if name =~ /\/(\.svn|\.hg|_darcs|CVS|\.git)/ 705 | print "\nSkipping boring directory #{name}" 706 | return true 707 | end 708 | false 709 | end 710 | 711 | # Test whether this is a Ruby file 712 | def Filter::skipfile name 713 | if name =~ /.rb$/i 714 | return false 715 | end 716 | # OK - test for magic hash bang 717 | firstline = File.new(name, :encoding => "BINARY").gets 718 | return false if firstline =~ /^#\!/ and firstline =~ /ruby/i 719 | true 720 | end 721 | 722 | end 723 | 724 | class TAGS 725 | def initialize(files) 726 | if $options.recurse 727 | @files = [] 728 | recurse = lambda do |fname| 729 | if File.directory? fname and not Filter::skipdir(fname) 730 | Dir.entries(fname).select {|f| f !~ /^\.(?:\.|\w+)?$/ }.map {|f| fname+'/'+f }.each(&recurse) 731 | else 732 | # is a file 733 | if File.file?(fname) and not Filter::skipfile(fname) 734 | # @files.push(fname) if fname =~ /.rb$/i 735 | @files.push(fname) 736 | end 737 | end 738 | end 739 | files.each(&recurse) 740 | else 741 | @files = files 742 | end 743 | end 744 | end 745 | 746 | class EMACS_TAGS < TAGS 747 | # Create the tags file in emacs mode 748 | def shipout 749 | open($options.tagfile, $options.tagfile_mode) do |out| 750 | @output = out 751 | @files.each do |fn| 752 | if File.directory? fn 753 | puts "\nWarning: #{fn} is a directory - SKIPPING. Specify --recurse option to recurse into directories.\n" 754 | next 755 | end 756 | output = [] 757 | size = 0 758 | 759 | $stderr.printf "--\n-- parse file: %s\n", fn if $options.debug 760 | parser = Parser.new(fn) 761 | parser.scan do |tk| 762 | $stderr.print tk, "\n" if $options.debug 763 | line = tk.readed.split(/\n/)[0] 764 | item = sprintf("%s\C-?%s\C-A%d,%s\n", 765 | line, 766 | tk.abs_name, 767 | tk.line_no, 768 | tk.seek) 769 | output.push item 770 | size += item.size 771 | end 772 | @output.print "\C-L\n#{fn},#{size}\n" 773 | @output.print output.join 774 | end 775 | end 776 | end 777 | end 778 | 779 | class VI_TAGS < TAGS 780 | # Create the tags file in vi(m) mode 781 | def shipout 782 | output = [] 783 | @files.each do |fn| 784 | if File.directory? fn 785 | puts "\nWarning: #{fn} is a directory - SKIPPING. Specify --recurse option to recurse into directories.\n" 786 | next 787 | end 788 | 789 | $stderr.printf "--\n-- parse file: %s\n", fn if $options.debug 790 | parser = Parser.new(fn) 791 | parser.scan do |tk| 792 | $stderr.print tk, "\n" if $options.debug 793 | line = tk.readed.split(/\n/)[0] 794 | output.push sprintf("%s\t%s\t/^%s/\n", 795 | tk.name, 796 | fn, 797 | line) 798 | output.push sprintf("%s\t%s\t/^%s/\n", 799 | tk.abs_name, 800 | fn, 801 | line) 802 | end 803 | end 804 | # sort entries 805 | open($options.tagfile, $options.tagfile_mode) do |out| 806 | out << output.sort!.join 807 | end 808 | end 809 | end 810 | 811 | class XREF_TAGS < TAGS 812 | # Print cross ref file (like ctags -x) 813 | def shipout 814 | output = [] 815 | @files.each do |fn| 816 | if File.directory? fn 817 | puts "\nWarning: #{fn} is a directory - SKIPPING. Specify --recurse option to recurse into directories.\n" 818 | next 819 | end 820 | 821 | size = 0 822 | 823 | $stderr.printf "--\n-- parse file: %s\n", fn if $options.debug 824 | parser = Parser.new(fn) 825 | parser.scan do |tk| 826 | $stderr.print tk, "\n" if $options.debug 827 | line = tk.readed.split(/\n/)[0] 828 | unless $options.xref_only_abs 829 | output.push sprintf("%-16s %-10s %4d %-16s %s\n", 830 | tk.name.gsub(/^::/, "").gsub(" ", "_").gsub(/\n/, '\n'), 831 | tk.def_name, 832 | tk.line_no, 833 | fn, 834 | line) 835 | end 836 | 837 | unless $options.xref_only_base 838 | output.push sprintf("%-16s %-10s %4d %-16s %s\n", 839 | tk.abs_name.gsub(/^::/, "").gsub(" ", "_").gsub(/\n/, '\n'), 840 | tk.def_name, 841 | tk.line_no, 842 | fn, 843 | line) 844 | end 845 | end 846 | end 847 | puts output.join 848 | end 849 | end 850 | 851 | 852 | end 853 | 854 | if (!$options.files_in_list && ARGV.size == 0) or ARGV[0] == '--help' 855 | ARGV.shift 856 | print usage 857 | exit 1 858 | end 859 | 860 | files = ARGV 861 | files += File.readlines($options.files_in_list).map { |f| f.chomp } if $options.files_in_list 862 | 863 | if $options.vi 864 | tags = RTAGS::VI_TAGS.new(files) 865 | elsif $options.xref 866 | tags = RTAGS::XREF_TAGS.new(files) 867 | else 868 | tags = RTAGS::EMACS_TAGS.new(files) 869 | end 870 | tags.shipout 871 | print "\n" if !$options.xref 872 | $stderr.print "\nEnd - tagfile generated\n" if (!$options.quiet && !$options.xref) 873 | -------------------------------------------------------------------------------- /test/data/fileops.rb: -------------------------------------------------------------------------------- 1 | # rtags regression test file (originally by David Powers as part of cfruby) 2 | 3 | module Cfruby 4 | 5 | module FileOps 6 | 7 | # Class variable to control the behavior of FileOps.backup globally 8 | @@backup = true 9 | 10 | # Base class for all FileOperation specific exceptions 11 | class FileOpsError < Cfruby::CfrubyError 12 | end 13 | 14 | # Raised when the requested protocol for a file operation is unknown 15 | class FileOpsUnknownProtocolError < FileOpsError 16 | end 17 | 18 | # Raised when a file operation is attempted on a non-existent file 19 | class FileOpsFileExistError < FileOpsError 20 | end 21 | 22 | # Raised when a move or copy will overwrite a file and :force => false 23 | class FileOpsOverwriteError < FileOpsError 24 | end 25 | 26 | # Raised when a method is called on a file of the wrong type 27 | class FileOpsWrongFiletypeError < FileOpsError 28 | end 29 | 30 | # Interface description for FileCommand interface. Should be 31 | # implemented on a case by case basis and included in the get_protocol 32 | # method. 33 | class FileOps::FileCommand 34 | 35 | # Moves +filename+ to +newfilename+. Options may be set to 36 | # one or more of the following: 37 | # :preserve:: true/false - preserve permissions 38 | # :noop:: true/false - don't actually do anything 39 | # :mode:: permissions - set the permissions of the copied file (uses chmod) 40 | def move(filename, newfilename, options={}) 41 | end 42 | 43 | # Copies +filename+ to +newfilename+. Options may be set to 44 | # one or more of the following: 45 | # :preserve:: true/false - preserve permissions 46 | # :noop:: true/false - don't actually do anything 47 | # :mode:: permissions - set the permissions of the copied file (uses chmod) 48 | def copy(filename, newfilename, options={}) 49 | end 50 | 51 | end 52 | 53 | 54 | # FileCommand interface for local to local operations 55 | class FileOps::LocalFileCommand 56 | 57 | # Options: 58 | # :force:: (defaults to true) force the move 59 | # :mode:: set the mode of +newfilename+ 60 | # :preserve:: attempts to preserve the mode and ownership of newfilename if it exists 61 | # :onlyonchange:: only copy if the file has changed (implies force) 62 | def move(filename, newfilename, options = {}) 63 | if(options[:force] == nil) 64 | options[:force] = true 65 | end 66 | 67 | currentstat = nil 68 | Cfruby.controller.attempt("move #{filename} to #{newfilename}", 'destructive') { 69 | if(options[:onlyonchange] and File.exist?(newfilename)) 70 | options[:force] = true 71 | originalsum = Cfruby::Checksum::Checksum.get_checksums(filename) 72 | newsum = Cfruby::Checksum::Checksum.get_checksums(newfilename) 73 | if(originalsum.sha1 == newsum.sha1) 74 | Cfruby.controller.attempt_abort("files have the same sha1 hash") 75 | end 76 | end 77 | 78 | if(File.exists?(newfilename)) 79 | if(options[:preserve]) 80 | currentstat = File.stat(newfilename) 81 | end 82 | 83 | if(options[:force]) 84 | FileOps.delete(newfilename) 85 | else 86 | raise(FileOpsOverwriteError, "\"#{newfilename}\" already exists") 87 | end 88 | end 89 | FileUtils.mv(filename, newfilename) 90 | 91 | if(currentstat and options[:preserve]) 92 | FileOps.chmod(newfilename, currentstat.mode) 93 | FileOps.chown(newfilename, currentstat.uid, currentstat.gid) 94 | end 95 | 96 | if(options[:mode] != nil) 97 | FileOps.chmod(newfilename, options[:mode]) 98 | end 99 | } 100 | end 101 | 102 | 103 | # Executes FileUtils.cp followed by FileOps.chmod and FileOps.chown (using :user, :group, and :mode). 104 | # If filename is a glob it will be expanded and all resultant filenames will be copied with the assumption 105 | # that newfilename is a directory. 106 | # Options: 107 | # :backup:: true to make a backup of +newfilename+ before copying 108 | # :force:: (defaults to true) force the copy even if newfilename exists 109 | # :onlyonchange:: only copy if the file has changed (implies force) 110 | # :recursive:: recursively copy 111 | def copy(filename, newfilename, options = {}) 112 | # set default options 113 | if(options[:force] == nil) 114 | options[:force] = true 115 | end 116 | if(options[:onlyonchange]) 117 | options[:force] = true 118 | end 119 | 120 | # first, a basic check that filename exists somehow 121 | if(Dir.glob(filename).length == 0) 122 | raise(FileOpsFileExistError, "\"#{filename}\" does not exist") 123 | end 124 | 125 | # get the base directory of the copy 126 | basedir = File.dirname(Pathname.new(Dir.glob(filename)[0]).realpath.to_s) 127 | basedirregex = Regexp.new(Regexp.escape(basedir) + "/?(.*)$") 128 | 129 | # use file find to get a list of files to copy 130 | FileFind.find(filename, options) { |filename| 131 | # copy each file after adjusting for the base directories 132 | basename = basedirregex.match(filename)[1] 133 | if(File.directory?(newfilename)) 134 | copy_single(filename, newfilename + "/#{basename}", options) 135 | else 136 | copy_single(filename, newfilename, options) 137 | end 138 | } 139 | end 140 | 141 | 142 | # Executes FileUtils.cp followed by FileOps.chmod and FileOps.chown (using :user, :group, and :mode). 143 | # filename and newfilename must be single files 144 | # Options: 145 | # :backup:: true to make a backup of +newfilename+ before copying 146 | # :force:: (defaults to true) force the copy even if newfilename exists 147 | # :onlyonchange:: only copy if the file has changed (implies force) 148 | def copy_single(filename, newfilename, options = {}) 149 | mode = options[:mode] 150 | owner = options[:user] 151 | group = options[:group] 152 | options.delete :mode 153 | options.delete :user 154 | options.delete :group 155 | 156 | force = options[:force] 157 | if(force == nil) 158 | force = true 159 | end 160 | 161 | Cfruby.controller.attempt("copy #{filename} to #{newfilename}", 'destructive') { 162 | if(!File.exists?(filename)) 163 | raise(FileOpsFileExistError, "\"#{filename}\" does not exist") 164 | end 165 | if(!force and File.exists?(newfilename)) 166 | raise(FileOpsOverwriteError, "\"#{newfilename}\" already exists") 167 | end 168 | 169 | if(options[:onlyonchange] and File.exist?(newfilename)) 170 | options[:force] = true 171 | originalsum = Cfruby::Checksum::Checksum.get_checksums(filename) 172 | newsum = Cfruby::Checksum::Checksum.get_checksums(newfilename) 173 | if(originalsum.sha1 == newsum.sha1) 174 | Cfruby.controller.attempt_abort("files have the same sha1 hash") 175 | end 176 | end 177 | 178 | if options[:backup] 179 | FileOps.backup(newfilename) if File.exist? newfilename 180 | options.delete :backup 181 | options.delete :onlyonchange 182 | end 183 | 184 | if(File.exists?(newfilename) and force) 185 | FileOps.delete(newfilename) 186 | end 187 | 188 | if(File.directory?(filename)) 189 | FileUtils.mkdir(newfilename) 190 | else 191 | FileUtils.cp(filename, newfilename, :preserve => true) 192 | end 193 | } 194 | 195 | # change ownership and mode if we need to 196 | FileOps.chown(newfilename,owner,group,options) if owner or group 197 | FileOps.chmod(newfilename,mode) if mode 198 | end 199 | 200 | end 201 | 202 | 203 | # FileCommand interface for rsync operations 204 | class FileOps::RsyncFileCommand 205 | 206 | # Options: 207 | # :user:: The user to use on the remote side 208 | # :archive:: Equivilant to -a in the rsync command 209 | # :recursive:: Recursive 210 | # :flags:: Passed directly to the rsync command 211 | def move(filename, newfilename, options = {}) 212 | end 213 | 214 | 215 | # Options: 216 | # :archive:: Equivilant to -a in the rsync command 217 | # :recursive:: Recursive 218 | # :flags:: Passed directly to the rsync command 219 | def copy(filename, newfilename, options = {}) 220 | flags = Array.new() 221 | if(options[:flags]) 222 | flags << options[:flags] 223 | end 224 | 225 | if(options[:archive]) 226 | flags << "-a" 227 | end 228 | 229 | if(options[:recursive]) 230 | flags << "-r" 231 | end 232 | 233 | rsynccommand = "rsync #{flags.join(' ')} #{filename} #{newfilename}" 234 | Cfruby.controller.attempt(rsynccommand, 'destructive', 'unknown') { 235 | Cfruby::Exec.exec(rsynccommand) 236 | } 237 | end 238 | 239 | end 240 | 241 | 242 | # FileCommand interface for http operations 243 | class FileOps::HTTPFileCommand 244 | 245 | def move(filename, newfilename, options = {}) 246 | raise(Exception, "HTTP move not implemented") 247 | end 248 | 249 | # Options: 250 | # :recursive:: Recursive 251 | # :flags:: Passed directly to the rsync command 252 | def copy(filename, targetdir, options = {}) 253 | flags = Array.new() 254 | if(options[:flags]) 255 | flags << options[:flags] 256 | end 257 | 258 | wgetcommand="cd #{targetdir} && " 259 | 260 | if(options[:recursive]) 261 | wgetcommand=wgetcommand + "wget -q -np -nH -r -l inf --cut-dirs=#{filename.split(/\//).length} #{flags} http://#{filename}" 262 | else 263 | wgetcommand=wgetcommand + "wget -q #{flags} http://#{filename}" 264 | end 265 | 266 | Cfruby.controller.attempt(wgetcommand, 'destructive', 'unknown') { 267 | Cfruby::Exec.exec(wgetcommand) 268 | } 269 | end 270 | end 271 | 272 | 273 | # Returns a FileCommand object based on the first protocol it sees 274 | # in either filename or newfilename 275 | def FileOps.get_protocol(filename, newfilename) 276 | protocolregex = /^([a-zA-Z]+):\/\// 277 | protocol = 'file' 278 | 279 | match = protocolregex.match(filename) 280 | if(match == nil) 281 | match = protocolregex.match(newfilename) 282 | end 283 | 284 | if(match != nil) 285 | protocol = match[1] 286 | end 287 | 288 | case(protocol) 289 | when 'file' 290 | return(LocalFileCommand.new()) 291 | when 'rsync' 292 | return(RsyncFileCommand.new()) 293 | when 'http' 294 | return(HTTPFileCommand.new()) 295 | else 296 | raise(FileOpsUnknownProtocolError, "Unknown protocol - \"#{protocol}\"") 297 | end 298 | end 299 | 300 | 301 | # Moves +filename+ to +newfilename+. Options may be set to 302 | # one or more of the following: 303 | # :??????:: anything defined under the protocol specific copy function 304 | def FileOps.move(filename, newfilename, options = {}) 305 | get_protocol(filename, newfilename).move(strip_protocol(filename), strip_protocol(newfilename), options) 306 | end 307 | 308 | 309 | # Copies +filename+ to +newfilename+. Options may be set to 310 | # one or more of the following: 311 | # :??????:: anything defined under the protocol specific copy function 312 | def FileOps.copy(filename, newfilename, options = {}) 313 | get_protocol(filename, newfilename).copy(strip_protocol(filename), strip_protocol(newfilename), options) 314 | end 315 | 316 | 317 | # Create an empty file named +filename+ 318 | # Returns true if the file was created, false otherwise 319 | def FileOps.touch(filename) 320 | created = false 321 | Cfruby.controller.attempt("touch #{filename}") { 322 | if File.exist? filename 323 | # if the file already exists do nothing 324 | Cfruby.controller.attempt_abort("#{filename} already exists - won't create") 325 | else 326 | f = File.new(filename,File::CREAT|File::TRUNC|File::RDWR) 327 | f.close 328 | Cfruby.controller.inform('verbose', "created file #{filename}") 329 | created = true 330 | end 331 | } 332 | 333 | return(created) 334 | end 335 | 336 | 337 | # Alias for delete 338 | def FileOps.unlink(filenamelist) 339 | FileOps.delete(filenamelist) 340 | end 341 | 342 | 343 | # Creates a directory entry. +dirname+ can be an Array or String. 344 | # Options: 345 | # :mode:: mode of the directory 346 | # :user:: user to own the directory 347 | # :group:: group to own the directory 348 | # :makeparent:: make any needed parent directories 349 | # Returns true if a directory was created, false otherwise 350 | def FileOps.mkdir(dirname, options = {}) 351 | if(dirname.kind_of?(String)) 352 | dirname = Array.[](dirname) 353 | end 354 | 355 | created = false 356 | 357 | dirname.each { |d| 358 | Cfruby.controller.attempt("mkdir #{d}", 'destructive') { 359 | if(!File.directory?(d)) 360 | if(options[:makeparent]) 361 | FileUtils.mkdir_p(d) 362 | else 363 | FileUtils.mkdir(d) 364 | end 365 | created = true 366 | mode = options[:mode] 367 | user = options[:user] or Process.euid() 368 | group = options[:group] or Process.egid() 369 | FileOps.chown(d,user,group,options) 370 | FileOps.chmod(d,mode) if mode 371 | else 372 | Cfruby.controller.attempt_abort("#{d} already exists") 373 | end 374 | } 375 | } 376 | 377 | return(created) 378 | end 379 | 380 | 381 | # Remove a directory entry. +dirname+ can be an Array or String. 382 | # Returns true if a directory was removed, false otherwise 383 | def FileOps.rmdir(dirname, force = false) 384 | if(dirname.kind_of?(String) or dirname.kind_of?(Pathname)) 385 | dirname = Array.[](dirname) 386 | end 387 | 388 | deletedsomething = false 389 | dirname.each do | d | 390 | Cfruby.controller.attempt("rmdir #{d}", 'nonreversible', 'destructive') { 391 | if(!test(?e, d)) 392 | Cfruby.controller.attempt_abort("#{d} does not exist") 393 | end 394 | if(test(?d, d)) 395 | if(force) 396 | FileUtils.rm_rf(d) 397 | deletedsomething = true 398 | else 399 | FileUtils.rmdir(d) 400 | deletedsomething = true 401 | end 402 | else 403 | raise(FileOpsWrongFiletypeError, "\"#{d}\" is not a directory") 404 | end 405 | } 406 | end 407 | 408 | return(deletedsomething) 409 | end 410 | 411 | 412 | # Creates a symbolic link +linkfile+ which points to +filename+. 413 | # If +linkfile+ already exists and it is a directory, creates a symbolic link 414 | # +linkfile/filename+. If +linkfile+ already exists and it is not a 415 | # directory, raises FileOpsOverwriteError. Returns true if a link is made 416 | # false otherwise. 417 | # Options: 418 | # :force:: if true, overwrite +linkfile+ even if it already exists 419 | def FileOps.link(filename, linkfile, options={}) 420 | createdlink = false 421 | if !File.exist? filename 422 | raise(FileOpsFileExistError, "filename '#{filename}' does not exist") 423 | else 424 | Cfruby.controller.attempt("link '#{linkfile}' -> '#{filename}'", 'destructive') { 425 | # Use a realpath for the filename - a relative path fails below 426 | filename = Pathname.new(filename).realpath 427 | if(File.exists?(linkfile)) 428 | if(File.symlink?(linkfile) and Pathname.new(linkfile).realpath == filename) 429 | # if the link already exists do nothing 430 | Cfruby.controller.attempt_abort("#{linkfile} already exists as a symlink") 431 | elsif(options[:force]) 432 | unlink(linkfile) 433 | else 434 | raise(FileOpsOverwriteError, "#{linkfile} already exists") 435 | end 436 | end 437 | 438 | FileUtils.ln_s(filename, linkfile) 439 | createdlink = true 440 | } 441 | end 442 | 443 | return(createdlink) 444 | end 445 | 446 | 447 | # Creates an empty file +filenames+ if the file does not already exist. +filenames+ may be 448 | # an Array or String. If the file does exist, the mode and ownership may be adjusted. Returns 449 | # true if a file was created, false otherwise. 450 | def FileOps.create(filenames, owner = Process::Sys.geteuid(), group = Process::Sys.getegid(), mode = 0600) 451 | if(filenames.kind_of?(String)) 452 | filenames = Array.[](filenames) 453 | end 454 | 455 | created = false 456 | filenames.each() { |filename| 457 | Cfruby.controller.attempt("create #{filename}", 'destructive') { 458 | currentumask = File.umask() 459 | begin 460 | if(!test(?f, filename)) 461 | # set a umask that disables all access to the file by default 462 | File.umask(0777) 463 | File.open(filename, File::CREAT|File::WRONLY) { |fp| 464 | } 465 | created = true 466 | end 467 | chmod = FileOps.chmod(filename, mode) 468 | chown = FileOps.chown(filename, owner, group) 469 | if(chmod == false and chown == false) 470 | Cfruby.controller.attempt_abort("\"#{filename}\" exists and has the appropriate owner, group, and mode") 471 | else 472 | created = true 473 | end 474 | ensure 475 | # restore the umask 476 | File.umask(currentumask) 477 | end 478 | } 479 | } 480 | 481 | return(created) 482 | end 483 | 484 | 485 | # Lock a file +fn+, using a lockfile, and return a file handle to +fn+. 486 | # +attr+ are standard file open attributes like 'w'. File based locking is 487 | # used to correctly handle mounted NFS and SMB shares. 488 | def FileOps.flock(fn, attr=nil, ext='.cflock') 489 | Cfruby.controller.attempt("lock #{fn}") { 490 | begin 491 | fnlock = fn+ext 492 | if File.exist? fnlock 493 | Cfruby.controller.inform("warn", "File #{fn} is locked by #{fnlock} (remove to fix) - skipping!") 494 | end 495 | 496 | Cfruby.controller.inform('debug', "locking #{fnlock}") 497 | fl = File.open fnlock,'w' 498 | fl.print "pid=#{Process.pid}\nCfruby lock file" 499 | fl.close 500 | f = File.open fn, attr 501 | 502 | # ---- Update file 503 | yield f 504 | ensure 505 | Cfruby.controller.inform('debug', "unlock #{fnlock}") 506 | File.unlink fnlock if fl 507 | f.close if f 508 | end 509 | } 510 | end 511 | 512 | 513 | # Sets @@backup 514 | def FileOps.set_backup(newbackup) 515 | @@backup = newbackup 516 | end 517 | 518 | # Creates a backup copy of +filename+ with the new filename 519 | # filename_cfruby_yyyymmdd_x, where x increments as more backups 520 | # are added to the same directory. Options: 521 | # :backupdir:: directory to hold the backups (defaults to the same directory as +filename+) 522 | # :onlyonchange:: prevent backup from making a backup if viable backup already exists. 523 | def FileOps.backup(filename, options={}) 524 | if !@@backup 525 | return 526 | end 527 | 528 | Cfruby.controller.attempt("backup #{filename}", 'destructive') { 529 | if(!filename.respond_to?(:dirname)) 530 | filename = Pathname.new(filename.to_s()) 531 | end 532 | 533 | # set the backup directory if it wasn't passed in 534 | backupdir = options[:backupdir] 535 | if(backupdir == nil) 536 | backupdir = filename.dirname() 537 | end 538 | 539 | # find the latest backup file and test the current file against it 540 | # if :onlyonchange is true 541 | if(options[:onlyonchange]) 542 | backupfiles = Dir.glob("#{backupdir}/#{filename.basename()}_[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9]*") 543 | if(backupfiles.length > 0) 544 | lastbackup = backupfiles.sort.reverse()[0] 545 | currentchecksums = Cfruby::Checksum::Checksum.get_checksums(filename) 546 | lastbackupchecksums = Cfruby::Checksum::Checksum.get_checksums(lastbackup) 547 | if(currentchecksums.sha1 == lastbackupchecksums.sha1) 548 | Cfruby.controller.attempt_abort("viable backup already exists \"#{lastbackup}\"") 549 | end 550 | end 551 | end 552 | 553 | tries = 3 554 | numbermatch = /_[0-9]{8}_([0-9]+)$/ 555 | begin 556 | nextnum = -1 557 | 558 | # loop through any existing backup files to get the next number 559 | Dir.[]("#{backupdir}/#{filename.basename()}_#{Time.now.strftime('%Y%m%d')}_*") { |backupfile| 560 | match = numbermatch.match(backupfile) 561 | if(match != nil) 562 | if(match[1].to_i() > nextnum) 563 | nextnum = match[1].to_i() 564 | end 565 | end 566 | } 567 | nextnum = nextnum + 1 568 | 569 | # attempt to open it 570 | success = false 571 | begin 572 | File.open("#{backupdir}/#{filename.basename()}_#{Time.now.strftime('%Y%m%d')}_#{nextnum}", File::RDONLY) 573 | rescue Exception 574 | FileOps.copy(filename, "#{backupdir}/#{filename.basename()}_#{Time.now.strftime('%Y%m%d')}_#{nextnum}") 575 | success = true 576 | end 577 | 578 | if(false == success) 579 | raise(Exception, "Unable to create backup copy of #{filename}") 580 | end 581 | rescue Exception 582 | # we play this game three times just to try to handle possible race 583 | # conditions between the choice of filename and the opening of the file 584 | tries = tries - 1 585 | if(tries < 0) 586 | raise($!) 587 | end 588 | end 589 | } 590 | end 591 | 592 | 593 | # Deletes files that contain no alphanumeric characters. Returns true if any files were deleted 594 | # false otherwise 595 | def FileOps.delete_nonalpha(basedir, options = {}) 596 | deleted = false 597 | Cfruby.controller.attempt("deleting files non-alpha files from \"#{basedir}\"", 'nonreversible', 'destructive') { 598 | if(FileOps.delete_not_matching_regex(basedir, /[a-zA-Z0-9]/)) 599 | deleted = true 600 | end 601 | } 602 | 603 | return(deleted) 604 | end 605 | 606 | 607 | # Deletes matching files. Returns true if a file is actually deleted, false otherwise. 608 | # In addition to the normal find options delete also takes: 609 | # :force:: => (true|false) delete non-empty matching directories 610 | def FileOps.delete(basedir, options = {}) 611 | deletedsomething = false 612 | Cfruby.controller.attempt("deleting files from \"#{basedir}\"", 'nonreversible', 'destructive') { 613 | begin 614 | options[:returnorder] = 'delete' 615 | Cfruby::FileFind.find(basedir, options) { |filename| 616 | if(!filename.symlink?() and filename.directory?()) 617 | FileOps.rmdir(filename, options[:force]) 618 | else 619 | FileOps::SymlinkHandler.unlink(filename) 620 | end 621 | deletedsomething = true 622 | } 623 | rescue Cfruby::FileFind::FileExistError 624 | Cfruby.controller.attempt_abort("#{basedir} does not exist") 625 | end 626 | } 627 | 628 | return(deletedsomething) 629 | end 630 | 631 | 632 | # Changes the owner, group, and mode all at once. Returns true if a change was made to 633 | # owner, group, or mode - false otherwise. If mode==nil it is ignored. 634 | def FileOps.chown_mod(basedir, owner, group, mode, options = {}) 635 | changemade = false 636 | Cfruby.controller.attempt("changing ownership and mode of matching files in \"#{basedir}\"", 'destructive') { 637 | usermanager = Cfruby::OS::OSFactory.new.get_os.get_user_manager() 638 | if(owner and !owner.kind_of?(Integer)) 639 | owner = usermanager.get_uid(owner) 640 | end 641 | if(group and !group.kind_of?(Integer)) 642 | group = usermanager.get_gid(group) 643 | end 644 | 645 | Cfruby::FileFind.find(basedir, options) { |filename| 646 | if(FileOps.chown(filename, owner, group)) 647 | changemade = true 648 | end 649 | if(mode!=nil and FileOps.chmod(filename, mode)) 650 | changemade = true 651 | end 652 | } 653 | } 654 | 655 | return(changemade) 656 | end 657 | 658 | 659 | # Disables matching files by setting all permissions to 0000. Returns true if anything 660 | # was disabled, false otherwise. 661 | def FileOps.disable(basedir, options = {}) 662 | disabled = false 663 | Cfruby.controller.attempt("disabling file in \"#{basedir}\"", 'destructive') { 664 | Cfruby::FileFind.find(basedir, options) { |filename| 665 | if(Cfruby::FileOps.chmod(filename, 0000)) 666 | disabled = true 667 | end 668 | } 669 | } 670 | 671 | return(disabled) 672 | end 673 | 674 | 675 | # Chown's matching files. Returns true if a change was made, false otherwise. 676 | def FileOps.chown(basedir, owner, group=nil, options = {}) 677 | changemade = false 678 | usermanager = Cfruby::OS::OSFactory.new.get_os.get_user_manager() 679 | if(owner and !owner.kind_of?(Integer)) 680 | owner = usermanager.get_uid(owner) 681 | end 682 | if(group and !group.kind_of?(Integer)) 683 | group = usermanager.get_gid(group) 684 | end 685 | 686 | Cfruby::FileFind.find(basedir, options) { |filename| 687 | Cfruby.controller.attempt("changing ownership of \"#{filename}\" to \"#{owner}:#{group}\"", 'destructive') { 688 | currentuid = File.stat(filename).uid 689 | currentgid = File.stat(filename).gid 690 | filename.chown(owner, group) 691 | if(currentuid == File.stat(filename).uid and currentgid == File.stat(filename).gid) 692 | Cfruby.controller.attempt_abort("unchanged, already owned by \"#{owner}:#{group}\"") 693 | end 694 | changemade = true 695 | } 696 | } 697 | 698 | return(changemade) 699 | end 700 | 701 | 702 | # Chmod's matching files. Returns true if a change was made, false otherwise. 703 | def FileOps.chmod(basedir, permissions, options = {}) 704 | changemade = false 705 | 706 | Cfruby::FileFind.find(basedir, options) { |filename| 707 | attemptmessage = "changing permissions of \"#{filename}\" to \"" 708 | if(permissions.kind_of?(Numeric)) 709 | attemptmessage = attemptmessage + sprintf("%o\"", permissions) 710 | else 711 | attemptmessage = attemptmessage + "#{permissions}\"" 712 | end 713 | Cfruby.controller.attempt(attemptmessage, 'destructive') { 714 | currentmode = File.stat(filename).mode() 715 | # try it with internal functions, but try to call chmod if we have to 716 | if(permissions.kind_of?(Numeric)) 717 | FileUtils.chmod(permissions, filename) 718 | else 719 | output = Cfruby::Exec.exec("chmod '" + permissions.to_s.gsub(/'/, "\\\&") + "' '" + filename.realpath.to_s.gsub(/'/, "\\\&") + "'") 720 | if(output[1].length > 0) 721 | raise(FileOpsError, output.join("\n")) 722 | end 723 | end 724 | 725 | if(currentmode == File.stat(filename).mode()) 726 | Cfruby.controller.attempt_abort("unchanged, already set to \"#{permissions}\"") 727 | else 728 | changemade = true 729 | end 730 | } 731 | } 732 | 733 | return(changemade) 734 | end 735 | 736 | 737 | # Methods for standard operations involving symbolic links 738 | module FileOps::SymlinkHandler 739 | 740 | # Returns File.stat unless it is a symbolic link not pointing 741 | # to an existing file - in that case it returns File.lstat 742 | def SymlinkHandler.stat(filename) 743 | if(!filename.kind_of?(Pathname)) 744 | filename = Pathname.new(filename.to_s) 745 | end 746 | 747 | if(filename.symlink? and broken?(filename)) 748 | return File.lstat(filename) 749 | end 750 | 751 | return(File.stat(filename)) 752 | end 753 | 754 | # the stdlib Pathname.unlink balks when removing a symlink - 755 | # this method will call File.unlink instead when dealing with 756 | # a symlink 757 | def SymlinkHandler.unlink(filename) 758 | if(!filename.kind_of?(Pathname)) 759 | filename = Pathname.new(filename.to_s) 760 | end 761 | 762 | if filename.symlink?() 763 | File.unlink filename.expand_path 764 | else 765 | filename.unlink() 766 | end 767 | end 768 | 769 | # Returns true if a file is a broken symlink 770 | def SymlinkHandler.broken?(symlink) 771 | if(!symlink.kind_of?(Pathname)) 772 | symlink = Pathname.new(symlink.to_s) 773 | end 774 | 775 | if(!symlink.symlink?()) 776 | return(false) 777 | end 778 | 779 | # expand the path and catch the ensuing error in the case of a broken link 780 | begin 781 | symlink.realpath() 782 | rescue 783 | if($!.kind_of?(Errno::ENOENT) and $!.to_s =~ /^no such file/i) 784 | return(true) 785 | else 786 | raise($!) 787 | end 788 | end 789 | 790 | return(false) 791 | end 792 | 793 | # Returns whether a symlink is actually pointing to +filename+. 794 | # Both parameters may be strings or File objects. This method 795 | # is used by Cfenjin to ascertain that when a symlink exists it 796 | # points to the right file. It returns false when +filename+ 797 | # does not exist (i.e. symlink points to nothing). 798 | # 799 | # In the case the symlink does not exist a FileOpsWrongFiletypeError 800 | # is thrown. 801 | def SymlinkHandler.points_to?(symlink, filename) 802 | if(!filename.kind_of?(Pathname)) 803 | filename = Pathname.new(filename.to_s) 804 | end 805 | 806 | if(!symlink.kind_of?(Pathname)) 807 | symlink = Pathname.new(symlink.to_s) 808 | end 809 | 810 | return false if !filename.exist? 811 | raise FileOpsWrongFiletypeError if !symlink.symlink? 812 | 813 | return filename.realpath.to_s == symlink.realpath.to_s 814 | end 815 | 816 | end 817 | 818 | 819 | private 820 | 821 | def FileOps.strip_protocol(filename) 822 | return(filename.to_s[/^([a-zA-Z]+:\/\/)?(.*)$/,2]) 823 | end 824 | 825 | end 826 | 827 | end 828 | --------------------------------------------------------------------------------