nic.pl 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. #!/usr/bin/perl
  2. my $VER = "2.0";
  3. use strict;
  4. use warnings;
  5. use FindBin;
  6. use lib "$FindBin::Bin/lib";
  7. use Getopt::Long;
  8. use Cwd qw(abs_path getcwd);
  9. use File::Spec;
  10. use File::Find;
  11. use File::Copy;
  12. use User::pwent;
  13. use POSIX qw(getuid);
  14. use Module::Load::Conditional 'can_load';
  15. use Tie::File;
  16. use NIC::Bridge::Context (PROMPT => \&nicPrompt);
  17. use NIC::Formats::NICTar;
  18. use NIC::NICType;
  19. our $savedStdout = *STDOUT;
  20. my @_dirs = File::Spec->splitdir(abs_path($FindBin::Bin));
  21. $_dirs[$#_dirs]="templates";
  22. our $_templatepath = File::Spec->catdir(@_dirs);
  23. $#_dirs--;
  24. my $_theospath = File::Spec->catdir(@_dirs);
  25. {
  26. my $_abstheospath = abs_path($_theospath);
  27. my $_cwd = abs_path(Cwd::getcwd());
  28. $_abstheospath .= '/' if $_abstheospath !~ /\/$/;
  29. $_cwd .= '/' if $_cwd !~ /\/$/;
  30. exitWithError("Cowardly refusing to make a project inside \$THEOS ($_abstheospath)") if($_cwd =~ /^$_abstheospath/);
  31. }
  32. my %CONFIG = (link_theos => 0);
  33. loadConfig();
  34. my $clean_project_name = "";
  35. my $project_name = "";
  36. my $package_prefix = $CONFIG{'package_prefix'};
  37. $package_prefix = "com.yourcompany" if !$package_prefix;
  38. my $package_name = "";
  39. my $username = $CONFIG{'username'};
  40. $username = "" if !$username;
  41. my $template = undef;
  42. my $nicfile = undef;
  43. Getopt::Long::Configure("bundling");
  44. GetOptions( "packagename|p=s" => \$package_name,
  45. "name|n=s" => \$project_name,
  46. "user|u=s" => \$username,
  47. "nic=s" => \$nicfile,
  48. "template|t=s" => \$template);
  49. $project_name = $ARGV[0] if($ARGV[0]);
  50. my $_versionstring = "NIC $VER - New Instance Creator";
  51. print $_versionstring,$/;
  52. print "-" x length($_versionstring),$/;
  53. my $NIC;
  54. if($nicfile) {
  55. $NIC = _loadNIC($nicfile) if $nicfile && -f $nicfile;
  56. } else {
  57. my @templates = getTemplates();
  58. if(scalar @templates == 0) {
  59. exitWithError("No file specified with --nic and no templates found.");
  60. }
  61. if($template) {
  62. my @matched = grep { $_->name eq $template } @templates;
  63. $NIC = $matched[0] if(scalar @matched > 0);
  64. }
  65. if(!$NIC) {
  66. $NIC = promptList(undef, "Choose a Template (required)", sub { local $_ = shift; return $_->name; }, @templates);
  67. }
  68. }
  69. exitWithError("No NIC file loaded.") if !$NIC;
  70. promptIfMissing(\$project_name, undef, "Project Name (required)");
  71. exitWithError("I can't live without a project name! Aieeee!") if !$project_name;
  72. $clean_project_name = cleanProjectName($project_name);
  73. $package_name = $package_prefix.".".packageNameIze($project_name) if $CONFIG{'skip_package_name'};
  74. promptIfMissing(\$package_name, $package_prefix.".".packageNameIze($project_name), "Package Name") unless $NIC->variableIgnored("PACKAGENAME");
  75. promptIfMissing(\$username, getUserName(), "Author/Maintainer Name") unless $NIC->variableIgnored("USER");
  76. my $directory = lc($clean_project_name);
  77. if(-d $directory) {
  78. my $response;
  79. promptIfMissing(\$response, "N", "There's already something in $directory. Continue");
  80. exit 1 if(uc($response) eq "N");
  81. }
  82. $NIC->variable("FULLPROJECTNAME") = $project_name;
  83. $NIC->variable("PROJECTNAME") = $clean_project_name;
  84. $NIC->variable("PACKAGENAME") = $package_name;
  85. $NIC->variable("USER") = $username;
  86. if(! -e "control" && ! -e "layout/DEBIAN/control") {
  87. $NIC->addConstraint("package");
  88. }
  89. foreach my $prompt ($NIC->prompts) {
  90. nicPrompt($NIC, $prompt->{name}, $prompt->{prompt}, $prompt->{default});
  91. }
  92. my $cwd = abs_path(getcwd());
  93. # Add theos symlink to the template, if necessary
  94. if($CONFIG{'link_theos'} != 0 && !$NIC->variableIgnored("THEOS")) {
  95. $NIC->addConstraint("link_theos");
  96. my $template_theos_reference = $NIC->_getContentWithoutCreate("theos");
  97. if(!$template_theos_reference || $template_theos_reference->type == NIC::NICType::TYPE_UNKNOWN) {
  98. print STDERR "[warning] Asked to link theos, but template lacks an optional theos link. Creating one! Contact the author of this template about this issue.",$/;
  99. $NIC->registerSymlink("theos", '@@THEOS_PATH@@');
  100. }
  101. my $theosLinkPath = $CONFIG{'theos_path'};
  102. $theosLinkPath = readlink("$cwd/theos") if !$theosLinkPath && (-l "$cwd/theos") && !$CONFIG{'ignore_parent_theos'};
  103. $theosLinkPath = "$cwd/theos" if !$theosLinkPath && (-d "$cwd/theos") && !$CONFIG{'ignore_parent_theos'};
  104. $theosLinkPath = $_theospath if !$theosLinkPath;
  105. # Set @@THEOS@@ to 'theos', so that the project refers to its linked copy of theos.
  106. $NIC->variable("THEOS") = "theos";
  107. $NIC->variable("THEOS_PATH") = $theosLinkPath;
  108. } else {
  109. # Trust that the user knows what he's doing and set @@THEOS@@ to $(THEOS). (or whatever the user prefers)
  110. my $theosLocalName = '$(THEOS)';
  111. $theosLocalName = $CONFIG{"theos_local_name"} if $CONFIG{"theos_local_name"};
  112. $NIC->variable("THEOS") = $theosLocalName;
  113. }
  114. # Execute control script.
  115. $NIC->exec or exitWithError("Failed to build template '".$NIC->name."'.");
  116. print "Instantiating ".$NIC->name." in ".lc($clean_project_name)."/...",$/;
  117. my $dirname = lc($clean_project_name);
  118. $NIC->build($dirname);
  119. chdir($cwd);
  120. my @makefiles = ("GNUmakefile", "makefile", "Makefile");
  121. my $makefile;
  122. map { $makefile = $_ if -e $_; } @makefiles;
  123. if($makefile) {
  124. tie(my @lines, 'Tie::File', $makefile);
  125. my $hasCommon = 0;
  126. map {$hasCommon++ if /common\.mk/;} @lines;
  127. if($hasCommon > 0) {
  128. my $alreadyHas = 0;
  129. map {$alreadyHas++ if /^\s*SUBPROJECTS.*$dirname/;} @lines;
  130. if($alreadyHas == 0) {
  131. print "Adding '$project_name' as an aggregate subproject in Theos makefile '$makefile'.",$/;
  132. my $newline = "SUBPROJECTS += $dirname";
  133. my $i = 0;
  134. my $aggLine = -1;
  135. map {$aggLine = $i if /aggregate\.mk/; $i++;} @lines;
  136. if($aggLine == -1) {
  137. push(@lines, $newline);
  138. push(@lines, "include \$(THEOS_MAKE_PATH)/aggregate.mk");
  139. } else {
  140. splice(@lines, $aggLine, 0, $newline);
  141. }
  142. }
  143. }
  144. untie(@lines);
  145. }
  146. print "Done.",$/;
  147. sub promptIfMissing {
  148. my $vref = shift;
  149. return if(${$vref});
  150. my $default = shift;
  151. my $prompt = shift;
  152. if($default) {
  153. print $::savedStdout $prompt, " [$default]: ";
  154. } else {
  155. print $::savedStdout $prompt, ": ";
  156. }
  157. $| = 1; $_ = <STDIN>;
  158. chomp;
  159. if($default) {
  160. ${$vref} = $_ ? $_ : $default;
  161. } else {
  162. ${$vref} = $_;
  163. }
  164. }
  165. sub promptList {
  166. my $default = shift;
  167. my $prompt = shift;
  168. my $formatsub = shift // sub { shift; };
  169. my @list = @_;
  170. $default = -1 if(!defined $default);
  171. for(0..$#list) { print " ".($_==$default?">":" ")."[".($_+1).".] ",$formatsub->($list[$_]),$/; }
  172. print $prompt,": ";
  173. $| = 1;
  174. my $idx = -1;
  175. while(<STDIN>) {
  176. chomp;
  177. if($default > -1 && $_ eq "") {
  178. $idx = $default;
  179. last;
  180. }
  181. if($_ < 1 || $_ > $#list+1) {
  182. print "Invalid value.",$/,$prompt,": ";
  183. next;
  184. }
  185. $idx = $_-1;
  186. last;
  187. }
  188. return $list[$idx];
  189. }
  190. sub exitWithError {
  191. my $error = shift;
  192. print STDERR "[error] ", $error, $/;
  193. exit 1;
  194. }
  195. sub _loadNIC {
  196. my $nicfile = shift;
  197. open(my $nichandle, "<", $nicfile);
  198. my $line = <$nichandle>;
  199. seek($nichandle, 0, 0);
  200. (my $prettyname = $nicfile) =~ s/$::_templatepath\/(.*)\.nic(\.tar)?/$1/g;
  201. $prettyname .= " (unnamed template)";
  202. my $nicversion = 1;
  203. my $NIC = undef;
  204. if($line =~ /^nic (\w+)$/) {
  205. $nicversion = $1;
  206. my $NICPackage = "NIC$nicversion";
  207. return undef if(!can_load(modules => {"NIC::Formats::$NICPackage" => undef}));
  208. $NIC = "NIC::Formats::$NICPackage"->new($nichandle, $prettyname);
  209. } else {
  210. $NIC = NIC::Formats::NICTar->new($nichandle, $prettyname);
  211. }
  212. close($nichandle);
  213. return $NIC;
  214. }
  215. sub getTemplates {
  216. our @templates = ();
  217. find({wanted => \&templateWanted, no_chdir => 1}, $_templatepath);
  218. sub templateWanted {
  219. if(-f && (/\.nic$/ || /\.nic\.tar$/)) {
  220. my $nic = _loadNIC($_);
  221. push(@templates, $nic) if $nic;
  222. }
  223. }
  224. return sort { $a->name cmp $b->name } @templates;
  225. }
  226. sub packageNameIze {
  227. my $name = shift;
  228. $name =~ s/ //g;
  229. $name =~ s/[^\w\+-.]//g;
  230. return lc($name);
  231. }
  232. sub cleanProjectName {
  233. my $name = shift;
  234. $name =~ s/ //g;
  235. $name =~ s/\W//g;
  236. return $name;
  237. }
  238. sub getUserName {
  239. my $pw = getpw(getuid());
  240. my ($fullname) = split(/\s*,\s*/, $pw->gecos);
  241. return $fullname ? $fullname : $pw->name;
  242. }
  243. sub getHomeDir {
  244. my $pw = getpw(getuid());
  245. return $pw->dir;
  246. }
  247. sub loadConfig {
  248. open(my $cfh, "<", getHomeDir()."/.nicrc") or return;
  249. while(<$cfh>) {
  250. if(/^(.+?)\s*=\s*\"(.*)\"$/) {
  251. my $key = $1;
  252. my $value = $2;
  253. $CONFIG{$key} = $value;
  254. }
  255. }
  256. }
  257. sub nicPrompt {
  258. # Do we want to import these variables into the NIC automatically? In the format name.VARIABLE?
  259. # If so, this could become awesome. We could $NIC->get($prompt->{name})
  260. # and have loaded the variables in a loop beforehand.
  261. # This would also allow the user to set certain variables (package prefix, username) for different templates.
  262. my ($nic, $variable, $prompt, $default) = @_;
  263. my $response = undef;
  264. $response = $CONFIG{$nic->name().".".$variable} if($variable);
  265. promptIfMissing(\$response, $default, "[".$nic->name."] ".$prompt);
  266. $NIC->variable($variable) = $response if $variable;
  267. # Return the response for anybody who's interested.
  268. $response;
  269. }