├── 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 |
--------------------------------------------------------------------------------