Skip to content

Commit c908105

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

11 files changed

+872
-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

+301
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
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 && i != filename.size() - 1)
92+
++depth;
93+
}
94+
return depth;
95+
}
96+
97+
/************************************************************************/
98+
/* GDALFSListAlgorithm::PrintEntry() */
99+
/************************************************************************/
100+
101+
void GDALFSListAlgorithm::PrintEntry(const VSIDIREntry *entry)
102+
{
103+
std::string filename;
104+
if (m_format == "json" && m_JSONAsTree)
105+
{
106+
filename = CPLGetFilename(entry->pszName);
107+
}
108+
else if (m_absolutePath)
109+
{
110+
if (CPLIsFilenameRelative(m_filename.c_str()))
111+
{
112+
char *pszCurDir = CPLGetCurrentDir();
113+
if (!pszCurDir)
114+
pszCurDir = CPLStrdup(".");
115+
if (m_filename == ".")
116+
filename = pszCurDir;
117+
else
118+
filename =
119+
CPLFormFilenameSafe(pszCurDir, m_filename.c_str(), nullptr);
120+
CPLFree(pszCurDir);
121+
}
122+
else
123+
{
124+
filename = m_filename;
125+
}
126+
filename =
127+
CPLFormFilenameSafe(filename.c_str(), entry->pszName, nullptr);
128+
}
129+
else
130+
{
131+
filename = entry->pszName;
132+
}
133+
134+
char permissions[1 + 3 + 3 + 3 + 1] = "----------";
135+
struct tm bdt;
136+
memset(&bdt, 0, sizeof(bdt));
137+
138+
if (m_longListing)
139+
{
140+
if (entry->bModeKnown)
141+
{
142+
if (VSI_ISDIR(entry->nMode))
143+
permissions[0] = 'd';
144+
for (int i = 0; i < 9; ++i)
145+
{
146+
if (entry->nMode & (1 << i))
147+
permissions[9 - i] = (i % 3) == 0 ? 'x'
148+
: (i % 3) == 1 ? 'w'
149+
: 'r';
150+
}
151+
}
152+
else if (VSI_ISDIR(entry->nMode))
153+
{
154+
strcpy(permissions, "dr-xr-xr-x");
155+
}
156+
else
157+
{
158+
strcpy(permissions, "-r--r--r--");
159+
}
160+
161+
CPLUnixTimeToYMDHMS(entry->nMTime, &bdt);
162+
}
163+
164+
if (m_format == "json")
165+
{
166+
if (m_JSONAsTree)
167+
{
168+
while (!m_stackNames.empty() &&
169+
GetDepth(m_stackNames.back()) >= GetDepth(entry->pszName))
170+
{
171+
m_oWriter.EndArray();
172+
m_oWriter.EndObj();
173+
m_stackNames.pop_back();
174+
}
175+
}
176+
177+
if (m_longListing)
178+
{
179+
m_oWriter.StartObj();
180+
m_oWriter.AddObjKey("name");
181+
m_oWriter.Add(filename);
182+
m_oWriter.AddObjKey("type");
183+
m_oWriter.Add(VSI_ISDIR(entry->nMode) ? "directory" : "file");
184+
m_oWriter.AddObjKey("size");
185+
m_oWriter.Add(static_cast<uint64_t>(entry->nSize));
186+
if (entry->bMTimeKnown)
187+
{
188+
m_oWriter.AddObjKey("last_modification_date");
189+
m_oWriter.Add(CPLSPrintf("%04d-%02d-%02d %02d:%02d:%02dZ",
190+
bdt.tm_year + 1900, bdt.tm_mon + 1,
191+
bdt.tm_mday, bdt.tm_hour, bdt.tm_min,
192+
bdt.tm_sec));
193+
}
194+
if (entry->bModeKnown)
195+
{
196+
m_oWriter.AddObjKey("permissions");
197+
m_oWriter.Add(permissions);
198+
}
199+
if (m_JSONAsTree && VSI_ISDIR(entry->nMode))
200+
{
201+
m_stackNames.push_back(entry->pszName);
202+
m_oWriter.AddObjKey("entries");
203+
m_oWriter.StartArray();
204+
}
205+
else
206+
{
207+
m_oWriter.EndObj();
208+
}
209+
}
210+
else
211+
{
212+
if (m_JSONAsTree && VSI_ISDIR(entry->nMode))
213+
{
214+
m_oWriter.StartObj();
215+
m_oWriter.AddObjKey("name");
216+
m_oWriter.Add(filename);
217+
218+
m_stackNames.push_back(entry->pszName);
219+
m_oWriter.AddObjKey("entries");
220+
m_oWriter.StartArray();
221+
}
222+
else
223+
{
224+
m_oWriter.Add(filename);
225+
}
226+
}
227+
}
228+
else if (m_longListing)
229+
{
230+
Print(CPLSPrintf("%s 1 unknown unknown %12" PRIu64
231+
" %04d-%02d-%02d %02d:%02d %s\n",
232+
permissions, static_cast<uint64_t>(entry->nSize),
233+
bdt.tm_year + 1900, bdt.tm_mon + 1, bdt.tm_mday,
234+
bdt.tm_hour, bdt.tm_min, filename.c_str()));
235+
}
236+
else
237+
{
238+
Print(filename.c_str());
239+
Print("\n");
240+
}
241+
}
242+
243+
/************************************************************************/
244+
/* GDALFSListAlgorithm::RunImpl() */
245+
/************************************************************************/
246+
247+
bool GDALFSListAlgorithm::RunImpl(GDALProgressFunc, void *)
248+
{
249+
VSIStatBufL sStat;
250+
if (VSIStatL(m_filename.c_str(), &sStat) != 0)
251+
{
252+
ReportError(CE_Failure, CPLE_FileIO, "'%s' does not exist",
253+
m_filename.c_str());
254+
return false;
255+
}
256+
257+
if (VSI_ISDIR(sStat.st_mode))
258+
{
259+
std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> dir(
260+
VSIOpenDir(m_filename.c_str(),
261+
m_recursive ? (m_depth == 0 ? 0
262+
: m_depth > 0 ? m_depth - 1
263+
: -1)
264+
: 0,
265+
nullptr),
266+
VSICloseDir);
267+
if (!dir)
268+
return false;
269+
if (m_format == "json")
270+
m_oWriter.StartArray();
271+
while (const auto entry = VSIGetNextDirEntry(dir.get()))
272+
{
273+
if (!(entry->pszName[0] == '.' &&
274+
(entry->pszName[1] == '.' || entry->pszName[1] == 0)))
275+
{
276+
PrintEntry(entry);
277+
}
278+
}
279+
while (!m_stackNames.empty())
280+
{
281+
m_stackNames.pop_back();
282+
m_oWriter.EndArray();
283+
m_oWriter.EndObj();
284+
}
285+
if (m_format == "json")
286+
m_oWriter.EndArray();
287+
}
288+
else
289+
{
290+
VSIDIREntry sEntry;
291+
sEntry.pszName = CPLStrdup(m_filename.c_str());
292+
sEntry.bModeKnown = true;
293+
sEntry.nMode = sStat.st_mode;
294+
sEntry.nSize = sStat.st_size;
295+
PrintEntry(&sEntry);
296+
}
297+
298+
return true;
299+
}
300+
301+
//! @endcond

0 commit comments

Comments
 (0)