Skip to content

Commit 9f0e30e

Browse files
committed
Add 'gdal fs ls' (replacement for gdal_ls.py sample script)
1 parent 4419226 commit 9f0e30e

11 files changed

+873
-14
lines changed

apps/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ add_library(
77
gdal_utils.h
88
gdalargumentparser.cpp
99
gdalalg_convert.cpp
10+
gdalalg_fs.cpp
11+
gdalalg_fs_ls.cpp
1012
gdalalg_info.cpp
1113
gdalalg_main.cpp
1214
gdalalg_mdim.cpp

apps/gdalalg_fs.cpp

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/******************************************************************************
2+
*
3+
* Project: GDAL
4+
* Purpose: gdal "fs" subcommand
5+
* Author: Even Rouault <even dot rouault at spatialys.com>
6+
*
7+
******************************************************************************
8+
* Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9+
*
10+
* SPDX-License-Identifier: MIT
11+
****************************************************************************/
12+
13+
#include "gdalalgorithm.h"
14+
15+
#include "gdalalg_fs_ls.h"
16+
17+
/************************************************************************/
18+
/* GDALFSAlgorithm */
19+
/************************************************************************/
20+
21+
class GDALFSAlgorithm final : public GDALAlgorithm
22+
{
23+
public:
24+
static constexpr const char *NAME = "fs";
25+
static constexpr const char *DESCRIPTION =
26+
"GDAL Virtual file system (VSI) commands.";
27+
static constexpr const char *HELP_URL = "/programs/gdal_fs.html";
28+
29+
GDALFSAlgorithm() : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
30+
{
31+
RegisterSubAlgorithm<GDALFSListAlgorithm>();
32+
}
33+
34+
private:
35+
bool RunImpl(GDALProgressFunc, void *) override
36+
{
37+
CPLError(CE_Failure, CPLE_AppDefined,
38+
"The Run() method should not be called directly on the \"gdal "
39+
"fs\" program.");
40+
return false;
41+
}
42+
};
43+
44+
GDAL_STATIC_REGISTER_ALG(GDALFSAlgorithm);

apps/gdalalg_fs_ls.cpp

+302
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/******************************************************************************
2+
*
3+
* Project: GDAL
4+
* Purpose: gdal "fs ls" subcommand
5+
* Author: Even Rouault <even dot rouault at spatialys.com>
6+
*
7+
******************************************************************************
8+
* Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9+
*
10+
* SPDX-License-Identifier: MIT
11+
****************************************************************************/
12+
13+
#include "gdalalg_fs_ls.h"
14+
15+
#include "cpl_string.h"
16+
#include "cpl_time.h"
17+
#include "cpl_vsi.h"
18+
19+
#include <cinttypes>
20+
21+
//! @cond Doxygen_Suppress
22+
23+
#ifndef _
24+
#define _(x) (x)
25+
#endif
26+
27+
/************************************************************************/
28+
/* GDALFSListAlgorithm::GDALFSListAlgorithm() */
29+
/************************************************************************/
30+
31+
GDALFSListAlgorithm::GDALFSListAlgorithm()
32+
: GDALAlgorithm(NAME, DESCRIPTION, HELP_URL), m_oWriter(JSONPrint, this)
33+
{
34+
AddArg("filename", 0, _("File or directory name"), &m_filename)
35+
.SetPositional()
36+
.SetRequired();
37+
38+
AddOutputFormatArg(&m_format).SetDefault("json").SetChoices("json", "text");
39+
40+
AddArg("long-listing", 'l', _("Use a long listing format"), &m_longListing)
41+
.AddAlias("long");
42+
AddArg("recursive", 'R', _("List subdirectories recursively"),
43+
&m_recursive);
44+
AddArg("depth", 0, _("Maximum depth in recursive mode"), &m_depth)
45+
.SetMinValueIncluded(1);
46+
AddArg("absolute-path", 0, _("Display absolute path"), &m_absolutePath)
47+
.AddAlias("abs");
48+
AddArg("tree", 0, _("Use a hiearchical presentation for JSON output"),
49+
&m_JSONAsTree);
50+
51+
AddOutputStringArg(&m_output);
52+
AddArg(
53+
"stdout", 0,
54+
_("Directly output on stdout. If enabled, output-string will be empty"),
55+
&m_stdout)
56+
.SetHiddenForCLI();
57+
}
58+
59+
/************************************************************************/
60+
/* GDALFSListAlgorithm::Print() */
61+
/************************************************************************/
62+
63+
void GDALFSListAlgorithm::Print(const char *str)
64+
{
65+
if (m_stdout)
66+
fwrite(str, 1, strlen(str), stdout);
67+
else
68+
m_output += str;
69+
}
70+
71+
/************************************************************************/
72+
/* GDALFSListAlgorithm::JSONPrint() */
73+
/************************************************************************/
74+
75+
/* static */ void GDALFSListAlgorithm::JSONPrint(const char *pszTxt,
76+
void *pUserData)
77+
{
78+
static_cast<GDALFSListAlgorithm *>(pUserData)->Print(pszTxt);
79+
}
80+
81+
/************************************************************************/
82+
/* GetDepth() */
83+
/************************************************************************/
84+
85+
static int GetDepth(const std::string &filename)
86+
{
87+
int depth = 0;
88+
const char sep = VSIGetDirectorySeparator(filename.c_str())[0];
89+
for (size_t i = 0; i < filename.size(); ++i)
90+
{
91+
if ((filename[i] == sep || filename[i] == '/') &&
92+
i != filename.size() - 1)
93+
++depth;
94+
}
95+
return depth;
96+
}
97+
98+
/************************************************************************/
99+
/* GDALFSListAlgorithm::PrintEntry() */
100+
/************************************************************************/
101+
102+
void GDALFSListAlgorithm::PrintEntry(const VSIDIREntry *entry)
103+
{
104+
std::string filename;
105+
if (m_format == "json" && m_JSONAsTree)
106+
{
107+
filename = CPLGetFilename(entry->pszName);
108+
}
109+
else if (m_absolutePath)
110+
{
111+
if (CPLIsFilenameRelative(m_filename.c_str()))
112+
{
113+
char *pszCurDir = CPLGetCurrentDir();
114+
if (!pszCurDir)
115+
pszCurDir = CPLStrdup(".");
116+
if (m_filename == ".")
117+
filename = pszCurDir;
118+
else
119+
filename =
120+
CPLFormFilenameSafe(pszCurDir, m_filename.c_str(), nullptr);
121+
CPLFree(pszCurDir);
122+
}
123+
else
124+
{
125+
filename = m_filename;
126+
}
127+
filename =
128+
CPLFormFilenameSafe(filename.c_str(), entry->pszName, nullptr);
129+
}
130+
else
131+
{
132+
filename = entry->pszName;
133+
}
134+
135+
char permissions[1 + 3 + 3 + 3 + 1] = "----------";
136+
struct tm bdt;
137+
memset(&bdt, 0, sizeof(bdt));
138+
139+
if (m_longListing)
140+
{
141+
if (entry->bModeKnown)
142+
{
143+
if (VSI_ISDIR(entry->nMode))
144+
permissions[0] = 'd';
145+
for (int i = 0; i < 9; ++i)
146+
{
147+
if (entry->nMode & (1 << i))
148+
permissions[9 - i] = (i % 3) == 0 ? 'x'
149+
: (i % 3) == 1 ? 'w'
150+
: 'r';
151+
}
152+
}
153+
else if (VSI_ISDIR(entry->nMode))
154+
{
155+
strcpy(permissions, "dr-xr-xr-x");
156+
}
157+
else
158+
{
159+
strcpy(permissions, "-r--r--r--");
160+
}
161+
162+
CPLUnixTimeToYMDHMS(entry->nMTime, &bdt);
163+
}
164+
165+
if (m_format == "json")
166+
{
167+
if (m_JSONAsTree)
168+
{
169+
while (!m_stackNames.empty() &&
170+
GetDepth(m_stackNames.back()) >= GetDepth(entry->pszName))
171+
{
172+
m_oWriter.EndArray();
173+
m_oWriter.EndObj();
174+
m_stackNames.pop_back();
175+
}
176+
}
177+
178+
if (m_longListing)
179+
{
180+
m_oWriter.StartObj();
181+
m_oWriter.AddObjKey("name");
182+
m_oWriter.Add(filename);
183+
m_oWriter.AddObjKey("type");
184+
m_oWriter.Add(VSI_ISDIR(entry->nMode) ? "directory" : "file");
185+
m_oWriter.AddObjKey("size");
186+
m_oWriter.Add(static_cast<uint64_t>(entry->nSize));
187+
if (entry->bMTimeKnown)
188+
{
189+
m_oWriter.AddObjKey("last_modification_date");
190+
m_oWriter.Add(CPLSPrintf("%04d-%02d-%02d %02d:%02d:%02dZ",
191+
bdt.tm_year + 1900, bdt.tm_mon + 1,
192+
bdt.tm_mday, bdt.tm_hour, bdt.tm_min,
193+
bdt.tm_sec));
194+
}
195+
if (entry->bModeKnown)
196+
{
197+
m_oWriter.AddObjKey("permissions");
198+
m_oWriter.Add(permissions);
199+
}
200+
if (m_JSONAsTree && VSI_ISDIR(entry->nMode))
201+
{
202+
m_stackNames.push_back(entry->pszName);
203+
m_oWriter.AddObjKey("entries");
204+
m_oWriter.StartArray();
205+
}
206+
else
207+
{
208+
m_oWriter.EndObj();
209+
}
210+
}
211+
else
212+
{
213+
if (m_JSONAsTree && VSI_ISDIR(entry->nMode))
214+
{
215+
m_oWriter.StartObj();
216+
m_oWriter.AddObjKey("name");
217+
m_oWriter.Add(filename);
218+
219+
m_stackNames.push_back(entry->pszName);
220+
m_oWriter.AddObjKey("entries");
221+
m_oWriter.StartArray();
222+
}
223+
else
224+
{
225+
m_oWriter.Add(filename);
226+
}
227+
}
228+
}
229+
else if (m_longListing)
230+
{
231+
Print(CPLSPrintf("%s 1 unknown unknown %12" PRIu64
232+
" %04d-%02d-%02d %02d:%02d %s\n",
233+
permissions, static_cast<uint64_t>(entry->nSize),
234+
bdt.tm_year + 1900, bdt.tm_mon + 1, bdt.tm_mday,
235+
bdt.tm_hour, bdt.tm_min, filename.c_str()));
236+
}
237+
else
238+
{
239+
Print(filename.c_str());
240+
Print("\n");
241+
}
242+
}
243+
244+
/************************************************************************/
245+
/* GDALFSListAlgorithm::RunImpl() */
246+
/************************************************************************/
247+
248+
bool GDALFSListAlgorithm::RunImpl(GDALProgressFunc, void *)
249+
{
250+
VSIStatBufL sStat;
251+
if (VSIStatL(m_filename.c_str(), &sStat) != 0)
252+
{
253+
ReportError(CE_Failure, CPLE_FileIO, "'%s' does not exist",
254+
m_filename.c_str());
255+
return false;
256+
}
257+
258+
if (VSI_ISDIR(sStat.st_mode))
259+
{
260+
std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> dir(
261+
VSIOpenDir(m_filename.c_str(),
262+
m_recursive ? (m_depth == 0 ? 0
263+
: m_depth > 0 ? m_depth - 1
264+
: -1)
265+
: 0,
266+
nullptr),
267+
VSICloseDir);
268+
if (!dir)
269+
return false;
270+
if (m_format == "json")
271+
m_oWriter.StartArray();
272+
while (const auto entry = VSIGetNextDirEntry(dir.get()))
273+
{
274+
if (!(entry->pszName[0] == '.' &&
275+
(entry->pszName[1] == '.' || entry->pszName[1] == 0)))
276+
{
277+
PrintEntry(entry);
278+
}
279+
}
280+
while (!m_stackNames.empty())
281+
{
282+
m_stackNames.pop_back();
283+
m_oWriter.EndArray();
284+
m_oWriter.EndObj();
285+
}
286+
if (m_format == "json")
287+
m_oWriter.EndArray();
288+
}
289+
else
290+
{
291+
VSIDIREntry sEntry;
292+
sEntry.pszName = CPLStrdup(m_filename.c_str());
293+
sEntry.bModeKnown = true;
294+
sEntry.nMode = sStat.st_mode;
295+
sEntry.nSize = sStat.st_size;
296+
PrintEntry(&sEntry);
297+
}
298+
299+
return true;
300+
}
301+
302+
//! @endcond

0 commit comments

Comments
 (0)