summaryrefslogtreecommitdiffstats
path: root/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp
blob: aaa4890855f04f7f9555d0b21f0223f6306d90a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "fileresolver.h"

#include "qdoc/boundaries/filesystem/filepath.h"

#include <QDir>

#include <iostream>
#include <algorithm>

/*!
 * \class FileResolver
 * \brief Encapsulate the logic that QDoc uses to find files whose
 * path is provided by the user and that are relative to the current
 * configuration.
 *
 * A FileResolver instance is configured during creation, defining the
 * root directories that the search should be performed on.
 *
 * Afterwards, it can be used to resolve paths relative to those
 * directories, by querying through the resolve() method.
 *
 * Queries are resolved through a linear search through root
 * directories, finding at most one file each time.
 * A file is considered to be resolved if, from any root directory,
 * the query represents an existing file.
 *
 * For example, consider the following directory structure on some
 * filesystem:
 *
 * \badcode
 * foo/
 * |
 * |-bar/
 * |-|
 * | |-anotherfile.txt
 * |-file.txt
 * \endcode
 *
 * And consider an instance of FileResolver tha considers \e{foo/} to
 * be a root directory for search.
 *
 * Then, queries such as \e {bar/anotherfile.txt} and \e {file.txt}
 * will be resolved.
 *
 * Instead, queries such as \e {foobar.cpp}, \e {bar}, and \e
 * {foo/bar/anotherfile.txt} will not be resolved, as they do not
 * represent any file reachable from a root directory for search.
 *
 * It is important to note that FileResolver always searches its root
 * directories in an order that is based on the lexicographic ordering
 * of the path of its root directories.
 *
 * For example, consider the following directory structure on some
 * filesystem:
 *
 * \badcode
 * foo/
 * |
 * |-bar/
 * |-|
 * | |-file.txt
 * |-foobar/
 * |-|
 * | |-file.txt
 * \endcode
 *
 * And consider an instance of FileResolver that considers \e
 * {foo/bar/} and \e {foo/foobar/} to be root directories for search.
 *
 * Then, when the query \e {file.txt} is resolved, it will always
 * resolve to the file in \e {bar}, as \e {bar} will be searched
 * before \e {foobar}.
 *
 * We say that \e {foobar/file.txt} is shadowed by \e {bar/file.txt}.
 *
 * Currently, if this is an issue, it is possible to resolve it by
 * using a common ancestor as a root directory instead of using
 * multiples directories.
 *
 * In the previous example, if \e {foo} is instead chosen as the root
 * directory for search, then queries \e {bar/file.txt} and \e
 * {foobar/file.txt} can be used to uniquely resolve the two files,
 * removing the shadowing.
 * */

/*!
 * Constructs an instance of FileResolver with the directories in \a
 * search_directories as root directories for searching.
 *
 * Duplicates in \a search_directories do not affect the resolution of
 * files for the instance.
 *
 * For example, if \a search_directories contains some directory D
 * more than once, the constructed instance will resolve files
 * equivalently to an instance constructed with a single appearance of
 * D.
 *
 * The order of \a search_directories does not affect the resolution
 * of files for an instance.
 *
 * For example, if \a search_directories contains a permutation of
 * directories D1, D2, ..., Dn, then the constructed instance will
 * resolve files equivalently to an instance constructed from a
 * difference permutation of the same directories.
 */
FileResolver::FileResolver(std::vector<DirectoryPath>&& search_directories)
    : search_directories{std::move(search_directories)}
{
    std::sort(this->search_directories.begin(), this->search_directories.end());
    this->search_directories.erase (
        std::unique(this->search_directories.begin(), this->search_directories.end()),
        this->search_directories.end()
    );
}

// REMARK: Note that we do not treat absolute path specially.
// This will in general mean that they cannot get resolved (albeit
// there is a peculiar instance in which they can considering that
// most path formats treat multiple adjacent separators as one).
//
// While we need to treat them at some point with a specific
// intention, this was avoided at the current moment as it is
// unrequired to build the actual documentation.
//
// In particular, avoiding this choice now allows us to move it to a
// later stage where we can work with the origin of the data itself.
// User-inputted paths come into the picture during the configuration
// process and when parsing qdoc comments, there is a good chance that
// some amount of sophistication will be required to handle this data
// at the code level, for example to ensure that multiplatform
// handling of paths is performed correctly.
//
// This will then define how we should handle absolute paths, if we
// can receive them at all and so on.

/*!
* Returns a ResolvedFile if \a query can be resolved or std::nullopt
* otherwise.
*
* The returned ResolvedFile, if any, will contain the provided \a
* query and the path that the \a query was resolved to.
*/
[[nodiscard]] std::optional<ResolvedFile> FileResolver::resolve(QString query) const {
    for (auto& directory_path : search_directories) {
        auto maybe_filepath = FilePath::refine(QDir(directory_path.value() + "/" + query).path());
        if (maybe_filepath) return ResolvedFile{std::move(query), std::move(*maybe_filepath)};
    }

    return std::nullopt;
}

/*!
 * \fn FileResolver::get_search_directories() const
 *
 * Returns a const-reference to a collection of root search
 * directories that this instance will use during the resolution of
 * files.
 */