Revision 443:350acce374a2 extra

View differences:

extra/soundsoftware/SoundSoftware-salted.pm
1
package Apache::Authn::SoundSoftware;
2

  
3
=head1 Apache::Authn::SoundSoftware
4

  
5
SoundSoftware - a mod_perl module for Apache authentication against a
6
Redmine database and optional LDAP implementing the access control
7
rules required for the SoundSoftware.ac.uk repository site.
8

  
9
=head1 SYNOPSIS
10

  
11
This module is closely based on the Redmine.pm authentication module
12
provided with Redmine.  It is intended to be used for authentication
13
in front of a repository service such as hgwebdir.
14

  
15
Requirements:
16

  
17
1. Clone/pull from repo for public project: Any user, no
18
authentication required
19

  
20
2. Clone/pull from repo for private project: Project members only
21

  
22
3. Push to repo for public project: "Permitted" users only (this
23
probably means project members who are also identified in the hgrc web
24
section for the repository and so will be approved by hgwebdir?)
25

  
26
4. Push to repo for private project: "Permitted" users only (as above)
27

  
28
5. Push to any repo that is tracking an external repo: Refused always
29

  
30
=head1 INSTALLATION
31

  
32
Debian/ubuntu:
33

  
34
  apt-get install libapache-dbi-perl libapache2-mod-perl2 \
35
    libdbd-mysql-perl libauthen-simple-ldap-perl libio-socket-ssl-perl
36

  
37
Note that LDAP support is hardcoded "on" in this script (it is
38
optional in the original Redmine.pm).
39

  
40
=head1 CONFIGURATION
41

  
42
   ## This module has to be in your perl path
43
   ## eg:  /usr/local/lib/site_perl/Apache/Authn/SoundSoftware.pm
44
   PerlLoadModule Apache::Authn::SoundSoftware
45

  
46
   # Example when using hgwebdir
47
   ScriptAlias / "/var/hg/hgwebdir.cgi/"
48

  
49
   <Location />
50
       AuthName "Mercurial"
51
       AuthType Basic
52
       Require valid-user
53
       PerlAccessHandler Apache::Authn::SoundSoftware::access_handler
54
       PerlAuthenHandler Apache::Authn::SoundSoftware::authen_handler
55
       SoundSoftwareDSN "DBI:mysql:database=redmine;host=localhost"
56
       SoundSoftwareDbUser "redmine"
57
       SoundSoftwareDbPass "password"
58
       Options +ExecCGI
59
       AddHandler cgi-script .cgi
60
       ## Optional where clause (fulltext search would be slow and
61
       ## database dependant).
62
       # SoundSoftwareDbWhereClause "and members.role_id IN (1,2)"
63
       ## Optional prefix for local repository URLs
64
       # SoundSoftwareRepoPrefix "/var/hg/"
65
  </Location>
66

  
67
See the original Redmine.pm for further configuration notes.
68

  
69
=cut
70

  
71
use strict;
72
use warnings FATAL => 'all', NONFATAL => 'redefine';
73

  
74
use DBI;
75
use Digest::SHA1;
76
use Authen::Simple::LDAP;
77
use Apache2::Module;
78
use Apache2::Access;
79
use Apache2::ServerRec qw();
80
use Apache2::RequestRec qw();
81
use Apache2::RequestUtil qw();
82
use Apache2::Const qw(:common :override :cmd_how);
83
use APR::Pool ();
84
use APR::Table ();
85

  
86
my @directives = (
87
  {
88
    name => 'SoundSoftwareDSN',
89
    req_override => OR_AUTHCFG,
90
    args_how => TAKE1,
91
    errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
92
  },
93
  {
94
    name => 'SoundSoftwareDbUser',
95
    req_override => OR_AUTHCFG,
96
    args_how => TAKE1,
97
  },
98
  {
99
    name => 'SoundSoftwareDbPass',
100
    req_override => OR_AUTHCFG,
101
    args_how => TAKE1,
102
  },
103
  {
104
    name => 'SoundSoftwareDbWhereClause',
105
    req_override => OR_AUTHCFG,
106
    args_how => TAKE1,
107
  },
108
  {
109
    name => 'SoundSoftwareRepoPrefix',
110
    req_override => OR_AUTHCFG,
111
    args_how => TAKE1,
112
  },
113
);
114

  
115
sub SoundSoftwareDSN { 
116
    my ($self, $parms, $arg) = @_;
117
    $self->{SoundSoftwareDSN} = $arg;
118
    my $query = "SELECT 
119
                 hashed_password, salt, auth_source_id, permissions
120
              FROM members, projects, users, roles, member_roles
121
              WHERE 
122
                projects.id=members.project_id
123
                AND member_roles.member_id=members.id
124
                AND users.id=members.user_id 
125
                AND roles.id=member_roles.role_id
126
                AND users.status=1 
127
                AND login=? 
128
                AND identifier=? ";
129
    $self->{SoundSoftwareQuery} = trim($query);
130
}
131

  
132
sub SoundSoftwareDbUser { set_val('SoundSoftwareDbUser', @_); }
133
sub SoundSoftwareDbPass { set_val('SoundSoftwareDbPass', @_); }
134
sub SoundSoftwareDbWhereClause { 
135
    my ($self, $parms, $arg) = @_;
136
    $self->{SoundSoftwareQuery} = trim($self->{SoundSoftwareQuery}.($arg ? $arg : "")." ");
137
}
138

  
139
sub SoundSoftwareRepoPrefix { 
140
    my ($self, $parms, $arg) = @_;
141
    if ($arg) {
142
	$self->{SoundSoftwareRepoPrefix} = $arg;
143
    }
144
}
145

  
146
sub trim {
147
    my $string = shift;
148
    $string =~ s/\s{2,}/ /g;
149
    return $string;
150
}
151

  
152
sub set_val {
153
    my ($key, $self, $parms, $arg) = @_;
154
    $self->{$key} = $arg;
155
}
156

  
157
Apache2::Module::add(__PACKAGE__, \@directives);
158

  
159

  
160
my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
161

  
162
sub access_handler {
163
    my $r = shift;
164

  
165
    print STDERR "SoundSoftware.pm: In access handler at " . scalar localtime() . "\n";
166

  
167
    unless ($r->some_auth_required) {
168
	$r->log_reason("No authentication has been configured");
169
	return FORBIDDEN;
170
    }
171

  
172
    my $method = $r->method;
173

  
174
    print STDERR "SoundSoftware.pm: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n";
175
    print STDERR "SoundSoftware.pm: Accept: " . $r->headers_in->{Accept} . "\n";
176

  
177
    my $dbh = connect_database($r);
178
    unless ($dbh) {
179
	print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n";
180
	return FORBIDDEN;
181
    }
182

  
183
    print STDERR "Connected to db, dbh is " . $dbh . "\n";
184

  
185
    my $project_id = get_project_identifier($dbh, $r);
186

  
187
    if (!defined $read_only_methods{$method}) {
188
        print STDERR "SoundSoftware.pm: Method is not read-only\n";
189
        if (project_repo_is_readonly($dbh, $project_id, $r)) {
190
            print STDERR "SoundSoftware.pm: Project repo is read-only, refusing access\n";
191
	    return FORBIDDEN;
192
        } else {
193
	    print STDERR "SoundSoftware.pm: Project repo is read-write, authentication handler required\n";
194
            return OK;
195
        }
196
    }
197

  
198
    my $status = get_project_status($dbh, $project_id, $r);
199

  
200
    $dbh->disconnect();
201
    undef $dbh;
202

  
203
    if ($status == 0) { # nonexistent
204
	print STDERR "SoundSoftware.pm: Project does not exist, refusing access\n";
205
	return FORBIDDEN;
206
    } elsif ($status == 1) { # public
207
	print STDERR "SoundSoftware.pm: Project is public, no restriction here\n";
208
	$r->set_handlers(PerlAuthenHandler => [\&OK])
209
    } else { # private
210
	print STDERR "SoundSoftware.pm: Project is private, authentication handler required\n";
211
    }
212

  
213
    return OK
214
}
215

  
216
sub authen_handler {
217
    my $r = shift;
218
    
219
    print STDERR "SoundSoftware.pm: In authentication handler at " . scalar localtime() . "\n";
220

  
221
    my $dbh = connect_database($r);
222
    unless ($dbh) {
223
        print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n";
224
        return AUTH_REQUIRED;
225
    }
226
    
227
    my $project_id = get_project_identifier($dbh, $r);
228
    my $realm = get_realm($dbh, $project_id, $r);
229
    $r->auth_name($realm);
230

  
231
    my ($res, $redmine_pass) =  $r->get_basic_auth_pw();
232
    unless ($res == OK) {
233
	$dbh->disconnect();
234
	undef $dbh;
235
	return $res;
236
    }
237
    
238
    print STDERR "SoundSoftware.pm: User is " . $r->user . ", got password\n";
239

  
240
    my $permitted = is_permitted($dbh, $project_id, $r->user, $redmine_pass, $r);
241
    
242
    $dbh->disconnect();
243
    undef $dbh;
244

  
245
    if ($permitted) {
246
	return OK;
247
    } else {
248
	print STDERR "SoundSoftware.pm: Not permitted\n";
249
	$r->note_auth_failure();
250
	return AUTH_REQUIRED;
251
    }
252
}
253

  
254
sub get_project_status {
255
    my $dbh = shift;
256
    my $project_id = shift;
257
    my $r = shift;
258

  
259
    if (!defined $project_id or $project_id eq '') {
260
	return 0; # nonexistent
261
    }
262
    
263
    my $sth = $dbh->prepare(
264
        "SELECT is_public FROM projects WHERE projects.identifier = ?;"
265
    );
266

  
267
    $sth->execute($project_id);
268
    my $ret = 0; # nonexistent
269
    if (my @row = $sth->fetchrow_array) {
270
    	if ($row[0] eq "1" || $row[0] eq "t") {
271
	    $ret = 1; # public
272
    	} else {
273
	    $ret = 2; # private
274
	}
275
    }
276
    $sth->finish();
277
    undef $sth;
278

  
279
    $ret;
280
}
281

  
282
sub project_repo_is_readonly {
283
    my $dbh = shift;
284
    my $project_id = shift;
285
    my $r = shift;
286

  
287
    if (!defined $project_id or $project_id eq '') {
288
        return 0; # nonexistent
289
    }
290

  
291
    my $sth = $dbh->prepare(
292
        "SELECT repositories.is_external FROM repositories, projects WHERE projects.identifier = ? AND repositories.project_id = projects.id;"
293
    );
294

  
295
    $sth->execute($project_id);
296
    my $ret = 0; # nonexistent
297
    if (my @row = $sth->fetchrow_array) {
298
        if (defined($row[0]) && ($row[0] eq "1" || $row[0] eq "t")) {
299
            $ret = 1; # read-only (i.e. external)
300
        } else {
301
            $ret = 0; # read-write
302
        }
303
    }
304
    $sth->finish();
305
    undef $sth;
306

  
307
    $ret;
308
}
309

  
310
sub is_permitted {
311
    my $dbh = shift;
312
    my $project_id = shift;
313
    my $redmine_user = shift;
314
    my $redmine_pass = shift;
315
    my $r = shift;
316

  
317
    my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
318

  
319
    my $cfg = Apache2::Module::get_config
320
	(__PACKAGE__, $r->server, $r->per_dir_config);
321

  
322
    my $query = $cfg->{SoundSoftwareQuery};
323
    my $sth = $dbh->prepare($query);
324
    $sth->execute($redmine_user, $project_id);
325

  
326
    my $ret;
327
    while (my ($hashed_password, $salt, $auth_source_id, $permissions) = $sth->fetchrow_array) {
328

  
329
	# Test permissions for this user before we verify credentials
330
	# -- if the user is not permitted this action anyway, there's
331
	# not much point in e.g. contacting the LDAP
332

  
333
	my $method = $r->method;
334

  
335
	if ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/)
336
	    || $permissions =~ /:commit_access/) {
337

  
338
	    # User would be permitted this action, if their
339
	    # credentials checked out -- test those now
340

  
341
	    print STDERR "SoundSoftware.pm: User $redmine_user has required role, checking credentials\n";
342

  
343
	    unless ($auth_source_id) {
344
                my $salted_password = Digest::SHA1::sha1_hex($salt.$pass_digest);
345
		if ($hashed_password eq $salted_password) {
346
		    print STDERR "SoundSoftware.pm: User $redmine_user authenticated via password\n";
347
		    $ret = 1;
348
		    last;
349
		}
350
	    } else {
351
		my $sthldap = $dbh->prepare(
352
		    "SELECT host,port,tls,account,account_password,base_dn,attr_login FROM auth_sources WHERE id = ?;"
353
		    );
354
		$sthldap->execute($auth_source_id);
355
		while (my @rowldap = $sthldap->fetchrow_array) {
356
		    my $ldap = Authen::Simple::LDAP->new(
357
			host    => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
358
			port    => $rowldap[1],
359
			basedn  => $rowldap[5],
360
			binddn  => $rowldap[3] ? $rowldap[3] : "",
361
			bindpw  => $rowldap[4] ? $rowldap[4] : "",
362
			filter  => "(".$rowldap[6]."=%s)"
363
			);
364
		    if ($ldap->authenticate($redmine_user, $redmine_pass)) {
365
			print STDERR "SoundSoftware.pm: User $redmine_user authenticated via LDAP\n";
366
			$ret = 1;
367
		    }
368
		}
369
		$sthldap->finish();
370
		undef $sthldap;
371
	    }
372
	} else {
373
	    print STDERR "SoundSoftware.pm: User $redmine_user lacks required role for this project\n";
374
	}
375
    }
376

  
377
    $sth->finish();
378
    undef $sth;
379

  
380
    $ret;
381
}
382

  
383
sub get_project_identifier {
384
    my $dbh = shift;
385
    my $r = shift;
386

  
387
    my $location = $r->location;
388
    my ($repo) = $r->uri =~ m{$location/*([^/]+)};
389

  
390
    return $repo if (!$repo);
391

  
392
    $repo =~ s/[^a-zA-Z0-9\._-]//g;
393

  
394
    # The original Redmine.pm returns the string just calculated as
395
    # the project identifier.  That won't do for us -- we may have
396
    # (and in fact already do have, in our test instance) projects
397
    # whose repository names differ from the project identifiers.
398

  
399
    # This is a rather fundamental change because it means that almost
400
    # every request needs more than one database query -- which
401
    # prompts us to start passing around $dbh instead of connecting
402
    # locally within each function as is done in Redmine.pm.
403

  
404
    my $sth = $dbh->prepare(
405
        "SELECT projects.identifier FROM projects, repositories WHERE repositories.project_id = projects.id AND repositories.url LIKE ?;"
406
    );
407

  
408
    my $cfg = Apache2::Module::get_config
409
	(__PACKAGE__, $r->server, $r->per_dir_config);
410

  
411
    my $prefix = $cfg->{SoundSoftwareRepoPrefix};
412
    if (!defined $prefix) { $prefix = '%/'; }
413

  
414
    my $identifier = '';
415

  
416
    $sth->execute($prefix . $repo);
417
    my $ret = 0;
418
    if (my @row = $sth->fetchrow_array) {
419
	$identifier = $row[0];
420
    }
421
    $sth->finish();
422
    undef $sth;
423

  
424
    print STDERR "SoundSoftware.pm: Repository '$repo' belongs to project '$identifier'\n";
425

  
426
    $identifier;
427
}
428

  
429
sub get_realm {
430
    my $dbh = shift;
431
    my $project_id = shift;
432
    my $r = shift;
433

  
434
    my $sth = $dbh->prepare(
435
        "SELECT projects.name FROM projects WHERE projects.identifier = ?;"
436
    );
437

  
438
    my $name = $project_id;
439

  
440
    $sth->execute($project_id);
441
    my $ret = 0;
442
    if (my @row = $sth->fetchrow_array) {
443
	$name = $row[0];
444
    }
445
    $sth->finish();
446
    undef $sth;
447

  
448
    # be timid about characters not permitted in auth realm and revert
449
    # to project identifier if any are found
450
    if ($name =~ m/[^\w\d\s\._-]/) {
451
	$name = $project_id;
452
    }
453

  
454
    my $realm = '"Mercurial repository for ' . "'$name'" . '"';
455

  
456
    $realm;
457
}
458

  
459
sub connect_database {
460
    my $r = shift;
461
    
462
    my $cfg = Apache2::Module::get_config
463
	(__PACKAGE__, $r->server, $r->per_dir_config);
464

  
465
    return DBI->connect($cfg->{SoundSoftwareDSN},
466
	                $cfg->{SoundSoftwareDbUser},
467
		        $cfg->{SoundSoftwareDbPass});
468
}
469

  
470
1;
extra/soundsoftware/SoundSoftware.pm
25 25

  
26 26
4. Push to repo for private project: "Permitted" users only (as above)
27 27

  
28
5. Push to any repo that is tracking an external repo: Refused always
29

  
28 30
=head1 INSTALLATION
29 31

  
30 32
Debian/ubuntu:
......
172 174
    print STDERR "SoundSoftware.pm: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n";
173 175
    print STDERR "SoundSoftware.pm: Accept: " . $r->headers_in->{Accept} . "\n";
174 176

  
175
    if (!defined $read_only_methods{$method}) {
176
	print STDERR "SoundSoftware.pm: Method is not read-only, authentication handler required\n";
177
	return OK;
178
    }
179

  
180 177
    my $dbh = connect_database($r);
181 178
    unless ($dbh) {
182 179
	print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n";
183 180
	return FORBIDDEN;
184 181
    }
185 182

  
186

  
187
print STDERR "Connected to db, dbh is " . $dbh . "\n";
183
    print STDERR "Connected to db, dbh is " . $dbh . "\n";
188 184

  
189 185
    my $project_id = get_project_identifier($dbh, $r);
186

  
187
    if (!defined $read_only_methods{$method}) {
188
        print STDERR "SoundSoftware.pm: Method is not read-only\n";
189
        if (project_repo_is_readonly($dbh, $project_id, $r)) {
190
            print STDERR "SoundSoftware.pm: Project repo is read-only, refusing access\n";
191
	    return FORBIDDEN;
192
        } else {
193
	    print STDERR "SoundSoftware.pm: Project repo is read-write, authentication handler required\n";
194
            return OK;
195
        }
196
    }
197

  
190 198
    my $status = get_project_status($dbh, $project_id, $r);
191 199

  
192 200
    $dbh->disconnect();
......
271 279
    $ret;
272 280
}
273 281

  
282
sub project_repo_is_readonly {
283
    my $dbh = shift;
284
    my $project_id = shift;
285
    my $r = shift;
286

  
287
    if (!defined $project_id or $project_id eq '') {
288
        return 0; # nonexistent
289
    }
290

  
291
    my $sth = $dbh->prepare(
292
        "SELECT repositories.is_external FROM repositories, projects WHERE projects.identifier = ? AND repositories.project_id = projects.id;"
293
    );
294

  
295
    $sth->execute($project_id);
296
    my $ret = 0; # nonexistent
297
    if (my @row = $sth->fetchrow_array) {
298
        if (defined($row[0]) && ($row[0] eq "1" || $row[0] eq "t")) {
299
            $ret = 1; # read-only (i.e. external)
300
        } else {
301
            $ret = 0; # read-write
302
        }
303
    }
304
    $sth->finish();
305
    undef $sth;
306

  
307
    $ret;
308
}
309

  
274 310
sub is_permitted {
275 311
    my $dbh = shift;
276 312
    my $project_id = shift;
extra/soundsoftware/convert-external-repos.rb
1
#!/usr/bin/env ruby
2

  
3
# == Synopsis
4
#
5
# convert-external-repos: Update local Mercurial mirrors of external repos,
6
# by running an external command for each project requiring an update.
7
#
8
# == Usage
9
#
10
#    convert-external-repos [OPTIONS...] -s [DIR] -r [HOST]
11
#     
12
# == Arguments (mandatory)
13
#
14
#   -s, --scm-dir=DIR         use DIR as base directory for repositories
15
#   -r, --redmine-host=HOST   assume Redmine is hosted on HOST. Examples:
16
#                             -r redmine.example.net
17
#                             -r http://redmine.example.net
18
#                             -r https://example.net/redmine
19
#   -k, --key=KEY             use KEY as the Redmine API key
20
#   -c, --command=COMMAND     use this command to update each external
21
#                             repository: command is called with the name
22
#                             of the project, the path to its repo, and
23
#                             its external repo url as its three args
24
#
25
# == Options
26
#
27
#   --http-user=USER          User for HTTP Basic authentication with Redmine WS
28
#   --http-pass=PASSWORD      Password for Basic authentication with Redmine WS
29
#   -t, --test                only show what should be done
30
#   -h, --help                show help and exit
31
#   -v, --verbose             verbose
32
#   -V, --version             print version and exit
33
#   -q, --quiet               no log
34

  
35

  
36
require 'getoptlong'
37
require 'rdoc/usage'
38
require 'find'
39
require 'etc'
40

  
41
Version = "1.0"
42

  
43
opts = GetoptLong.new(
44
                      ['--scm-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
45
                      ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
46
                      ['--key',          '-k', GetoptLong::REQUIRED_ARGUMENT],
47
                      ['--http-user',          GetoptLong::REQUIRED_ARGUMENT],
48
                      ['--http-pass',          GetoptLong::REQUIRED_ARGUMENT],
49
                      ['--command' ,     '-c', GetoptLong::REQUIRED_ARGUMENT],
50
                      ['--test',         '-t', GetoptLong::NO_ARGUMENT],
51
                      ['--verbose',      '-v', GetoptLong::NO_ARGUMENT],
52
                      ['--version',      '-V', GetoptLong::NO_ARGUMENT],
53
                      ['--help'   ,      '-h', GetoptLong::NO_ARGUMENT],
54
                      ['--quiet'  ,      '-q', GetoptLong::NO_ARGUMENT]
55
                      )
56

  
57
$verbose      = 0
58
$quiet        = false
59
$redmine_host = ''
60
$repos_base   = ''
61
$http_user    = ''
62
$http_pass    = ''
63
$test         = false
64

  
65
$mirrordir    = '/var/mirror'
66

  
67
def log(text, options={})
68
  level = options[:level] || 0
69
  puts text unless $quiet or level > $verbose
70
  exit 1 if options[:exit]
71
end
72

  
73
def system_or_raise(command)
74
  raise "\"#{command}\" failed" unless system command
75
end
76

  
77
begin
78
  opts.each do |opt, arg|
79
    case opt
80
    when '--scm-dir';        $repos_base   = arg.dup
81
    when '--redmine-host';   $redmine_host = arg.dup
82
    when '--key';            $api_key      = arg.dup
83
    when '--http-user';      $http_user    = arg.dup
84
    when '--http-pass';      $http_pass    = arg.dup
85
    when '--command';        $command      = arg.dup
86
    when '--verbose';        $verbose += 1
87
    when '--test';           $test = true
88
    when '--version';        puts Version; exit
89
    when '--help';           RDoc::usage
90
    when '--quiet';          $quiet = true
91
    end
92
  end
93
rescue
94
  exit 1
95
end
96

  
97
if $test
98
  log("running in test mode")
99
end
100

  
101
if ($redmine_host.empty? or $repos_base.empty? or $command.empty?)
102
  RDoc::usage
103
end
104

  
105
unless File.directory?($repos_base)
106
  log("directory '#{$repos_base}' doesn't exist", :exit => true)
107
end
108

  
109
begin
110
  require 'active_resource'
111
rescue LoadError
112
  log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
113
end
114

  
115
class Project < ActiveResource::Base
116
  self.headers["User-agent"] = "SoundSoftware external repository converter/#{Version}"
117
end
118

  
119
log("querying Redmine for projects...", :level => 1);
120

  
121
$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
122
$redmine_host.gsub!(/\/$/, '')
123

  
124
Project.site = "#{$redmine_host}/sys";
125
Project.user = $http_user;
126
Project.password = $http_pass;
127

  
128
begin
129
  # Get all active projects that have the Repository module enabled
130
  projects = Project.find(:all, :params => {:key => $api_key})
131
rescue => e
132
  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
133
end
134

  
135
if projects.nil?
136
  log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
137
end
138

  
139
log("retrieved #{projects.size} projects", :level => 1)
140

  
141
projects.each do |project|
142
  log("treating project #{project.name}", :level => 1)
143

  
144
  if project.identifier.empty?
145
    log("\tno identifier for project #{project.name}")
146
    next
147
  elsif not project.identifier.match(/^[a-z0-9\-]+$/)
148
    log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
149
    next
150
  end
151

  
152
  if !project.respond_to?(:repository) or !project.repository.is_external?
153
    log("\tproject #{project.identifier} does not use an external repository");
154
    next
155
  end
156

  
157
  external_url = project.repository.external_url;
158
  log("\tproject #{project.identifier} has external repository url #{external_url}");
159

  
160
  if !external_url.match(/^[a-z][a-z+]{0,8}[a-z]:\/\//)
161
    log("\tthis doesn't look like a plausible url to me, skipping")
162
    next
163
  end
164

  
165
  repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
166

  
167
  unless File.directory?(repos_path)
168
    log("\tproject repo directory '#{repos_path}' doesn't exist")
169
    next
170
  end
171

  
172
  system($command, project.identifier, repos_path, external_url)
173
  
174
  $cache_clearance_file = File.join($mirrordir, project.identifier, 'url_changed')
175
  if File.file?($cache_clearance_file)
176
    log("\tproject repo url has changed, requesting cache clearance")
177
    if project.post(:repository_cache, :key => $api_key)
178
      File.delete($cache_clearance_file)
179
    end
180
  end
181

  
182
end
183
  
extra/soundsoftware/extract-docs.sh
12 12
redgrp="redmine"
13 13

  
14 14
apikey=""
15
apischeme="https"
15 16
apihost=""
16 17
apiuser=""
17 18
apipass=""
......
22 23
    *) progdir="$(pwd)/$progdir" ;;
23 24
esac
24 25

  
25
types="doxygen javadoc" # Do Doxygen first (it can be used for Java too)
26
types="doxygen javadoc matlabdocs" # Do Doxygen first (it can be used for Java too)
26 27

  
27 28
for x in $types; do
28 29
    if [ ! -x "$progdir/extract-$x.sh" ]; then
......
36 37
    p="$1"
37 38
    if [ -n "$apikey" ]; then
38 39
	if [ -n "$apiuser" ]; then
39
	    sudo -u docgen curl -u "$apiuser":"$apipass" "http://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d ""
40
	    sudo -u docgen curl -u "$apiuser":"$apipass" "$apischeme://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d ""
40 41
	else
41
	    sudo -u docgen curl "http://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d ""
42
	    sudo -u docgen curl "$apischeme://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d ""
42 43
	fi
43 44
    else 
44 45
	echo "Can't enable Embedded, API not configured" 1>&2
extra/soundsoftware/extract-matlabdocs.sh
1
#!/bin/bash
2

  
3
docdir="/var/doc"
4

  
5
progdir=$(dirname $0)
6
case "$progdir" in
7
    /*) ;;
8
    *) progdir="$(pwd)/$progdir" ;;
9
esac
10

  
11
project="$1"
12
projectdir="$2"
13
targetdir="$3"
14

  
15
if [ -z "$project" ] || [ -z "$targetdir" ] || [ -z "$projectdir" ]; then
16
    echo "Usage: $0 <project> <projectdir> <targetdir>"
17
    exit 2
18
fi
19

  
20
if [ ! -d "$projectdir" ]; then
21
    echo "Project directory $projectdir not found"
22
    exit 1
23
fi
24

  
25
if [ ! -d "$targetdir" ]; then
26
    echo "Target dir $targetdir not found"
27
    exit 1
28
fi
29

  
30
if [ -f "$targetdir/index.html" ]; then
31
    echo "Target dir $targetdir already contains index.html"
32
    exit 1
33
fi
34

  
35
mfile=$(find "$projectdir" -type f -name \*.m -print0 | xargs -0 grep -l '^% ' | head -1)
36

  
37
if [ -z "$mfile" ]; then
38
    echo "No MATLAB files with comments found for project $project"
39
    exit 1
40
fi
41

  
42
echo "Project $project contains at least one MATLAB file with comments"
43

  
44
cd "$projectdir" || exit 1
45

  
46
perl "$progdir/matlab-docs.pl" -c "$progdir/matlab-docs.conf" -d "$targetdir"
47

  
extra/soundsoftware/matlab-docs-credit.html
1
<div style="clear: both; float: right"><small><i>Produced by mtree2html by Hartmut Pohlheim</i></small></div>
extra/soundsoftware/matlab-docs.conf
1
# configuration file for generation of html-docu from m-files
2
#
3
# Author:   Hartmut Pohlheim
4
# History:  05.11.2000  file created (parameters for mtree2html2001)
5
#
6
# The following options/variables must be changed/adapted:
7
#   dirmfiles
8
#   dirhtml
9
#   csslink
10
#   texttitleframelayout
11
#   texttitlefiles
12
#
13
# The following options/variables should be adapted:
14
#   authorfile
15
#   filenametopframe
16
#   codeheadmeta
17

  
18
#========================================================================
19
# Variables (possible keywords: set)
20
# to use the built-in settings, comment the line using # in first column
21
#========================================================================
22

  
23
#------------------------------------------------------------------------
24
# dirmfiles: name of directory containing Matlab m-files
25
# dirhtml: name of directory to place the html-files into
26
# exthtml: extension used for the html files (.html or .htm)
27
#          don't forget the point in front of the extension
28
#------------------------------------------------------------------------
29
set dirmfiles = .
30
set dirhtml = doc-output
31
set exthtml = .html
32

  
33
#------------------------------------------------------------------------
34
# authorfile:   name of file containing info about author (in html)
35
#               if defined, this text is included at the bottom of the 
36
#               html files
37
#------------------------------------------------------------------------
38
set authorfile = matlab-docs-credit.html
39

  
40
#------------------------------------------------------------------------
41
# csslink:   text for linking to css file (style sheets)
42
#            the text defined here is directly included into the head 
43
#            of the html file
44
#------------------------------------------------------------------------
45
#set csslink = <link rel=stylesheet type="text/css" href="CSSFILENAME.css" />
46

  
47
#------------------------------------------------------------------------
48
# links2filescase: this is a bit difficult
49
#                  Matlab is case sensitive on UNIX, but case insensitive
50
#                  on Windows. Under UNIX Matlab function calls work 
51
#                  only, when the case of file name and function call are 
52
#                  identical, under Windows you can do what you want.
53
#                  This scripts help you, to keep an exact case in your 
54
#                  project.
55
#          exact - internal links are only generated, when case of file 
56
#                  name and in source code are identical
57
#            all - case doesn't matter
58
#     exactupper - same as exact, additionally links are also vreated to 
59
#                  all upper case function names in source code (often 
60
#                  used by Mathworks)
61
#      exactvery - same as exact, additionally info about not matching
62
#                  case is written to screen (stdout), this can be very 
63
#                  helpful in cleaning up the case in a project
64
#------------------------------------------------------------------------
65
set links2filescase = all
66

  
67
#------------------------------------------------------------------------
68
# texttitleframelayout:    text of title for frame layout file (whole docu)
69
#------------------------------------------------------------------------
70
set texttitleframelayout = MATLAB Function Documentation
71

  
72
#------------------------------------------------------------------------
73
# texttitle/headerindexalldirs: text of title and header for directory index
74
#------------------------------------------------------------------------
75
set texttitleindexalldirs = Index of Directories
76
set textheaderindexalldirs = Index of Directories
77

  
78
#------------------------------------------------------------------------
79
# texttitle/headerindex:    text of title and header for index file
80
#------------------------------------------------------------------------
81
set texttitleindex = A-Z Index of Functions
82
set textheaderindex = A-Z Index of Functions
83

  
84
#------------------------------------------------------------------------
85
# texttitle/headerfiles:    text of title and header for files
86
#                           name of file will be added at the end
87
#------------------------------------------------------------------------
88
set texttitlefiles = Function
89
set textheaderfiles = Documentation of
90

  
91
#------------------------------------------------------------------------
92
# frames: whether to use frames in layout (yes or no)
93
#------------------------------------------------------------------------
94
set frames = no
95

  
96
#------------------------------------------------------------------------
97
# filenametopframe: name of file including frame layout (highest level file)
98
# [default: index]
99
#------------------------------------------------------------------------
100
set filenametopframe = index
101

  
102
#------------------------------------------------------------------------
103
# textjumpindexglobal: text displayed for jump to index of all files
104
#                      (global)
105
# textjumpindexlocal:  text displayed for jump to index of files in actual
106
#                      directory (local)
107
#------------------------------------------------------------------------
108
set textjumpindexglobal = <b>Index of</b> all files:
109
set textjumpindexlocal = this subdirectory only:
110

  
111
#------------------------------------------------------------------------
112
# includesource: include source of m-files in documentation [YES|no]
113
#------------------------------------------------------------------------
114
set includesource = yes
115

  
116
#------------------------------------------------------------------------
117
# usecontentsm: use contents.m files as well for structured
118
#               (hopefully) index [YES|no]
119
#------------------------------------------------------------------------
120
set usecontentsm = no
121

  
122
#------------------------------------------------------------------------
123
# includesource: write/update contents.m files [yes|NO]
124
#------------------------------------------------------------------------
125
set writecontentsm = no
126

  
127
#------------------------------------------------------------------------
128
# processtree:  parse whole directory tree recursively [YES|no]
129
#------------------------------------------------------------------------
130
set processtree = yes
131

  
132
#------------------------------------------------------------------------
133
# producetree:  produce tree for html-files in same structure than
134
#		          tree of m-files [yes|NO]
135
#               if no, all files are saved in the same directory, often 
136
#               easier for outside linking to files
137
#------------------------------------------------------------------------
138
set producetree = yes
139

  
140
#------------------------------------------------------------------------
141
# codebodyindex/files: HTML-code for adding to BODY tag
142
#                      can be used for defining colors and
143
#                      backgroundimages of the files
144
#                      No longer recommended, use the css file
145
#------------------------------------------------------------------------
146
set codebodyindex =
147
set codebodyfiles =
148

  
149
#------------------------------------------------------------------------
150
# codeheadmeta: HTML-code added in HEAD area, use for supplying META info
151
#------------------------------------------------------------------------
152
set codeheadmeta = 
153

  
154
#------------------------------------------------------------------------
155
# codehr: HTML-code used to define a <HR>, do what you want
156
#------------------------------------------------------------------------
157
set codehr = <hr>
158

  
159
#------------------------------------------------------------------------
160
# codeheader: HTML-code added to <H*> tags, use for centering header text
161
#             or changing the colour/size/font of the header text
162
#------------------------------------------------------------------------
163
set codeheader = 
164

  
165

  
166
# End of parameter file
extra/soundsoftware/matlab-docs.pl
1
@rem = '--*-Perl-*--';
2
@rem = '
3
@echo off
4
perl -w -S %0.bat %1 %2 %3 %4 %5 %6 %7 %8 %9
5
goto endofperl
6
@rem ';
7
# perl -w -S %0.bat "$@"
8
#!/usr/bin/perl
9
#
10
# mtree2html_2000 - produce html files from Matlab m-files.
11
#                   use configuration file for flexibility
12
#                   can process tree of directories
13
#
14
# Copyright (C) 1996-2000 Hartmut Pohlheim.  All rights reserved.
15
# includes small parts of m2html from Jeffrey C. Kantor 1995
16
#
17
# Author:  Hartmut Pohlheim
18
# History: 06.03.1996  file created
19
#          07.03.1996  first working version
20
#          08.03.1996  modularized, help text only once included
21
#          11.03.1996  clean up, some functions rwritten
22
#          18.04.1996  silent output with writing in one line only
23
#                      version 0.20 fixed
24
#          14.05.1996  start of adding tree structure, could create tree
25
#          15.05.1996  creating of index files for every directory
26
#          17.05.1996  first working version except compact A-Z index
27
#          20.05.1996  cleanup of actual version, more variables and 
28
#                      configurable settings
29
#          21.05.1996  reading, update and creation of contents.m added 
30
#          22.05.1996  creation of short index started
31
#          28.05.1996  jump letters for short index,
32
#                      3 different directory indexes (short/long/contents)
33
#          29.05.1996  major cleanup, short and long index created from one function
34
#                      links for HTML and Indexes from 1 function,
35
#                      version 0.9
36
#          30.05.1996  contents.m changed to Contents.m (because unix likes it)
37
#                      function definition can be in first line of m file before comments
38
#                      version 0.91 fixed
39
#          03.06.1996  contents file can be written as wanted, the links will be correct
40
#                      cross references in help block of m-file will be found and
41
#                      converted, even if the name of the function is written upper case
42
#                      version 0.92 fixed
43
#          05.06.1996  construction of dependency matrix changed, is able now to process
44
#                      even the whole matlab tree (previous version needed to much memory)
45
#                      removed warning for contents files in different directories
46
#                      version 0.94 fixed
47
#          06.06.1996  new link name matrices for ConstructHTMLFile created,
48
#                      everything is done in ConstructDependencyMatrix,
49
#                      both dependencies (calls and called) and matrix 
50
#                      with all mentioned names in this m-file, thus, much
51
#                      less scanning in html construction
52
#                      script is now (nearly) linear scalable, thus, matlab-toolbox
53
#                      tree takes less than 1 hour on a Pentium120, with source
54
#                      version 0.96 fixed
55
#          10.06.1996  order of creation changed, first all indexes (includes 
56
#                      update/creation of contents.m) and then ConstructDepency
57
#                      thus, AutoAdd section will be linked as well
58
#                      excludenames extended, some more common word function names added
59
#                      version 0.97 fixed
60
#          17.02.1998  writecontentsm as command line parameter added
61
#                      error of file not found will even appear when silent
62
#                      version 1.02
63
#          21.05.2000  mark comments in source code specially (no fully correct, 
64
#                      can't handle % in strings)
65
#                      version 1.11
66
#          05.11.2000  link also to upper and mixed case m-files
67
#                      searching for .m files now really works (doesn't find grep.com any longer)
68
#                      file renamed to mtree2html2001
69
#                      generated html code now all lower case 
70
#                      inclusion of meta-description and meta-keywords in html files
71
#                      HTML4 compliance done (should be strict HTML4.0, quite near XHTML)
72
#                      version 1.23
73
#
74
#	   29.03.2011  (Chris Cannam) add frames option.
75

  
76
$VERSION  = '1.23';
77
($PROGRAM = $0) =~ s@.*/@@; $PROGRAM = "\U$PROGRAM\E";
78
$debug = 1;
79

  
80
#------------------------------------------------------------------------
81
# Define platform specific things
82
#------------------------------------------------------------------------
83
# suffix for files to search is defined twice
84
# the first ($suffix) is for string creation and contains the . as well
85
# the second ($suffixforsearch) is for regular expression, handling of . is quite special
86
$suffix = ".m";
87
$suffixforsearch = "m";
88
# the directory separator
89
$dirsep = "/";
90
# what is the current directory
91
$diract = ".";
92

  
93
#------------------------------------------------------------------------
94
#  Define all variables and their standard settings
95
#  documentation of variables is contained in accompanying rc file
96
#------------------------------------------------------------------------
97
%var =
98
(
99
   'authorfile',                '',
100
   'codebodyfiles',             '',
101
   'codebodyindex',             '',
102
   'codeheadmeta',              '<meta name="author of conversion perl script" content="Hartmut Pohlheim" />',
103
   'codehr',                    '<hr size="3" noshade="noshade" />',
104
   'codeheader',                '',
105
   'configfile',                'matlab-docs.conf',
106
   'csslink',                   '',
107
   'dirmfiles',                 $diract,
108
   'dirhtml',                   $diract,
109
   'exthtml',                   '.html',
110
   'frames',                    'yes',
111
   'filenametopframe',          'index',
112
   'filenameindexlongglobal',   'indexlg',
113
   'filenameindexlonglocal',    'indexll',
114
   'filenameindexshortglobal',  'indexsg',
115
   'filenameindexshortlocal',   'indexsl',
116
   'filenameextensionframe',    'f',
117
   'filenameextensionindex',    'i',
118
   'filenameextensionjump',     'j',
119
   'filenamedirshort',          'dirtops',
120
   'filenamedirlong',           'dirtopl',
121
   'filenamedircontents',       'dirtopc',
122
   'includesource',             'yes',
123
   'links2filescase',           'all',
124
   'processtree',               'yes',
125
   'producetree',               'yes',
126
   'textjumpindexlocal',        'Local Index',
127
   'textjumpindexglobal',       'Global Index',
128
   'texttitleframelayout',      'Documentation of Matlab Files',
129
   'texttitleindexalldirs',     'Index of Directories',
130
   'textheaderindexalldirs',    'Index of Directories',
131
   'texttitleindex',            '',
132
   'textheaderindex',           '',
133
   'texttitlefiles',            'Documentation of ',
134
   'textheaderfiles',           'Documentation of ',
135
   'usecontentsm',              'yes',
136
   'writecontentsm',            'no'
137
);
138

  
139

  
140
# define all m-file names, that should be excluded from linking
141
# however, files will still be converted
142
@excludenames = ( 'all','ans','any','are',
143
                  'cs',
144
                  'demo','dos',
145
                  'echo','edit','else','elseif','end','exist',
146
                  'flag','for','function',
147
                  'global',
148
                  'help',
149
                  'i','if','inf','info',
150
                  'j',
151
                  'more',
152
                  'null',
153
                  'return',
154
                  'script','strings',
155
                  'what','which','while','who','whos','why',
156
                );
157

  
158
# Text for inclusion in created HTML/Frame files: Doctype and Charset
159
$TextDocTypeHTML  = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">';
160
$TextDocTypeFrame = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">'; 
161
$TextMetaCharset = '<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />';
162

  
163
#------------------------------------------------------------------------
164
# Read the command line arguments
165
#------------------------------------------------------------------------
166
if (@ARGV == 0) {
167
   &DisplayHelp()  if &CheckFileName($var{'configfile'}, 'configuration file');
168
}
169

  
170
# Print provided command line arguments on screen
171
foreach (@ARGV) { print "   $_\n      "; }
172

  
173
# Get the options
174
use Getopt::Long;
175
@options = ('help|h', 'todo|t', 'version|v',
176
            'authorfile|a=s', 'configfile|c=s', 'dirhtml|html|d=s',
177
            'dirmfiles|mfiles|m=s', 'includesource|i=s',
178
            'processtree|r=s', 'producetree|p=s',
179
            'silent|quiet|q', 'writecontentsm|w=s');
180
&GetOptions(@options) || die "use -h switch to display help statement\n";
181

  
182

  
183
# Display help or todo list, when requested
184
&DisplayHelp()                         if $opt_help;
185
&DisplayTodo()                         if $opt_todo;
186
die "$PROGRAM v$VERSION\n"             if $opt_version;
187

  
188
$exit_status = 0;
189

  
190
#------------------------------------------------------------------------
191
# Read the config file
192
#------------------------------------------------------------------------
193
$var{'configfile'} = $opt_configfile         if $opt_configfile;
194
&GetConfigFile($var{'configfile'});
195

  
196

  
197
#------------------------------------------------------------------------
198
# Process/Check the command line otions
199
#------------------------------------------------------------------------
200
$var{'dirhtml'}   = $opt_dirhtml              if $opt_dirhtml;
201
if (!(substr($var{'dirhtml'}, -1, 1) eq $dirsep)) { $var{'dirhtml'} = $var{'dirhtml'}.$dirsep; }
202
$var{'dirmfiles'} = $opt_dirmfiles            if $opt_dirmfiles;
203
if (!(substr($var{'dirmfiles'}, -1, 1) eq $dirsep)) { $var{'dirmfiles'} = $var{'dirmfiles'}.$dirsep; }
204

  
205
$var{'authorfile'} = $opt_author              if $opt_author;
206
$var{'includesource'} = $opt_includesource    if $opt_includesource;
207
if ($var{'includesource'} ne 'no') { $var{'includesource'} = 'yes'; }
208
$var{'processtree'} = $opt_processtree        if $opt_processtree;
209
if ($var{'processtree'} ne 'no') { $var{'processtree'} = 'yes'; }
210
$var{'producetree'} = $opt_producetree        if $opt_producetree;
211
if ($var{'producetree'} ne 'no') { $var{'producetree'} = 'yes'; }
212
if ($var{'processtree'} eq 'no') { $var{'producetree'} = 'no'; }
213
if ($var{'frames'} ne 'no') { $var{'frames'} = 'yes'; }
214
# if (($var{'processtree'} eq 'yes') && ($var{'producetree'} eq 'no')) { $var{'usecontentsm'} = 'no'; }
215

  
216
$var{'writecontentsm'} = $opt_writecontentsm  if $opt_writecontentsm;
217

  
218
#------------------------------------------------------------------------
219
# Do the real stuff
220
#------------------------------------------------------------------------
221

  
222
# Print variables on screen, when not silent
223
&ListVariables                          if !$opt_silent;
224

  
225
# Check the author file
226
if ($var{'authorfile'} ne '') {
227
    if (!($var{'authorfile'} =~ m,^/,)) {
228
	# relative path: treat as relative to config file
229
	my $cfd = $var{'configfile'};
230
	$cfd =~ s,/[^/]*$,/,;
231
	$cfd =~ s,^[^/]*$,.,;
232
	$var{'authorfile'} = "$cfd/" . $var{'authorfile'};
233
    }
234
    if (&CheckFileName($var{'authorfile'}, 'author file')) {
235
	$var{'authorfile'} = '';
236
	if (!$opt_silent) { print "   Proceeding without author information!\n"; }
237
    }
238
}
239

  
240
# Call the function doing all the real work
241
&ConstructNameMatrix;
242

  
243
&ConstructDependencyMatrix;
244

  
245
&ConstructAllIndexFiles;
246

  
247
&ConstructHTMLFiles;
248

  
249
exit $exit_status;
250

  
251
#------------------------------------------------------------------------
252
# Construct list of all mfile names and initialize various data arrays.
253
#------------------------------------------------------------------------
254
sub ConstructNameMatrix
255
{
256
   local(*MFILE);
257
   local($file, $dirname);
258
   local(@newdirectories);
259
   local(%localnames);
260
   
261
   $RecDeep = 0;
262
   &ParseTreeReadFiles($var{'dirmfiles'}, $RecDeep);
263

  
264
   foreach $dirname (@directories) { 
265
      if ($dirnumbermfiles{$dirname} > 0) {
266
         push(@newdirectories, $dirname);
267
         if (! defined($contentsname{$dirname})) {
268
            $contentsname{$dirname} = 'Contents';
269
            if (($var{'writecontentsm'} eq 'no') && ($var{'usecontentsm'} eq 'yes')) {
270
               print "\r ParseTree - for directory  $dirname  no contents file found!\n";
271
               print   "             create one or enable writing of contents file (writecontentsm = yes)!\n";
272
            }
273
         }
274
      }
275
   }
276
   @alldirectories = @directories;
277
   @directories = @newdirectories;
278

  
279
   foreach $dirname (@directories) { 
280
      if ($debug > 0) { print "Dir: $dirname \t\t $dirnumbermfiles{$dirname} \t$contentsname{$dirname}\n"; }
281
   }
282
   
283
   @names = sort(keys %mfile);
284

  
285
   # check, if name of directory is identical to name of file
286
   @dirsinglenames = values(%dirnamesingle);
287
   grep($localnames{$_}++, @dirsinglenames);
288
   @dirandfilename = grep($localnames{$_}, @names);
289
   if (@dirandfilename) { 
290
      print "\r   Name clash between directory and file name: @dirandfilename\n";
291
      print   "      These files will be excluded from linking!\n";
292
      push(@excludenames, @dirandfilename);
293
   }
294
   
295
   # construct names matrix for help text linking
296
   #    exclude some common words (and at the same time m-functions) from linking in help text
297
   grep($localnames{$_}++, @excludenames);
298
   @linknames = grep(!$localnames{$_}, @names);
299

  
300
   if ($debug > 2) { print "linknames (names of found m-files):\n    @linknames\n"; }
301
   
302
}
303

  
304
#------------------------------------------------------------------------
305
# Parse tree and collect all Files
306
#------------------------------------------------------------------------
307
sub ParseTreeReadFiles
308
{
309
   local($dirname, $localRecDeep) = @_;
310
   local($file, $name, $filewosuffix);
311
   local($dirhtmlname, $dirmode);
312
   local($relpath, $relpathtoindex, $replacevardir);
313
   local(*CHECKDIR, *AKTDIR);
314
   local(@ALLEFILES);
315
   
316
   opendir(AKTDIR, $dirname) || die "ParseTree - Can't open directory $dirname: $!";
317
   if ($debug > 1) { print "\nDirectory: $dirname\n"; }
318

  
319
   # create relative path
320
   $_ = $dirname; $replacevardir = $var{'dirmfiles'};
321
   s/$replacevardir//; $relpath = $_;
322
   s/[^\/]+/../g; $relpathtoindex = $_;
323

  
324
   # producetree no
325
   if ($var{'producetree'} eq 'no') { $relpath = ''; $relpathtoindex = ''; }
326

  
327
   # names of directories (top-level and below top-level m-file-directory)
328
   push(@directories, $dirname);
329
   $dirnumbermfiles{$dirname} = 0;    # set number of m-files for this dir to zero
330
   # relative path from top-level directory, depends on directory name
331
   $dirnamerelpath{$dirname} = $relpath;
332
   # relative path from actual directory to top-level directory, depends on directory name
333
   $dirnamerelpathtoindex{$dirname} = $relpathtoindex;
334
   # recursion level for directory, depends on directory name
335
   $dirnamerecdeep{$dirname} = $localRecDeep;
336
   
337
   # only the name of the directory, without path
338
   $rindexprint = rindex($dirname, $dirsep, length($dirname)-2);
339
   $rindsub = substr($dirname, $rindexprint+1, length($dirname)-$rindexprint-2);
340
   $dirnamesingle{$dirname} = $rindsub;
341

  
342
   # create name of html-directories 
343
   $_ = $dirname;
344
   s/$var{'dirmfiles'}/$var{'dirhtml'}/;
345
   $dirhtmlname = $_;
346
   if ($var{'producetree'} eq 'no') { $dirhtmlname = $var{'dirhtml'}; }
347
   # try to open html directory, if error, then create directory,
348
   # use same mode as for corresponding m-file directory
349
   opendir(CHECKDIR,"$dirhtmlname") || do {
350
      $dirmode = (stat($dirname))[2]; # print "$dirmode\n";
351
      mkdir("$dirhtmlname", $dirmode) || die ("Cannot create directory $dirhtmlname: $! !");
352
   };
353
   closedir(CHECKDIR);
354

  
355

  
356
   # read everything from this directory and process them
357
   @ALLEFILES = readdir(AKTDIR);
358

  
359
   foreach $file (@ALLEFILES) {
360
      # exclude . and .. directories
361
      next if $file eq '.';  next if $file eq '..';
362

  
363
      # test for existense of entry (redundant, used for debugging)
364
      if (-e $dirname.$file) {
365
         # if it's a directory, call this function recursively
366
         if (-d $dirname.$file) {
367
            if ($var{'processtree'} eq 'yes') {
368
               &ParseTreeReadFiles($dirname.$file.$dirsep, $localRecDeep+1);
369
            }
370
         }
371
         # if it's a file - test for m-file, save name and create some arrays
372
         elsif (-f $dirname.$file) {
373
            if ($file =~ /\.$suffixforsearch$/i) {
374
               # Remove the file suffix to establish the matlab identifiers
375
               $filewosuffix = $file;
376
               $filewosuffix =~ s/\.$suffixforsearch$//i;
377
               # $filename = $name;
378

  
379
               # Contents file in unix must start with a capital letter (Contents.m)
380
               # ensure, that m-file name is lower case, except the contents file
381
               if (! ($filewosuffix =~ /^contents$/i)) {
382
		   # if ($var{'links2filescase'}  eq 'low') { $filewosuffix = "\L$filewosuffix\E"; }
383
                  $filewosuffixlow = "\L$filewosuffix\E";
384
               }
385
               else { $contentsname{$dirname} = $filewosuffix; }
386

  
387
               # internal handle name is always lower case
388
               $name     = $filewosuffixlow;
389
               # file name is not lower case
390
               $filename = $filewosuffix;
391

  
392
               # if don't use C|contents.m, then forget all C|contents.m
393
               if ($var{'usecontentsm'} eq 'no') { if ($name =~ /contents/i) { next; } }
394

  
395
               # if m-file with this name already exists, use directory and name for name
396
               # only the first occurence of name will be used for links
397
               if (defined $mfile{$name}) { 
398
                  if (! ($name =~ /^contents$/i) ) {
399
                     print "\r ParseTree - Name conflict:  $name in $dirname already exists: $mfile{$name} !\n";
400
                     print   "             $mfile{$name}  will be used for links!\n";
401
                  }
402
                  $name = $dirname.$name;
403
               }
404
               # mfile name with path
405
               $mfile{$name} = $dirname.$file;
406
               # mfile name (without path)
407
               $mfilename{$name} = $filename;
408
               # mfile directory
409
               $mfiledir{$name} = $dirname;
410
               
411
               # html file name and full path, special extension of Contents files
412
               if ($name =~ /contents/i) { $extrahtmlfilename = $dirnamesingle{$dirname}; }
413
               else { $extrahtmlfilename = ''; }
414
               $hfile{$name} = $dirhtmlname.$mfilename{$name}.$extrahtmlfilename.$var{'exthtml'};
415

  
416
               # save relative html path
417
               # if ($var{'producetree'} eq 'yes') {
418
               $hfilerelpath{$name} = $relpath;
419
               # } else { # if no tree to produce, relative path is empty
420
               #    $hfilerelpath{$name} = '';
421
               # }
422

  
423
               # create relative path from html file to directory with global index file
424
               $hfileindexpath{$name} = $relpathtoindex;
425

  
426
               # Function declaration, if one exists, set default to script
427
               $synopsis{$name} = "";
428
               $mtype{$name} = "script";
429

  
430
               # First comment line
431
               $apropos{$name} = "";
432

  
433
               # count number of m-files in directories
434
               $dirnumbermfiles{$dirname}++;
435

  
436
               if ($debug > 1) {
437
                  if ($opt_silent) { print "\r"; }
438
                  print "   ParseTree: $name \t\t $mfile{$name} \t\t $hfile{$name}\t\t";
439
                  if (!$opt_silent) { print "\n"; }
440
               }
441
            }
442
         }
443
         else {
444
            print "Unknown type of file in $dirname: $file\n";
445
         }
446
      }
447
      else { print "Error: Not existing file in $dirname: $file\n"; }
448
   }
449

  
450
   closedir(AKTDIR)
451
   
452
}
453

  
454
#------------------------------------------------------------------------
455
# Construct Dependency matrix
456
#    $dep{$x,$y} > 0 if $x includes a reference to $y.
457
#------------------------------------------------------------------------
458
sub ConstructDependencyMatrix
459
{
460
   &ConstructDependencyMatrixReadFiles('all');
461
   &ConstructDependencyMatrixReally;
462
}
463

  
464

  
465
#------------------------------------------------------------------------
466
# Construct Dependency matrix
467
#    $dep{$x,$y} > 0 if $x includes a reference to $y.
468
#------------------------------------------------------------------------
469
sub ConstructDependencyMatrixReadFiles
470
{
471
   local($whatstring) = @_;
472
   local(*MFILE);
473
   local($name, $inames);
474
   local(%symbolsdep, %symbolsall);
475

  
476
   # Initialize as all zeros.
477
   # foreach $name (@names) { grep($dep{$name,$_}=0,@names); if ($debug > 0) { print "\r   DepMatrix anlegen: $name\t$#names\t"; } }
478

  
479
   # Compute the dependency matrix
480
   $inames = -1;
481
   foreach $name (@names) {
482
      # Read each file and tabulate the distinct alphanumeric identifiers in
483
      # an array of symbols. Also scan for:
484
      #   synopsis: The function declaration line
485
      #   apropos:  The first line of the help text
486

  
487
      # look for whatstring, if all: process every file, if contents: process only contents files 
488
      if ($whatstring eq 'contents') { if (! ($name =~ /contents$/i) ) { next; } }
489
      elsif ($whatstring eq 'all') { }    # do nothing
490
      else { print "\r   ConstructDependency: Unknown parameter whatstring: $whatstring \n"; }
491

  
492
      undef %symbolsall; undef %symbolsdep;
493
      open(MFILE,"<$mfile{$name}") || die("Can't open $mfile{$name}: $!\n");
494
      while (<MFILE>) {
495
         chop;
496

  
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff