portvcs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,104 @@
1
+ =Name
2
+
3
+ portvcs--FreeBSD ports commits history viewer that doesn't require
4
+ neither local ports tree nor CVS checkouts.
5
+
6
+
7
+ ==Synopsis
8
+
9
+ portvcs [options] category/port
10
+
11
+
12
+ ==Description
13
+
14
+ The portvcs utility connects to a remote VCS (Version Control System)
15
+ server and grabs the history of commits.
16
+
17
+ At the time of writing FreeBSD uses CVS (jeez) for its ports collection,
18
+ so portvcs (with default configuration) connects to well-known French
19
+ CVS server via TCP port 2401 and reads the logs of a particular port.
20
+ By default the utility constrains the history to 1 year but that's easy
21
+ changeable via +-d+ CL (command line) option.
22
+
23
+ portvcs isn't restricted to FreeBSD ports--it can read logs from
24
+ (probably) any CVS server but in that case its usefulness is very
25
+ doubtful.
26
+
27
+ The options are as follows:
28
+
29
+ --ports-tree-local DIR:: Set this if you have the ports tree
30
+ directory in an unusual place.
31
+
32
+ --config-dirs:: List all possible locations for the
33
+ configuration file. The first found wins.
34
+
35
+ --config NAME:: The name of the configuration file. If
36
+ it contains +/+ in it, the list from
37
+ <tt>\--config-dirs</tt> is ignored.
38
+
39
+ -d EXP:: Passes the EXP to VCS server to set limits
40
+ for the logs. Usually it's something like
41
+ '2 months ago<'. For all possible values,
42
+ read the help for the option +-d+ in cvs
43
+ help for its log command, e.g, type:
44
+
45
+ % info -n '(cvs)log options'
46
+
47
+ --host STR:: A hostname of a remote server.
48
+
49
+ --port N:: A port number (default is 2401) of the
50
+ remote server.
51
+
52
+ --user STR:: A user name on the remote server.
53
+
54
+ --pass STR:: A password for the user on the remote
55
+ server.
56
+
57
+ --cvsroot DIR:: CVSROOT on a remote server.
58
+
59
+ --ports-tree DIR:: A directory that follows CVSROOT and serves
60
+ as a ports tree. For example, CVSROOT can
61
+ be +/home/ncvs+ and the ports directory can
62
+ be +ports+ or may be empty at all.
63
+
64
+ --vcs-version:: Just get version of a remote VCS server and
65
+ exit.
66
+
67
+ --cvs-pass-scramble STR:: A handy option for encrypting...
68
+ --cvs-pass-descramble STR:: ... and decrypting CVS passwords.
69
+
70
+ -V:: Show portvcs version and exit.
71
+
72
+ -v:: Be more verbose. You can supply it several
73
+ times, viz. +-vv+ dumps even more debug info.
74
+
75
+ ==Configuration
76
+
77
+ portvcs looks for its configuration at 3 places at start up.
78
+
79
+ 1. At <tt>PORTVCS_CONF</tt> env variable. (Its format is exactly similar
80
+ to CL options.)
81
+
82
+ 2. At configuration file. Its default name is <tt>portvcs.yaml</tt> and
83
+ it can be stored in several system directories which are observable
84
+ by <tt>--config--dirs</tt> CL option.
85
+
86
+ 3. At command line.
87
+
88
+ Higher number levels overrides the values from lower number levels.
89
+
90
+ The configuration file must be in YAML format. Look into <tt>`gem env
91
+ gemdir`/gems/portvcs-x.y.z/etc/</tt> directory for samples.
92
+
93
+
94
+ ==Examples
95
+
96
+ % portvcs www/firefox
97
+ % portvcs /usr/ports/www/firefox
98
+ % portvcs www/firefox/files/patch-configure.in
99
+
100
+ % pwd
101
+ /usr/ports/x11-toolkits/gtk20
102
+ % portvcs Makefile
103
+ % portvcs files/patch-gtk_gtksignal.h
104
+ % portvcs -d '3 month ago<' .
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ # -*-ruby-*-
2
+
3
+ require 'rake'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/clean'
6
+ require 'rake/rdoctask'
7
+ require 'rake/testtask'
8
+
9
+ spec = Gem::Specification.new() {|i|
10
+ i.name = "portvcs"
11
+ i.version = `bin/#{i.name} -V`
12
+ i.summary = "FreeBSD ports commits history viewer that doesn't require neither local ports tree nor CVS checkouts."
13
+ i.author = 'Alexander Gromnitsky'
14
+ i.email = 'alexander.gromnitsky@gmail.com'
15
+ i.homepage = "http://github.com/gromnitsky/#{i.name}"
16
+ i.platform = Gem::Platform::RUBY
17
+ i.required_ruby_version = '>= 1.9.2'
18
+ i.files = FileList['lib/**/*', 'bin/*', 'doc/*', 'etc/*', '[A-Z]*', 'test/**/*']
19
+
20
+ i.executables = FileList['bin/*'].gsub(/^bin\//, '')
21
+ i.default_executable = i.name
22
+
23
+ i.test_files = FileList['test/test_*.rb']
24
+
25
+ i.rdoc_options << '-m' << 'doc/README.rdoc'
26
+ i.extra_rdoc_files = FileList['doc/*']
27
+
28
+ i.add_development_dependency('open4', '>= 1.0.1')
29
+ }
30
+
31
+ Rake::GemPackageTask.new(spec).define()
32
+
33
+ task(default: %(repackage))
34
+
35
+ Rake::RDocTask.new('doc') {|i|
36
+ i.main = 'doc/README.rdoc'
37
+ i.rdoc_files = FileList['doc/*', 'lib/**/*.rb']
38
+ i.rdoc_files.exclude("lib/**/plugins")
39
+ }
40
+
41
+ Rake::TestTask.new() {|i|
42
+ i.test_files = FileList['test/test_*.rb']
43
+ }
data/bin/portvcs ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env ruby
2
+ # -*-ruby-*-
3
+
4
+ require 'date'
5
+ require 'optparse'
6
+ require 'yaml'
7
+ require 'shellwords.rb'
8
+
9
+ require_relative '../lib/portvcs/portvcs.rb'
10
+ require_relative '../lib/portvcs/utils.rb'
11
+
12
+ include PortVCS
13
+
14
+ $conf = {
15
+ verbose: 0,
16
+ banner: "Usage: #{File.basename($0)} [options] category/port",
17
+
18
+ vcs: 'cvs',
19
+ vcs_legal: ['cvs'], # TODO: get this array automatically
20
+ host: '',
21
+ port: 2401,
22
+ user: '',
23
+ cvsroot: '',
24
+ pass: '',
25
+ ports_tree: '',
26
+
27
+ date_cond: "#{(DateTime.now - 366).strftime('%d %b %Y')}<",
28
+
29
+ config: 'portvcs.yaml',
30
+ config_dirs: [File.absolute_path("#{File.dirname(File.expand_path($0))}/../etc"),
31
+ '/etc', '/usr/etc', '/usr/local/etc',
32
+ "#{Gem.dir}/gems/#{Meta::NAME}-#{Meta::VERSION}/etc"],
33
+
34
+ mode: 'log',
35
+ ports_tree_local: '/usr/ports'
36
+ }
37
+
38
+ def veputs(level, t)
39
+ puts(t) if $conf[:verbose] >= level
40
+ end
41
+
42
+ def errx(ec, t)
43
+ STDERR.puts File.basename($0) + ' error: ' + t.to_s
44
+ exit ec if ec > 0
45
+ end
46
+
47
+ def cl_parse(myargs, times = 1)
48
+ o = OptionParser.new()
49
+ o.banner = $conf[:banner]
50
+ o.on('-V', 'Show the program version') {
51
+ puts Meta::VERSION
52
+ exit 0
53
+ }
54
+ o.on('--cvs-pass-scramble STR', 'Handy encoding of CVS passwords') { |v|
55
+ puts CVSDumb.pass_scramble(v)
56
+ exit 0
57
+ }
58
+ o.on('--cvs-pass-descramble STR') { |v|
59
+ puts CVSDumb.pass_descramble(v)
60
+ exit 0
61
+ }
62
+ o.on('--config NAME', "Set a config name (default is #{$conf[:config]})") {|v|
63
+ $conf[:config] = v
64
+ }
65
+ o.on('--config-dirs', 'Show possible config locations') {
66
+ $conf[:config_dirs].each { |i|
67
+ f = i + '/' + $conf[:config]
68
+ puts (File.readable?(f) ? '* ' : ' ') + f
69
+ }
70
+ exit 0
71
+ }
72
+ o.on('--ports-tree-local DIR', "(Default is #{$conf[:ports_tree_local]})") { |v| $conf[:ports_tree_local] = v }
73
+ o.on('-v', 'Be more verbose') {
74
+ # protect variable from incrementing in a second parse round
75
+ $conf[:verbose] += 1 if times <= 1
76
+ }
77
+ o.on('--host STR') { |v| $conf[:host] = v }
78
+ o.on('--port N', Integer, "(default is #{$conf[:port]})") { |v| $conf[:port] = v }
79
+ o.on('--user STR') { |v| $conf[:user] = v }
80
+ o.on('--pass STR') { |v| $conf[:pass] = v }
81
+ o.on('--ports-tree DIR') { |v| $conf[:ports_tree] = v }
82
+ o.on('--cvsroot DIR') { |v| $conf[:cvsroot] = v }
83
+ o.on('-d EXP', "Date condition, for example '1 Jan 2010<'") { |v| $conf[:date_cond] = v }
84
+ o.on('--vcs TYPE', $conf[:vcs_legal], "Select version control system (#{$conf[:vcs_legal].join(', ')})") { |v| $conf[:vcs] = v }
85
+ o.on('--vcs-version', 'Get version of a remote VCS server') { $conf[:mode] = 'version' }
86
+
87
+ begin
88
+ o.parse!(myargs)
89
+ rescue
90
+ errx(1, $!)
91
+ end
92
+ end
93
+
94
+ def pager_setup
95
+ [ENV['PAGER'], 'less', 'more'].compact.uniq.each {|i|
96
+ next unless Utils.in_path?(i.split.first)
97
+ io = IO.popen(i, "w") rescue next
98
+ next if $? and $?.exited? # pager didn't work
99
+ veputs(2, "Pager: #{i}")
100
+ return io
101
+ }
102
+ return nil
103
+ end
104
+
105
+
106
+ # --[ main ]------------------------------------------------------------
107
+
108
+ # 1. parse env var
109
+ if ENV.key?(Meta::NAME.upcase + '_CONF')
110
+ cl_parse ENV[Meta::NAME.upcase + '_CONF'].shellsplit
111
+ end
112
+
113
+ # 2. parse CL in case of --config option
114
+ argv1 = ARGV.dup
115
+ cl_parse argv1
116
+
117
+ # 3. load config & final CL parse
118
+ begin
119
+ r = Utils.config_load($conf, $conf[:config], $conf[:config_dirs])
120
+ rescue
121
+ errx(1, "cannot load config: #{$!}")
122
+ end
123
+ veputs(1, "Loaded config: #{r}")
124
+ cl_parse ARGV, 2
125
+
126
+ abort($conf[:banner]) if (ARGV.size == 0 || ARGV[0] =~ /^\s*$/) && $conf[:mode] == 'log'
127
+
128
+ # print our env
129
+ if $conf[:verbose] >= 1
130
+ puts 'Libs dir: '+Utils.gem_libdir
131
+ pp $conf
132
+ end
133
+
134
+ # 4. connect to remote VCS
135
+ v = VCS.new($conf[:vcs])
136
+ begin
137
+ v.open(debug: ($conf[:verbose] >= 2 ? true : false), host: $conf[:host], cvsroot: $conf[:cvsroot],
138
+ port: $conf[:port], user: $conf[:user], pass: $conf[:pass],
139
+ ports_tree: $conf[:ports_tree]) { |vp|
140
+
141
+ case $conf[:mode]
142
+ when 'version'
143
+ puts vp.version
144
+ else
145
+ if !(p = Utils.port_extract_name(ARGV[0].dup.strip, $conf[:ports_tree_local]))
146
+ errx(1, "invalid port name")
147
+ end
148
+ p[1] = 'Makefile' if !p[1]
149
+
150
+ out = pager_setup() || STDOUT # get output stream
151
+ vp.log(p[0], p[1], $conf[:date_cond]) {|i|
152
+ begin
153
+ out.puts i
154
+ rescue Errno::EPIPE
155
+ out = STDOUT
156
+ retry
157
+ end
158
+ }
159
+ out.close unless out == STDOUT
160
+ end
161
+ }
162
+ rescue
163
+ errx(1, $!)
164
+ end
data/doc/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2010 Alexander Gromnitsky.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/doc/README.rdoc ADDED
@@ -0,0 +1,104 @@
1
+ =Name
2
+
3
+ portvcs--FreeBSD ports commits history viewer that doesn't require
4
+ neither local ports tree nor CVS checkouts.
5
+
6
+
7
+ ==Synopsis
8
+
9
+ portvcs [options] category/port
10
+
11
+
12
+ ==Description
13
+
14
+ The portvcs utility connects to a remote VCS (Version Control System)
15
+ server and grabs the history of commits.
16
+
17
+ At the time of writing FreeBSD uses CVS (jeez) for its ports collection,
18
+ so portvcs (with default configuration) connects to well-known French
19
+ CVS server via TCP port 2401 and reads the logs of a particular port.
20
+ By default the utility constrains the history to 1 year but that's easy
21
+ changeable via +-d+ CL (command line) option.
22
+
23
+ portvcs isn't restricted to FreeBSD ports--it can read logs from
24
+ (probably) any CVS server but in that case its usefulness is very
25
+ doubtful.
26
+
27
+ The options are as follows:
28
+
29
+ --ports-tree-local DIR:: Set this if you have the ports tree
30
+ directory in an unusual place.
31
+
32
+ --config-dirs:: List all possible locations for the
33
+ configuration file. The first found wins.
34
+
35
+ --config NAME:: The name of the configuration file. If
36
+ it contains +/+ in it, the list from
37
+ <tt>\--config-dirs</tt> is ignored.
38
+
39
+ -d EXP:: Passes the EXP to VCS server to set limits
40
+ for the logs. Usually it's something like
41
+ '2 months ago<'. For all possible values,
42
+ read the help for the option +-d+ in cvs
43
+ help for its log command, e.g, type:
44
+
45
+ % info -n '(cvs)log options'
46
+
47
+ --host STR:: A hostname of a remote server.
48
+
49
+ --port N:: A port number (default is 2401) of the
50
+ remote server.
51
+
52
+ --user STR:: A user name on the remote server.
53
+
54
+ --pass STR:: A password for the user on the remote
55
+ server.
56
+
57
+ --cvsroot DIR:: CVSROOT on a remote server.
58
+
59
+ --ports-tree DIR:: A directory that follows CVSROOT and serves
60
+ as a ports tree. For example, CVSROOT can
61
+ be +/home/ncvs+ and the ports directory can
62
+ be +ports+ or may be empty at all.
63
+
64
+ --vcs-version:: Just get version of a remote VCS server and
65
+ exit.
66
+
67
+ --cvs-pass-scramble STR:: A handy option for encrypting...
68
+ --cvs-pass-descramble STR:: ... and decrypting CVS passwords.
69
+
70
+ -V:: Show portvcs version and exit.
71
+
72
+ -v:: Be more verbose. You can supply it several
73
+ times, viz. +-vv+ dumps even more debug info.
74
+
75
+ ==Configuration
76
+
77
+ portvcs looks for its configuration at 3 places at start up.
78
+
79
+ 1. At <tt>PORTVCS_CONF</tt> env variable. (Its format is exactly similar
80
+ to CL options.)
81
+
82
+ 2. At configuration file. Its default name is <tt>portvcs.yaml</tt> and
83
+ it can be stored in several system directories which are observable
84
+ by <tt>--config--dirs</tt> CL option.
85
+
86
+ 3. At command line.
87
+
88
+ Higher number levels overrides the values from lower number levels.
89
+
90
+ The configuration file must be in YAML format. Look into <tt>`gem env
91
+ gemdir`/gems/portvcs-x.y.z/etc/</tt> directory for samples.
92
+
93
+
94
+ ==Examples
95
+
96
+ % portvcs www/firefox
97
+ % portvcs /usr/ports/www/firefox
98
+ % portvcs www/firefox/files/patch-configure.in
99
+
100
+ % pwd
101
+ /usr/ports/x11-toolkits/gtk20
102
+ % portvcs Makefile
103
+ % portvcs files/patch-gtk_gtksignal.h
104
+ % portvcs -d '3 month ago<' .
data/doc/TODO ADDED
@@ -0,0 +1,2 @@
1
+ + pager
2
+ - turn on gzip in cvs protocol
@@ -0,0 +1,7 @@
1
+ ---
2
+ :host: anoncvs.fr.FreeBSD.org
3
+ :port: 2401
4
+ :user: anoncvs
5
+ :pass: anoncvs
6
+ :cvsroot: /home/ncvs
7
+ :ports_tree: ports
data/etc/portvcs.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ :host: anoncvs.fr.FreeBSD.org
3
+ :port: 2401
4
+ :user: anoncvs
5
+ :pass: anoncvs
6
+ :cvsroot: /home/ncvs
7
+ :ports_tree: ports
@@ -0,0 +1,6 @@
1
+ module PortVCS
2
+ module Meta
3
+ NAME = 'portvcs'
4
+ VERSION = '0.0.1'
5
+ end
6
+ end
@@ -0,0 +1,180 @@
1
+ require 'socket'
2
+
3
+ # :include: ../../README.rdoc
4
+ module PortVCS
5
+
6
+ # A wrapper for *Dumb classes
7
+ class VCS
8
+
9
+ def self.select(type)
10
+ case type
11
+ when 'cvs'
12
+ return CVSDumb
13
+ else
14
+ fail "unknown VCS '#{type}'"
15
+ end
16
+ end
17
+
18
+ # sys -- a class like CVSDumb
19
+ def initialize(sys)
20
+ @sys = VCS.select(sys)
21
+ end
22
+
23
+ attr_accessor :sys
24
+
25
+ def open(p)
26
+ s1 = s2 = @sys.new(p)
27
+ if block_given?
28
+ begin
29
+ s1 = yield(s2)
30
+ rescue
31
+ fail $!
32
+ ensure
33
+ s2.close
34
+ end
35
+ end
36
+
37
+ return s1
38
+ end
39
+
40
+ end # VCS
41
+
42
+ class CVSDumb
43
+ # debug print prefixes
44
+ CLIENT = 'C: '
45
+ SERVER = 'S: '
46
+
47
+ PASSPAIR = {
48
+ ?! => 120, ?" => 53, ?% => 109, ?& => 72, ?' => 108, ?( => 70, ?) => 64,
49
+ ?* => 76, ?+ => 67, ?, => 116, ?- => 74, ?. => 68, ?/ => 87, ?0 => 111,
50
+ ?1 => 52, ?2 => 75, ?3 => 119, ?4 => 49, ?5 => 34, ?6 => 82, ?7 => 81,
51
+ ?8 => 95, ?9 => 65, ?: => 112, ?; => 86, ?< => 118, ?= => 110, ?> => 122,
52
+ ?? => 105, ?A => 57, ?B => 83, ?C => 43, ?D => 46, ?E => 102, ?F => 40,
53
+ ?G => 89, ?H => 38, ?I => 103, ?J => 45, ?K => 50, ?L => 42, ?M => 123,
54
+ ?N => 91, ?O => 35, ?P => 125, ?Q => 55, ?R => 54, ?S => 66, ?T => 124,
55
+ ?U => 126, ?V => 59, ?W => 47, ?X => 92, ?Y => 71, ?Z => 115, ?_ => 56,
56
+ ?a => 121, ?b => 117, ?c => 104, ?d => 101, ?e => 100, ?f => 69, ?g => 73,
57
+ ?f => 69, ?h => 99, ?i => 63, ?j => 94, ?k => 93, ?l => 39, ?m => 37,
58
+ ?n => 61, ?o => 48, ?p => 58, ?q => 113, ?r => 32, ?s => 90, ?t => 44,
59
+ ?u => 98, ?v => 60, ?w => 51, ?x => 33, ?y => 97, ?z => 62
60
+ }
61
+
62
+ def self.pass_scramble(t)
63
+ r = ?A
64
+ t.each_char {|i|
65
+ fail "invalid char in pass: #{i}" if ! PASSPAIR.key? i
66
+ r << PASSPAIR[i].chr
67
+ }
68
+ return r
69
+ end
70
+
71
+ # the algorithm is symmetric
72
+ def self.pass_descramble(t)
73
+ pass_scramble(t)[2..-1]
74
+ end
75
+
76
+ # p is a hash
77
+ def initialize(p)
78
+ @host = p[:host]
79
+ @port = p[:port]
80
+ @user = p[:user]
81
+ @pass = p[:pass]
82
+ @cvsroot = p[:cvsroot].strip
83
+ @debug = p[:debug]
84
+ @ports_tree = p[:ports_tree].strip
85
+
86
+ @client = TCPSocket.open(@host, @port)
87
+ request("BEGIN AUTH REQUEST")
88
+ request(@cvsroot)
89
+ request(@user)
90
+ request(CVSDumb.pass_scramble(@pass))
91
+ request("END AUTH REQUEST")
92
+ fail "cannot auth to #{@user}@#{@host}" if getline != 'I LOVE YOU'
93
+ end
94
+
95
+ attr_accessor :debug
96
+
97
+ def version
98
+ request('version')
99
+ request('noop')
100
+ r = getline_text
101
+ getline # for 'noop'
102
+ return r
103
+ end
104
+
105
+ def log(dir, file = nil, date_cond = nil)
106
+ request("Root #{@cvsroot}")
107
+ request("Directory .")
108
+ request("#{@cvsroot}/#{@ports_tree.to_s == '' ? '' : @ports_tree + '/'}#{dir}")
109
+ request("Argument -N")
110
+ if date_cond
111
+ request("Argument -d")
112
+ request("Argument #{date_cond}")
113
+ end
114
+ request("Argument #{file}") if file != nil
115
+ request('log')
116
+
117
+ getmultilines_text {|i| yield i }
118
+ end
119
+
120
+ def close
121
+ @client.close
122
+ end
123
+
124
+
125
+ protected
126
+
127
+ # debug print
128
+ def dputs(type, t)
129
+ puts "#{type}#{t}" if debug
130
+ end
131
+
132
+ def getline
133
+ r = @client.gets.strip
134
+ dputs(SERVER, r)
135
+ return r
136
+ end
137
+
138
+ def getline_text
139
+ @client
140
+ r = getline
141
+ if r.match(/^M (.+)/)
142
+ getline # read final 'ok\n'
143
+ return $1
144
+ else
145
+ fail "protocol error (unexpected string): #{r}"
146
+ end
147
+ end
148
+
149
+ def getmultilines_text
150
+ is_error = false
151
+ e = []
152
+ while line = @client.gets.strip
153
+ break if line == 'ok'
154
+ if line == 'error'
155
+ is_error = true
156
+ break
157
+ end
158
+
159
+ if (line =~ /^(M|E) ?(.*)/)
160
+ if $1 == 'E'
161
+ e << $2
162
+ else
163
+ yield $2
164
+ end
165
+ else
166
+ fail "protocol error (unexpected string): #{line}"
167
+ end
168
+ end
169
+
170
+ fail "protocol error: " + e.join(', ') if is_error
171
+ end
172
+
173
+ def request(t)
174
+ @client.send("#{t}\n", 0)
175
+ dputs(CLIENT, t)
176
+ end
177
+
178
+ end # CVSDumb
179
+
180
+ end # module
@@ -0,0 +1,88 @@
1
+ require 'pp'
2
+ require_relative 'meta'
3
+
4
+ module PortVCS
5
+
6
+ class Utils
7
+
8
+ # load config file immediately if it contains '/' in its name,
9
+ # otherwise search through several dirs for it.
10
+ #
11
+ # conf -- a hash to merge result with
12
+ #
13
+ # return a loaded filename or nil on error
14
+ def self.config_load(conf, file, dirs)
15
+ p = ->(f) {
16
+ if File.readable?(f)
17
+ begin
18
+ myconf = YAML.load_file(f)
19
+ rescue
20
+ abort("cannot parse #{f}: #{$!}")
21
+ end
22
+ %w(host port user pass cvsroot ports_tree).each { |i|
23
+ fail "missing or nil '#{i}' in #{f}" if ! myconf.key?(i.to_sym) || ! myconf[i.to_sym]
24
+ }
25
+ conf.merge!(myconf)
26
+ return file
27
+ end
28
+ return nil
29
+ }
30
+
31
+ if file.index('/')
32
+ return p.call(file)
33
+ else
34
+ dirs.each {|dir| return dir+'/'+file if p.call(dir + '/' + file) }
35
+ end
36
+
37
+ return nil
38
+ end
39
+
40
+ # p -- a string to examine
41
+ # ptree -- a directory name with a local port tree, for example "/usr/ports"
42
+ def self.port_extract_name(t, ptree)
43
+ return nil if t.to_s == '' || (! t.index('/') && Dir.pwd !~ /^#{ptree}/)
44
+
45
+ t.sub!(/#{ptree}(\/*)?/, '') if t =~ /^#{ptree}/
46
+ return nil if t[0] == '/'
47
+
48
+ # idempotent lambda
49
+ inptree = ->() {
50
+ if Dir.pwd =~ /^#{ptree}/
51
+ l = (d = Dir.pwd.sub(/#{ptree}(\/*)?/, '')).split('/').length
52
+ return File.split(d + '/' + t) if l >= 2 && l <= 3
53
+ end
54
+ }
55
+
56
+ case t.split('/').length
57
+ when 1
58
+ return inptree.call
59
+ when 2
60
+ # assuming files/patch-aa in /usr/ports/www/firefox/files/
61
+ return inptree.call if inptree.call
62
+ # assuming www/firefox
63
+ return [t]
64
+ when 3..4
65
+ # assuming www/firefox/Makefile
66
+ return File.split(t)
67
+ end
68
+
69
+ return nil
70
+ end
71
+
72
+ def self.gem_libdir
73
+ t = ["#{File.dirname(File.expand_path($0))}/../lib/#{PortVCS::Meta::NAME}",
74
+ "#{Gem.dir}/gems/#{PortVCS::Meta::NAME}-#{PortVCS::Meta::VERSION}/lib/#{PortVCS::Meta::NAME}"]
75
+ t.each {|i| return i if File.readable?(i) }
76
+ fail "both paths are invalid: #{t}"
77
+ end
78
+
79
+ def self.in_path?(file)
80
+ return true if file =~ %r%\A/% and File.exist? file
81
+
82
+ ENV['PATH'].split(File::PATH_SEPARATOR).any? do |path|
83
+ File.exist? File.join(path, file)
84
+ end
85
+ end
86
+
87
+ end # Utils
88
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,45 @@
1
+ # various staff for minitest
2
+
3
+ require 'fileutils'
4
+ require 'open4'
5
+
6
+ include FileUtils
7
+
8
+ require_relative '../lib/portvcs/utils'
9
+ include PortVCS
10
+
11
+ # don't run tests automatically if they were invoked as 'gem check -t ...'
12
+ if $0 =~ /gem/
13
+ require 'minitest/unit'
14
+ else
15
+ require 'minitest/autorun'
16
+ end
17
+
18
+ def cmd_run(cmd)
19
+ so = sr = ''
20
+ status = Open4::popen4(cmd) { |pid, stdin, stdout, stderr|
21
+ so = stdout.read
22
+ sr = stderr.read
23
+ }
24
+ [status.exitstatus, sr, so]
25
+ end
26
+
27
+ # return the right directory for (probably executable) _c_
28
+ def cmd(c)
29
+ case File.basename(Dir.pwd)
30
+ when Meta::NAME.downcase
31
+ # test probably is executed from the Rakefile
32
+ Dir.chdir('test')
33
+ when 'test'
34
+ # we are in the test directory, there is nothing special to do
35
+ else
36
+ # tests were invoked by 'gem check -t bwkfanboy'
37
+ begin
38
+ Dir.chdir(Utils.gem_libdir + '/../../test')
39
+ rescue
40
+ raise "running tests from '#{Dir.pwd}' isn't supported: #{$!}"
41
+ end
42
+ end
43
+
44
+ '../bin/' + c
45
+ end
File without changes
File without changes
File without changes
@@ -0,0 +1,2 @@
1
+ # "There is no way to do CVS right."
2
+ # -- Linus Torvalds.
@@ -0,0 +1,7 @@
1
+ ---
2
+ :host: 127.0.0.1
3
+ :port: 2401
4
+ :user: anonymous
5
+ :pass: anoncvs
6
+ :cvsroot: /usr/local/cvsroot
7
+ :ports_tree: ''
@@ -0,0 +1,7 @@
1
+ ---
2
+ :host: 127.0.0.1
3
+ :port:
4
+ :user: anonymous
5
+ :pass: anoncvs
6
+ :cvsroot: /usr/local/cvsroot
7
+ :ports_tree: ''
@@ -0,0 +1,39 @@
1
+ require_relative 'helper'
2
+
3
+ class TestCVSDumb < MiniTest::Unit::TestCase
4
+ CMD = 'portvcs'
5
+ CONFIG = 'semis/localhost_cvs.yaml'
6
+ MSG = 'This test requires a running CVS server on localhost with auth credentials
7
+ listed in semis/localhost_cvs.yaml file'
8
+
9
+ def setup
10
+ cmd 'foobar' # cd to tests directory
11
+
12
+ @skip_log_of_the_something = true
13
+ # this probably won't work on your machine
14
+ if ENV.key?('CVSROOT').to_s !~ /^\s*$/
15
+ cd 'semis/cvstest'
16
+ cmd_run("rm -rf '" + ENV['CVSROOT']+'/test/portvcs' + "'")
17
+ r = cmd_run("cvs import -m 'Huh?' test foo bar")
18
+ if r[0] != 0
19
+ warn("cannot import a test file into #{ENV['CVSROOT']}: "+r[1..-1].join('; '))
20
+ else
21
+ @skip_log_of_the_something = false
22
+ end
23
+ cd '../..'
24
+ end
25
+ end
26
+
27
+ def test_version
28
+ r = cmd_run("#{cmd CMD} --config #{CONFIG} --vcs-version")
29
+ assert_equal(0, r[0], MSG)
30
+ assert_match(/Concurrent Versions System \(CVS\) 1.11/, r[2])
31
+ end
32
+
33
+ def test_log_of_the_something
34
+ skip('cvsroot on localhost is not ready') if @skip_log_of_the_something
35
+ r = cmd_run("#{cmd CMD} --config #{CONFIG} test/portvcs")
36
+ assert_equal(0, r[0], MSG)
37
+ refute_equal(0, r[2].size)
38
+ end
39
+ end
@@ -0,0 +1,68 @@
1
+ require_relative 'helper'
2
+
3
+ class TestUtils < MiniTest::Unit::TestCase
4
+ def setup
5
+ cmd 'foobar' # cd to tests directory
6
+ @ptree = Dir.pwd + '/' + 'ports'
7
+ end
8
+
9
+ def test_name_resolution
10
+ save = Dir.pwd
11
+
12
+ assert_equal(nil, Utils.port_extract_name(nil, @ptree))
13
+ assert_equal(nil, Utils.port_extract_name('', nil))
14
+ assert_equal(nil, Utils.port_extract_name(' ', nil))
15
+ assert_equal(nil, Utils.port_extract_name('foo ', nil))
16
+ assert_equal(nil, Utils.port_extract_name('/', nil))
17
+ assert_equal(nil, Utils.port_extract_name('////////// //////', nil))
18
+ assert_equal(nil, Utils.port_extract_name('//////////', nil))
19
+ assert_equal(nil, Utils.port_extract_name('/foo', nil))
20
+ assert_equal(nil, Utils.port_extract_name('foo/', nil))
21
+
22
+ assert_equal(['foo/bar'], Utils.port_extract_name('/foo/bar', nil))
23
+ assert_equal(['q/w', 'e'], Utils.port_extract_name('/q/w/e', nil))
24
+ assert_equal(['q/w/e', 'r'], Utils.port_extract_name('/q/w/e/r', nil))
25
+ assert_equal(nil, Utils.port_extract_name('/q/w/e/r/t', nil))
26
+ assert_equal(nil, Utils.port_extract_name('/q/w/e/r/t/y/u/i/o', nil))
27
+
28
+ Dir.chdir @ptree
29
+ assert_equal(nil, Utils.port_extract_name('program', @ptree))
30
+ assert_equal(['category/program'],
31
+ Utils.port_extract_name('category/program', @ptree))
32
+ assert_equal(['category/program', 'Makefile'],
33
+ Utils.port_extract_name('category/program/Makefile', @ptree))
34
+
35
+ Dir.chdir 'category'
36
+ assert_equal(nil, Utils.port_extract_name('program', @ptree))
37
+
38
+ Dir.chdir 'program'
39
+ assert_equal(["category/program", "pkg-descr"],
40
+ Utils.port_extract_name('pkg-descr', @ptree))
41
+
42
+ assert_equal(["category/program/files", "patch-aa"],
43
+ Utils.port_extract_name('files/patch-aa', @ptree))
44
+
45
+ Dir.chdir 'files'
46
+ assert_equal(["category/program/files", "patch-aa"],
47
+ Utils.port_extract_name('patch-aa', @ptree))
48
+
49
+ Dir.chdir save
50
+ end
51
+
52
+ def test_config
53
+ t = { foo: 'bar' }
54
+ t_exp = {
55
+ foo: 'bar', host: '127.0.0.1', port: 2401, user: 'anonymous',
56
+ pass: 'anoncvs', cvsroot: '/usr/local/cvsroot', ports_tree: ''
57
+ }
58
+
59
+ assert_equal('semis/localhost_cvs.yaml',
60
+ Utils.config_load(t, 'semis/localhost_cvs.yaml', nil))
61
+ assert_equal(t_exp, t)
62
+
63
+ assert_raises(RuntimeError) {
64
+ Utils.config_load(t, 'semis/localhost_cvs_incomplete.yaml', nil)
65
+ }
66
+ end
67
+
68
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: portvcs
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Alexander Gromnitsky
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-13 00:00:00 +02:00
18
+ default_executable: portvcs
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: open4
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 0
31
+ - 1
32
+ version: 1.0.1
33
+ type: :development
34
+ version_requirements: *id001
35
+ description:
36
+ email: alexander.gromnitsky@gmail.com
37
+ executables:
38
+ - portvcs
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - doc/TODO
43
+ - doc/README.rdoc
44
+ - doc/LICENSE
45
+ files:
46
+ - lib/portvcs/portvcs.rb
47
+ - lib/portvcs/utils.rb
48
+ - lib/portvcs/meta.rb
49
+ - bin/portvcs
50
+ - doc/TODO
51
+ - doc/README.rdoc
52
+ - doc/LICENSE
53
+ - etc/portvcs.yaml
54
+ - etc/fr.FreeBSD.org.yaml
55
+ - README.rdoc
56
+ - Rakefile
57
+ - test/ports/category/program/files/patch-aa
58
+ - test/ports/category/program/Makefile
59
+ - test/ports/category/program/pkg-descr
60
+ - test/helper.rb
61
+ - test/semis/localhost_cvs.yaml
62
+ - test/semis/localhost_cvs_incomplete.yaml
63
+ - test/semis/cvstest/portvcs/Makefile
64
+ - test/test_utils.rb
65
+ - test/test_cvsdumb.rb
66
+ has_rdoc: true
67
+ homepage: http://github.com/gromnitsky/portvcs
68
+ licenses: []
69
+
70
+ post_install_message:
71
+ rdoc_options:
72
+ - -m
73
+ - doc/README.rdoc
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ segments:
82
+ - 1
83
+ - 9
84
+ - 2
85
+ version: 1.9.2
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ requirements: []
95
+
96
+ rubyforge_project:
97
+ rubygems_version: 1.3.7
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: FreeBSD ports commits history viewer that doesn't require neither local ports tree nor CVS checkouts.
101
+ test_files:
102
+ - test/test_utils.rb
103
+ - test/test_cvsdumb.rb