1<?php
2/**
3 * This file is part of phpDocumentor.
4 *
5 * For the full copyright and license information, please view the LICENSE
6 * file that was distributed with this source code.
7 *
8 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
9 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10 * @link      http://phpdoc.org
11 */
12
13namespace phpDocumentor\Reflection\DocBlock;
14
15use phpDocumentor\Reflection\DocBlock\Tags\Example;
16
17/**
18 * Class used to find an example file's location based on a given ExampleDescriptor.
19 */
20class ExampleFinder
21{
22    /** @var string */
23    private $sourceDirectory = '';
24
25    /** @var string[] */
26    private $exampleDirectories = [];
27
28    /**
29     * Attempts to find the example contents for the given descriptor.
30     *
31     * @param Example $example
32     *
33     * @return string
34     */
35    public function find(Example $example)
36    {
37        $filename = $example->getFilePath();
38
39        $file = $this->getExampleFileContents($filename);
40        if (!$file) {
41            return "** File not found : {$filename} **";
42        }
43
44        return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount()));
45    }
46
47    /**
48     * Registers the project's root directory where an 'examples' folder can be expected.
49     *
50     * @param string $directory
51     *
52     * @return void
53     */
54    public function setSourceDirectory($directory = '')
55    {
56        $this->sourceDirectory = $directory;
57    }
58
59    /**
60     * Returns the project's root directory where an 'examples' folder can be expected.
61     *
62     * @return string
63     */
64    public function getSourceDirectory()
65    {
66        return $this->sourceDirectory;
67    }
68
69    /**
70     * Registers a series of directories that may contain examples.
71     *
72     * @param string[] $directories
73     */
74    public function setExampleDirectories(array $directories)
75    {
76        $this->exampleDirectories = $directories;
77    }
78
79    /**
80     * Returns a series of directories that may contain examples.
81     *
82     * @return string[]
83     */
84    public function getExampleDirectories()
85    {
86        return $this->exampleDirectories;
87    }
88
89    /**
90     * Attempts to find the requested example file and returns its contents or null if no file was found.
91     *
92     * This method will try several methods in search of the given example file, the first one it encounters is
93     * returned:
94     *
95     * 1. Iterates through all examples folders for the given filename
96     * 2. Checks the source folder for the given filename
97     * 3. Checks the 'examples' folder in the current working directory for examples
98     * 4. Checks the path relative to the current working directory for the given filename
99     *
100     * @param string $filename
101     *
102     * @return string|null
103     */
104    private function getExampleFileContents($filename)
105    {
106        $normalizedPath = null;
107
108        foreach ($this->exampleDirectories as $directory) {
109            $exampleFileFromConfig = $this->constructExamplePath($directory, $filename);
110            if (is_readable($exampleFileFromConfig)) {
111                $normalizedPath = $exampleFileFromConfig;
112                break;
113            }
114        }
115
116        if (!$normalizedPath) {
117            if (is_readable($this->getExamplePathFromSource($filename))) {
118                $normalizedPath = $this->getExamplePathFromSource($filename);
119            } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) {
120                $normalizedPath = $this->getExamplePathFromExampleDirectory($filename);
121            } elseif (is_readable($filename)) {
122                $normalizedPath = $filename;
123            }
124        }
125
126        return $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : null;
127    }
128
129    /**
130     * Get example filepath based on the example directory inside your project.
131     *
132     * @param string $file
133     *
134     * @return string
135     */
136    private function getExamplePathFromExampleDirectory($file)
137    {
138        return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file;
139    }
140
141    /**
142     * Returns a path to the example file in the given directory..
143     *
144     * @param string $directory
145     * @param string $file
146     *
147     * @return string
148     */
149    private function constructExamplePath($directory, $file)
150    {
151        return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file;
152    }
153
154    /**
155     * Get example filepath based on sourcecode.
156     *
157     * @param string $file
158     *
159     * @return string
160     */
161    private function getExamplePathFromSource($file)
162    {
163        return sprintf(
164            '%s%s%s',
165            trim($this->getSourceDirectory(), '\\/'),
166            DIRECTORY_SEPARATOR,
167            trim($file, '"')
168        );
169    }
170}
171