portvcs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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