1<?php
2
3final class ArcanistLiberateWorkflow
4  extends ArcanistArcWorkflow {
5
6  public function getWorkflowName() {
7    return 'liberate';
8  }
9
10  public function getWorkflowInformation() {
11    // TOOLSETS: Expand this help.
12
13    $help = pht(<<<EOTEXT
14Create or update an Arcanist library.
15EOTEXT
16);
17
18    return $this->newWorkflowInformation()
19      ->setSynopsis(
20        pht('Create or update an Arcanist library.'))
21      ->addExample(pht('**liberate**'))
22      ->addExample(pht('**liberate** [__path__]'))
23      ->setHelp($help);
24  }
25
26  public function getWorkflowArguments() {
27    return array(
28      $this->newWorkflowArgument('clean')
29        ->setHelp(
30          pht('Perform a clean rebuild, ignoring caches. Thorough, but slow.')),
31      $this->newWorkflowArgument('argv')
32        ->setWildcard(true)
33        ->setIsPathArgument(true),
34    );
35  }
36
37  protected function newPrompts() {
38    return array(
39      $this->newPrompt('arc.liberate.create')
40        ->setDescription(
41          pht(
42            'Confirms creation of a new library.')),
43    );
44  }
45
46
47  public function runWorkflow() {
48    $log = $this->getLogEngine();
49
50    $argv = $this->getArgument('argv');
51    if (count($argv) > 1) {
52      throw new ArcanistUsageException(
53        pht(
54          'Provide only one path to "arc liberate". The path should identify '.
55          'a directory where you want to create or update a library.'));
56    } else if (!$argv) {
57      $log->writeStatus(
58        pht('SCAN'),
59        pht('Searching for libraries in the current working directory...'));
60
61      $init_files = id(new FileFinder(getcwd()))
62        ->withPath('*/__phutil_library_init__.php')
63        ->find();
64
65      if (!$init_files) {
66        throw new ArcanistUsageException(
67          pht(
68            'Unable to find any libraries under the current working '.
69            'directory. To create a library, provide a path.'));
70      }
71
72      $paths = array();
73      foreach ($init_files as $init) {
74        $paths[] = Filesystem::resolvePath(dirname($init));
75      }
76    } else {
77      $paths = array(
78        Filesystem::resolvePath(head($argv)),
79      );
80    }
81
82    foreach ($paths as $path) {
83      $log->writeStatus(
84        pht('WORK'),
85        pht(
86          'Updating library: %s',
87          Filesystem::readablePath($path).DIRECTORY_SEPARATOR));
88      $this->liberatePath($path);
89    }
90
91    $log->writeSuccess(
92      pht('DONE'),
93      pht('Updated %s librarie(s).', phutil_count($paths)));
94
95    return 0;
96  }
97
98  private function liberatePath($path) {
99    if (!Filesystem::pathExists($path.'/__phutil_library_init__.php')) {
100      echo tsprintf(
101        "%s\n",
102        pht(
103          'No library currently exists at the path "%s"...',
104          $path));
105      $this->liberateCreateDirectory($path);
106      $this->liberateCreateLibrary($path);
107      return;
108    }
109
110    $version = $this->getLibraryFormatVersion($path);
111    switch ($version) {
112      case 1:
113        throw new ArcanistUsageException(
114          pht(
115            'This very old library is no longer supported.'));
116      case 2:
117        return $this->liberateVersion2($path);
118      default:
119        throw new ArcanistUsageException(
120          pht("Unknown library version '%s'!", $version));
121    }
122
123    echo tsprintf("%s\n", pht('Done.'));
124  }
125
126  private function getLibraryFormatVersion($path) {
127    $map_file = $path.'/__phutil_library_map__.php';
128    if (!Filesystem::pathExists($map_file)) {
129      // Default to library v1.
130      return 1;
131    }
132
133    $map = Filesystem::readFile($map_file);
134
135    $matches = null;
136    if (preg_match('/@phutil-library-version (\d+)/', $map, $matches)) {
137      return (int)$matches[1];
138    }
139
140    return 1;
141  }
142
143  private function liberateVersion2($path) {
144    $bin = $this->getScriptPath('support/lib/rebuild-map.php');
145
146    $argv = array();
147    if ($this->getArgument('clean')) {
148      $argv[] = '--drop-cache';
149    }
150
151    return phutil_passthru(
152      '%%PHP_CMD%% -f %R -- %Ls %R',
153      $bin,
154      $argv,
155      $path);
156  }
157
158  private function liberateCreateDirectory($path) {
159    if (Filesystem::pathExists($path)) {
160      if (!is_dir($path)) {
161        throw new ArcanistUsageException(
162          pht(
163            'Provide a directory to create or update a libphutil library in.'));
164      }
165      return;
166    }
167
168    echo tsprintf(
169      "%!\n%W\n",
170      pht('NEW LIBRARY'),
171      pht(
172        'The directory "%s" does not exist. Do you want to create it?',
173        $path));
174
175    $query = pht('Create new library?');
176
177    $this->getPrompt('arc.liberate.create')
178      ->setQuery($query)
179      ->execute();
180
181    execx('mkdir -p %R', $path);
182  }
183
184  private function liberateCreateLibrary($path) {
185    $init_path = $path.'/__phutil_library_init__.php';
186    if (Filesystem::pathExists($init_path)) {
187      return;
188    }
189
190    echo pht("Creating new libphutil library in '%s'.", $path)."\n";
191
192    do {
193      echo pht('Choose a name for the new library.')."\n";
194      $name = phutil_console_prompt(
195        pht('What do you want to name this library?'));
196
197      if (preg_match('/^[a-z-]+$/', $name)) {
198        break;
199      } else {
200        echo phutil_console_format(
201          "%s\n",
202          pht(
203          'Library name should contain only lowercase letters and hyphens.'));
204      }
205    } while (true);
206
207    $template =
208      "<?php\n\n".
209      "phutil_register_library('{$name}', __FILE__);\n";
210
211    echo pht(
212      "Writing '%s' to '%s'...\n",
213      '__phutil_library_init__.php',
214      $path);
215    Filesystem::writeFile($init_path, $template);
216    $this->liberateVersion2($path);
217  }
218
219
220  private function getScriptPath($script) {
221    $root = dirname(phutil_get_library_root('arcanist'));
222    return $root.'/'.$script;
223  }
224
225}
226