FileType::markdown is the only document format that detection can never reach.
file_type_table.cpp declares detect_by_content = false for it, and it is one
of exactly two rows that do (the other being excel_binary_workbook). So a .md
comes back as text_file, and list_file_types never offers markdown at all —
the caller has to already know, and pass FileType::markdown.
That is the right call for content sniffing: CommonMark has no signature, and
every plain text file is valid markdown. Sniffing it would mean every .txt
becoming a markdown document.
But it leaves a real gap, because the file name is already at the API boundary
and thrown away:
DecodedFile::DecodedFile(const std::string &path, const Logger &logger)
: DecodedFile(File::from_disk(path), logger) {}
File::from_disk(path) keeps the bytes and drops the name, and neither
magic.cpp nor open_strategy.cpp mentions an extension anywhere. Meanwhile
odr::file_type_by_file_extension exists in the public API and is used for
everything except deciding what a file is.
Why it is worth reconsidering now
Markdown is not the only format this shape applies to, it is just the first where
it bites. Anything text-based and signature-less has the same problem, and the
text branch of open_strategy already guesses among siblings by trial decode:
auto text = std::make_shared<text::TextFile>(file);
result.push_back(FileType::text_file);
try { result.push_back(csv::CsvFile(text).file_type()); } catch (...) {}
try { result.push_back(json::JsonFile(text).file_type()); } catch (...) {}
A .md file that happens to parse as CSV is offered as CSV today, while markdown
— the thing the name says it is — is not offered at all.
Options
1. Leave it, document it
Hosts that know the name pass FileType::markdown. Costs nothing; keeps the
asymmetry that a .md opened by path renders as preformatted text while the same
bytes opened with the type render as prose.
2. Take the name as a hint
Carry the file name (or just an extension) into detection and let it rank the
candidates content detection produced, rather than override them. Something like
a field on DecodePreference, or a File::from_disk that remembers its name.
- A
.md lists [text_file, markdown] instead of [text_file], with markdown
first, so a caller taking the first entry gets prose.
- Nothing is claimed on the name alone: a
.md holding a zip is still a zip.
- The precedent is already there —
file_type_priority in DecodePreference
exists to reorder exactly this list.
- The open question is where the name lives.
File is bytes today, and
from_memory has no name, so the hint has to be optional everywhere.
3. Allow name-only detection for signature-less types
Let detect_by_content = false types be produced when the extension says so and
nothing else claimed the bytes. Narrower than (2) and needs no ranking, but it
makes the extension authoritative for one class of types, which is a different
policy from the rest of the table.
4. Sniff markdown after all
Score for markdown constructs — headings, fenced code, link syntax, list markers
— and offer it below text_file past a threshold. No API change, works for
from_memory, but it is a heuristic on a format defined so that plain prose is
valid, so the threshold would be arbitrary and every .txt a coin flip.
Recommendation
(2), with (1) as the fallback if the name plumbing is not worth it. It is the
option that matches how DecodePreference already works, keeps content the only
thing that can claim bytes, and generalises to the next signature-less format
instead of special-casing markdown.
Worth settling before markdown detection is depended on anywhere, since (2) and
(3) differ in what list_file_types returns and that is observable.
FileType::markdownis the only document format that detection can never reach.file_type_table.cppdeclaresdetect_by_content = falsefor it, and it is oneof exactly two rows that do (the other being
excel_binary_workbook). So a.mdcomes back as
text_file, andlist_file_typesnever offers markdown at all —the caller has to already know, and pass
FileType::markdown.That is the right call for content sniffing: CommonMark has no signature, and
every plain text file is valid markdown. Sniffing it would mean every
.txtbecoming a markdown document.
But it leaves a real gap, because the file name is already at the API boundary
and thrown away:
File::from_disk(path)keeps the bytes and drops the name, and neithermagic.cppnoropen_strategy.cppmentions an extension anywhere. Meanwhileodr::file_type_by_file_extensionexists in the public API and is used foreverything except deciding what a file is.
Why it is worth reconsidering now
Markdown is not the only format this shape applies to, it is just the first where
it bites. Anything text-based and signature-less has the same problem, and the
text branch of
open_strategyalready guesses among siblings by trial decode:A
.mdfile that happens to parse as CSV is offered as CSV today, while markdown— the thing the name says it is — is not offered at all.
Options
1. Leave it, document it
Hosts that know the name pass
FileType::markdown. Costs nothing; keeps theasymmetry that a
.mdopened by path renders as preformatted text while the samebytes opened with the type render as prose.
2. Take the name as a hint
Carry the file name (or just an extension) into detection and let it rank the
candidates content detection produced, rather than override them. Something like
a field on
DecodePreference, or aFile::from_diskthat remembers its name..mdlists[text_file, markdown]instead of[text_file], with markdownfirst, so a caller taking the first entry gets prose.
.mdholding a zip is still a zip.file_type_priorityinDecodePreferenceexists to reorder exactly this list.
Fileis bytes today, andfrom_memoryhas no name, so the hint has to be optional everywhere.3. Allow name-only detection for signature-less types
Let
detect_by_content = falsetypes be produced when the extension says so andnothing else claimed the bytes. Narrower than (2) and needs no ranking, but it
makes the extension authoritative for one class of types, which is a different
policy from the rest of the table.
4. Sniff markdown after all
Score for markdown constructs — headings, fenced code, link syntax, list markers
— and offer it below
text_filepast a threshold. No API change, works forfrom_memory, but it is a heuristic on a format defined so that plain prose isvalid, so the threshold would be arbitrary and every
.txta coin flip.Recommendation
(2), with (1) as the fallback if the name plumbing is not worth it. It is the
option that matches how
DecodePreferencealready works, keeps content the onlything that can claim bytes, and generalises to the next signature-less format
instead of special-casing markdown.
Worth settling before markdown detection is depended on anywhere, since (2) and
(3) differ in what
list_file_typesreturns and that is observable.