PageRenderTime 28ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/application/tasks/generate.php

https://bitbucket.org/Maron1/taqman
PHP | 854 lines | 427 code | 172 blank | 255 comment | 54 complexity | 50776ea70698b009e7bb5350dc9caaef MD5 | raw file
  1. <?php
  2. /**
  3. * Laravel Generator
  4. *
  5. * Rapidly create models, views, migrations + schema, assets, tests, etc.
  6. *
  7. * USAGE:
  8. * Add this file to your Laravel application/tasks directory
  9. * and call the methods with: php artisan generate:[model|controller|migration] [args]
  10. *
  11. * See individual methods for additional usage instructions.
  12. *
  13. * @author Jeffrey Way <jeffrey@jeffrey-way.com>
  14. * @license haha - whatever you want.
  15. * @version 0.8
  16. * @since July 26, 2012
  17. *
  18. */
  19. class Generate_Task
  20. {
  21. /*
  22. * Set these paths to the location of your assets.
  23. */
  24. public static $css_dir = 'css/';
  25. public static $sass_dir = 'css/sass/';
  26. public static $less_dir = 'css/less/';
  27. public static $js_dir = 'js/';
  28. public static $coffee_dir = 'js/coffee/';
  29. /*
  30. * The content for the generate file
  31. */
  32. public static $content;
  33. /**
  34. * As a convenience, fetch popular assets for user
  35. * php artisan generate:assets jquery.js <---
  36. */
  37. public static $external_assets = array(
  38. // JavaScripts
  39. 'jquery.js' => 'http://code.jquery.com/jquery.js',
  40. 'backbone.js' => 'http://backbonejs.org/backbone.js',
  41. 'underscore.js' => 'http://underscorejs.org/underscore.js',
  42. 'handlebars.js' => 'http://cloud.github.com/downloads/wycats/handlebars.js/handlebars-1.0.rc.1.js',
  43. // CSS
  44. 'normalize.css' => 'https://raw.github.com/necolas/normalize.css/master/normalize.css'
  45. );
  46. /**
  47. * Time Savers
  48. *
  49. */
  50. public function c($args) { return $this->controller($args); }
  51. public function m($args) { return $this->model($args); }
  52. public function mig($args) { return $this->migration($args); }
  53. public function v($args) { return $this->view($args); }
  54. public function a($args) { return $this->assets($args); }
  55. public function t($args) { return $this->test($args); }
  56. public function r($args) { return $this->resource($args); }
  57. /**
  58. * Simply echos out some help info.
  59. *
  60. */
  61. public function help() { $this->run(); }
  62. public function run()
  63. {
  64. echo <<<EOT
  65. \n=============GENERATOR COMMANDS=============\n
  66. generate:controller [name] [methods]
  67. generate:model [name]
  68. generate:view [name]
  69. generate:migration [name] [field:type]
  70. generate:test [name] [methods]
  71. generate:assets [asset]
  72. generate:resource [name] [methods/views]
  73. \n=====================END====================\n
  74. EOT;
  75. }
  76. /**
  77. * Generate a controller file with optional actions.
  78. *
  79. * USAGE:
  80. *
  81. * php artisan generate:controller Admin
  82. * php artisan generate:controller Admin index edit
  83. * php artisan generate:controller Admin index index:post restful
  84. *
  85. * @param $args array
  86. * @return string
  87. */
  88. public function controller($args)
  89. {
  90. if ( empty($args) ) {
  91. echo "Error: Please supply a class name, and your desired methods.\n";
  92. return;
  93. }
  94. // Name of the class and file
  95. $class_name = str_replace('.', '/', ucwords(array_shift($args)));
  96. // Where will this file be stored?
  97. $file_path = $this->path('controllers') . strtolower("$class_name.php");
  98. // Admin/panel => Admin_Panel
  99. $class_name = $this->prettify_class_name($class_name);
  100. // Begin building up the file's content
  101. Template::new_class($class_name . '_Controller', 'Base_Controller');
  102. $content = '';
  103. // Let's see if they added "restful" anywhere in the args.
  104. if ( $restful = $this->is_restful($args) ) {
  105. $args = array_diff($args, array('restful'));
  106. $content .= 'public $restful = true;';
  107. }
  108. // Now we filter through the args, and create the funcs.
  109. foreach($args as $method) {
  110. // Were params supplied? Like index:post?
  111. if ( strpos($method, ':') !== false ) {
  112. list($method, $verb) = explode(':', $method);
  113. $content .= Template::func("{$verb}_{$method}");
  114. } else {
  115. $action = $restful ? 'get' : 'action';
  116. $content .= Template::func("{$action}_{$method}");
  117. }
  118. }
  119. // Add methods/actions to class.
  120. Content::add_after('{', $content);
  121. // Prettify
  122. $this->prettify();
  123. // Create the file
  124. $this->write_to_file($file_path);
  125. }
  126. /**
  127. * Generate a model file + boilerplate. (To be expanded.)
  128. *
  129. * USAGE
  130. *
  131. * php artisan generate:model User
  132. *
  133. * @param $args array
  134. * @return string
  135. */
  136. public function model($args)
  137. {
  138. // Name of the class and file
  139. $class_name = is_array($args) ? ucwords($args[0]) : ucwords($args);
  140. $file_path = $this->path('models') . strtolower("$class_name.php");
  141. // Begin building up the file's content
  142. Template::new_class($class_name, 'Eloquent' );
  143. $this->prettify();
  144. // Create the file
  145. $this->write_to_file($file_path);
  146. }
  147. /**
  148. * Generate a migration file + schema
  149. *
  150. * INSTRUCTIONS:
  151. * - Separate each word with an underscore
  152. * - Name your migrations according to what you're doing
  153. * - Try to use the `table` keyword, to hint at the table name: create_users_table
  154. * - Use the `add`, `create`, `update` and `delete` keywords, according to your needs.
  155. * - For each field, specify its name and type: id:integer, or body:text
  156. * - You may also specify additional options, like: age:integer:nullable, or email:string:unique
  157. *
  158. *
  159. * USAGE OPTIONS
  160. *
  161. * php artisan generate:migration create_users_table
  162. * php artisan generate:migration create_users_table id:integer email:string:unique age:integer:nullable
  163. * php artisan generate:migration add_user_id_to_posts_table user_id:integer
  164. * php artisan generate:migration delete_active_from_users_table active:boolean
  165. *
  166. * @param $args array
  167. * @return string
  168. */
  169. public function migration($args)
  170. {
  171. if ( empty($args) ) {
  172. echo "Error: Please provide a name for your migration.\n";
  173. return;
  174. }
  175. $class_name = array_shift($args);
  176. // Determine what the table name should be.
  177. $table_name = $this->parse_table_name($class_name);
  178. // Capitalize where necessary: a_simple_string => A_Simple_String
  179. $class_name = implode('_', array_map('ucwords', explode('_', $class_name)));
  180. // Let's create the path to where the migration will be stored.
  181. $file_path = $this->path('migrations') . date('Y_m_d_His') . strtolower("_$class_name.php");
  182. $this->generate_migration($class_name, $table_name, $args);
  183. return $this->write_to_file($file_path);
  184. }
  185. /**
  186. * Create any number of views
  187. *
  188. * USAGE:
  189. *
  190. * php artisan generate:view home show
  191. * php artisan generate:view home.index home.show
  192. *
  193. * @param $args array
  194. * @return void
  195. */
  196. public function view($paths)
  197. {
  198. if ( empty($paths) ) {
  199. echo "Warning: no views were specified. Add some!\n";
  200. return;
  201. }
  202. foreach( $paths as $path ) {
  203. $file_path = $this->path('views') . str_replace('.', '/', $path) . '.blade.php';
  204. self::$content = "This is the $file_path view";
  205. $this->write_to_file($file_path);
  206. }
  207. }
  208. /**
  209. * Create assets in the public directory
  210. *
  211. * USAGE:
  212. * php artisan generate:assets style1.css some_module.js
  213. *
  214. * @param $assets array
  215. * @return void
  216. */
  217. public function asset($assets) { return $this->assets($assets); }
  218. public function assets($assets)
  219. {
  220. if( empty($assets) ) {
  221. echo "Please specify the assets that you would like to create.";
  222. return;
  223. }
  224. foreach( $assets as $asset ) {
  225. // What type of file? CSS, JS?
  226. $ext = File::extension($asset);
  227. if( !$ext ) {
  228. // Hmm - not sure what to do.
  229. echo "Warning: Could not determine file type. Please specify an extension.";
  230. continue;
  231. }
  232. // Set the path, dependent upon the file type.
  233. switch ($ext) {
  234. case 'js':
  235. $path = self::$js_dir . $asset;
  236. break;
  237. case 'coffee':
  238. $path = self::$coffee_dir . $asset;
  239. break;
  240. case 'scss':
  241. case 'sass':
  242. $path = self::$sass_dir . $asset;
  243. break;
  244. case 'less':
  245. $path = self::$less_dir . $asset;
  246. break;
  247. case 'css':
  248. default:
  249. $path = self::$css_dir . $asset;
  250. break;
  251. }
  252. if ( $this->is_external_asset($asset) ) {
  253. $this->fetch($asset);
  254. } else { self::$content = ''; }
  255. $this->write_to_file(path('public') . $path, '');
  256. }
  257. }
  258. /**
  259. * Create PHPUnit test classes with optional methods
  260. *
  261. * USAGE:
  262. *
  263. * php artisan generate:test membership
  264. * php artisan generate:test membership can_disable_user can_reset_user_password
  265. *
  266. * @param $args array
  267. * @return void
  268. */
  269. public function test($args)
  270. {
  271. if ( empty($args) ) {
  272. echo "Please specify a name for your test class.\n";
  273. return;
  274. }
  275. $class_name = ucwords(array_shift($args));
  276. $file_path = $this->path('tests');
  277. if ( isset($this->should_include_tests) ) {
  278. $file_path .= 'controllers/';
  279. }
  280. $file_path .= strtolower("{$class_name}.test.php");
  281. // Begin building up the file's content
  282. Template::new_class($class_name . '_Test', 'PHPUnit_Framework_TestCase');
  283. // add the functions
  284. $tests = '';
  285. foreach($args as $test) {
  286. // Don't worry about tests for non-get methods for now.
  287. if ( strpos($test, ':') !== false ) continue;
  288. if ( $test === 'restful' ) continue;
  289. // make lower case
  290. $func = Template::func("test_{$test}");
  291. // Only if we're generating a resource.
  292. if ( isset($this->should_include_tests) ) {
  293. $func = Template::test($class_name, $test);
  294. }
  295. $tests .= $func;
  296. }
  297. // add funcs to class
  298. Content::add_after('{', $tests);
  299. // Create the file
  300. $this->write_to_file($file_path, $this->prettify());
  301. }
  302. /**
  303. * Determines whether the asset that the user wants is
  304. * contained with the external assets array
  305. *
  306. * @param $assets string
  307. * @return boolean
  308. */
  309. protected function is_external_asset($asset)
  310. {
  311. return array_key_exists(strtolower($asset), static::$external_assets);
  312. }
  313. /**
  314. * Fetch external asset
  315. *
  316. * @param $url string
  317. * @return string
  318. */
  319. protected function fetch($url)
  320. {
  321. self::$content = file_get_contents(static::$external_assets[$url]);
  322. return self::$content;
  323. }
  324. /**
  325. * Prepares the $name of the class
  326. * Admin/panel => Admin_Panel
  327. *
  328. * @param $class_name string
  329. */
  330. protected function prettify_class_name($class_name)
  331. {
  332. return preg_replace_callback('/\/([a-zA-Z])/', function($m) {
  333. return "_" . strtoupper($m[1]);
  334. }, $class_name);
  335. }
  336. /**
  337. * Creates the content for the migration file.
  338. *
  339. * @param $class_name string
  340. * @param $table_name string
  341. * @param $args array
  342. * @return void
  343. */
  344. protected function generate_migration($class_name, $table_name, $args)
  345. {
  346. // Figure out what type of event is occuring. Create, Delete, Add?
  347. list($table_action, $table_event) = $this->parse_action_type($class_name);
  348. // Now, we begin creating the contents of the file.
  349. Template::new_class($class_name);
  350. /* The Migration Up Function */
  351. $up = $this->migration_up($table_event, $table_action, $table_name, $args);
  352. /* The Migration Down Function */
  353. $down = $this->migration_down($table_event, $table_action, $table_name, $args);
  354. // Add both the up and down function to the migration class.
  355. Content::add_after('{', $up . $down);
  356. return $this->prettify();
  357. }
  358. protected function migration_up($table_event, $table_action, $table_name, $args)
  359. {
  360. $up = Template::func('up');
  361. // Insert a new schema function into the up function.
  362. $up = $this->add_after('{', Template::schema($table_action, $table_name), $up);
  363. // Create the field rules for for the schema
  364. if ( $table_event === 'create' ) {
  365. $fields = $this->set_column('increments', 'id') . ';';
  366. $fields .= $this->add_columns($args);
  367. $fields .= $this->set_column('timestamps', null) . ';';
  368. }
  369. else if ( $table_event === 'delete' ) {
  370. $fields = $this->drop_columns($args);
  371. }
  372. else if ( $table_event === 'add' || $table_event === 'update' ) {
  373. $fields = $this->add_columns($args);
  374. }
  375. // Insert the fields into the schema function
  376. return $this->add_after('function($table) {', $fields, $up);
  377. }
  378. protected function migration_down($table_event, $table_action, $table_name, $args)
  379. {
  380. $down = Template::func('down');
  381. if ( $table_event === 'create' ) {
  382. $schema = Template::schema('drop', $table_name, false);
  383. // Add drop schema into down function
  384. $down = $this->add_after('{', $schema, $down);
  385. } else {
  386. // for delete, add, and update
  387. $schema = Template::schema('table', $table_name);
  388. }
  389. if ( $table_event === 'delete' ) {
  390. $fields = $this->add_columns($args);
  391. // add fields to schema
  392. $schema = $this->add_after('function($table) {', $fields, $schema);
  393. // add schema to down function
  394. $down = $this->add_after('{', $schema, $down);
  395. }
  396. else if ( $table_event === 'add' ) {
  397. $fields = $this->drop_columns($args);
  398. // add fields to schema
  399. $schema = $this->add_after('function($table) {', $fields, $schema);
  400. // add schema to down function
  401. $down = $this->add_after('{', $schema, $down);
  402. }
  403. else if ( $table_event === 'update' ) {
  404. // add schema to down function
  405. $down = $this->add_after('{', $schema, $down);
  406. }
  407. return $down;
  408. }
  409. /**
  410. * Generate resource (model, controller, and views)
  411. *
  412. * @param $args array
  413. * @return void
  414. */
  415. public function resource($args)
  416. {
  417. if ( $this->should_include_tests($args) ) {
  418. $args = array_diff($args, array('with_tests'));
  419. }
  420. // Pluralize controller name
  421. if ( !preg_match('/admin|config/', $args[0]) ) {
  422. $args[0] = Str::plural($args[0]);
  423. }
  424. // If only the resource name was provided, let's build out the full resource map.
  425. if ( count($args) === 1 ) {
  426. $args = array($args[0], 'index', 'index:post', 'show', 'edit', 'new', 'update:put', 'destroy:delete', 'restful');
  427. }
  428. $this->controller($args);
  429. // Singular for everything else
  430. $resource_name = Str::singular(array_shift($args));
  431. // Should we include tests?
  432. if ( isset($this->should_include_tests) ) {
  433. $this->test(array_merge(array(Str::plural($resource_name)), $args));
  434. }
  435. if ( $this->is_restful($args) ) {
  436. // Remove that restful item from the array. No longer needed.
  437. $args = array_diff($args, array('restful'));
  438. }
  439. $args = $this->determine_views($args);
  440. // Let's take any supplied view names, and set them
  441. // in the resource name's directory.
  442. $views = array_map(function($val) use($resource_name) {
  443. return "{$resource_name}.{$val}";
  444. }, $args);
  445. $this->view($views);
  446. $this->model($resource_name);
  447. }
  448. /**
  449. * Figure out what the name of the table is.
  450. *
  451. * Fetch the value that comes right before "_table"
  452. * Or try to grab the very last word that comes after "_" - create_*users*
  453. * If all else fails, return a generic "TABLE", to be filled in by the user.
  454. *
  455. * @param $class_name string
  456. * @return string
  457. */
  458. protected function parse_table_name($class_name)
  459. {
  460. // Try to figure out the table name
  461. // We'll use the word that comes immediately before "_table"
  462. // create_users_table => users
  463. preg_match('/([a-zA-Z]+)_table/', $class_name, $matches);
  464. if ( empty($matches) ) {
  465. // Or, if the user doesn't write "table", we'll just use
  466. // the text at the end of the string
  467. // create_users => users
  468. preg_match('/_([a-zA-Z]+)$/', $class_name, $matches);
  469. }
  470. // Hmm - I'm stumped. Just use a generic name.
  471. return empty($matches)
  472. ? "TABLE"
  473. : $matches[1];
  474. }
  475. /**
  476. * Write the contents to the specified file
  477. *
  478. * @param $file_path string
  479. * @param $content string
  480. * @param $type string [model|controller|migration]
  481. * @return void
  482. */
  483. protected function write_to_file($file_path, $success = '')
  484. {
  485. $success = $success ?: "Create: $file_path.\n";
  486. if ( File::exists($file_path) ) {
  487. // we don't want to overwrite it
  488. echo "Warning: File already exists at $file_path\n";
  489. return;
  490. }
  491. // As a precaution, let's see if we need to make the folder.
  492. File::mkdir(dirname($file_path));
  493. if ( File::put($file_path, self::$content) !== false ) {
  494. echo $success;
  495. } else {
  496. echo "Whoops - something...erghh...went wrong!\n";
  497. }
  498. }
  499. /**
  500. * Try to determine what type of table action should occur.
  501. * Add, Create, Delete??
  502. *
  503. * @param $class_name string
  504. * @return aray
  505. */
  506. protected function parse_action_type($class_name)
  507. {
  508. // What type of action? Creating a table? Adding a column? Deleting?
  509. if ( preg_match('/delete|update|add(?=_)/i', $class_name, $matches) ) {
  510. $table_action = 'table';
  511. $table_event = strtolower($matches[0]);
  512. } else {
  513. $table_action = $table_event = 'create';
  514. }
  515. return array($table_action, $table_event);
  516. }
  517. protected function increment()
  518. {
  519. return "\$table->increments('id')";
  520. }
  521. protected function set_column($type, $field = '')
  522. {
  523. return empty($field)
  524. ? "\$table->$type()"
  525. : "\$table->$type('$field')";
  526. }
  527. protected function add_option($option)
  528. {
  529. return "->{$option}()";
  530. }
  531. /**
  532. * Add columns
  533. *
  534. * Filters through the provided args, and builds up the schema text.
  535. *
  536. * @param $args array
  537. * @return string
  538. */
  539. protected function add_columns($args)
  540. {
  541. $content = '';
  542. // Build up the schema
  543. foreach( $args as $arg ) {
  544. // Like age, integer, and nullable
  545. @list($field, $type, $setting) = explode(':', $arg);
  546. if ( !$type ) {
  547. echo "There was an error in your formatting. Please try again. Did you specify both a field and data type for each? age:int\n";
  548. die();
  549. }
  550. // Primary key check
  551. if ( $field === 'id' and $type === 'integer' ) {
  552. $rule = $this->increment();
  553. } else {
  554. $rule = $this->set_column($type, $field);
  555. if ( !empty($setting) ) {
  556. $rule .= $this->add_option($setting);
  557. }
  558. }
  559. $content .= $rule . ";";
  560. }
  561. return $content;
  562. }
  563. /**
  564. * Drop Columns
  565. *
  566. * Filters through the args and applies the "drop_column" syntax
  567. *
  568. * @param $args array
  569. * @return string
  570. */
  571. protected function drop_columns($args)
  572. {
  573. $fields = array_map(function($val) {
  574. $bits = explode(':', $val);
  575. return "'$bits[0]'";
  576. }, $args);
  577. if ( count($fields) === 1 ) {
  578. return "\$table->drop_column($fields[0]);";
  579. } else {
  580. return "\$table->drop_column(array(" . implode(', ', $fields) . "));";
  581. }
  582. }
  583. public function path($dir)
  584. {
  585. return path('app') . "$dir/";
  586. }
  587. /**
  588. * Crazy sloppy prettify. TODO - Cleanup
  589. *
  590. * @param $content string
  591. * @return string
  592. */
  593. protected function prettify()
  594. {
  595. $content = self::$content;
  596. $content = str_replace('<?php ', "<?php\n\n", $content);
  597. $content = str_replace('{}', "\n{\n\n}", $content);
  598. $content = str_replace('public', "\n\n\tpublic", $content);
  599. $content = str_replace("() \n{\n\n}", "()\n\t{\n\n\t}", $content);
  600. $content = str_replace('}}', "}\n\n}", $content);
  601. // Migration-Specific
  602. $content = preg_replace('/ ?Schema::/', "\n\t\tSchema::", $content);
  603. $content = preg_replace('/\$table(?!\))/', "\n\t\t\t\$table", $content);
  604. $content = str_replace('});}', "\n\t\t});\n\t}", $content);
  605. $content = str_replace(');}', ");\n\t}", $content);
  606. $content = str_replace("() {", "()\n\t{", $content);
  607. self::$content = $content;
  608. }
  609. public function add_after($where, $to_add, $content)
  610. {
  611. // return preg_replace('/' . $where . '/', $where . $to_add, $content, 1);
  612. return str_replace($where, $where . $to_add, $content);
  613. }
  614. protected function is_restful($args)
  615. {
  616. $restful_pos = array_search('restful', $args);
  617. return $restful_pos !== false;
  618. }
  619. protected function should_include_tests($args)
  620. {
  621. $tests_pos = array_search('with_tests', $args);
  622. if ( $tests_pos !== false ) {
  623. return $this->should_include_tests = true;
  624. }
  625. return false;
  626. }
  627. protected function determine_views($args)
  628. {
  629. $views = array();
  630. foreach($args as $arg) {
  631. $bits = explode(':', $arg);
  632. $name = $bits[0];
  633. if ( isset($bits[1]) && strtolower($bits[1]) === 'get' || !isset($bits[1]) ) {
  634. $views[] = $name;
  635. }
  636. }
  637. return $views;
  638. }
  639. }
  640. class Content {
  641. public static function add_after($where, $to_add)
  642. {
  643. Generate_Task::$content = str_replace($where, $where . $to_add, Generate_Task::$content);
  644. }
  645. }
  646. class Template {
  647. public static function test($class_name, $test)
  648. {
  649. return <<<EOT
  650. public function test_{$test}()
  651. {
  652. \$response = Controller::call('{$class_name}@$test');
  653. \$this->assertEquals('200', \$response->foundation->getStatusCode());
  654. \$this->assertRegExp('/.+/', (string)\$response, 'There should be some content in the $test view.');
  655. }
  656. EOT;
  657. }
  658. public static function func($func_name)
  659. {
  660. return <<<EOT
  661. public function {$func_name}()
  662. {
  663. }
  664. EOT;
  665. }
  666. public static function new_class($name, $extends_class = null)
  667. {
  668. $content = "<?php class $name";
  669. if ( !empty($extends_class) ) {
  670. $content .= " extends $extends_class";
  671. }
  672. $content .= ' {}';
  673. Generate_Task::$content = $content;
  674. }
  675. public static function schema($table_action, $table_name, $cb = true)
  676. {
  677. $content = "Schema::$table_action('$table_name'";
  678. return $cb
  679. ? $content . ', function($table) {});'
  680. : $content . ');';
  681. }
  682. }