yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

aidanfnvOmit "Repro" category from default help text output (#8032)41314741d

master
20.6 KiB785 linesraw
1// slang-command-options-writer.cpp
2
3#include "slang-command-options-writer.h"
4
5#include "slang-byte-encode-util.h"
6#include "slang-char-util.h"
7#include "slang-string-util.h"
8
9namespace Slang
10{
11
12namespace
13{ // anonymous
14typedef CommandOptionsWriter::Style Style;
15} // namespace
16
17static bool _isMarkdown(Style style)
18{
19    return style == Style::Markdown || style == Style::NoLinkMarkdown;
20}
21static bool _hasLinks(Style style)
22{
23    return style == Style::Markdown;
24}
25
26/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! MarkdownCommandOptionsWriter !!!!!!!!!!!!!!!!!!!!!!!!!!! */
27
28class MarkdownCommandOptionsWriter : public CommandOptionsWriter
29{
30public:
31    typedef CommandOptionsWriter Super;
32
33    typedef uint32_t LinkFlags;
34    struct LinkFlag
35    {
36        enum Enum
37        {
38            Category = 0x1,
39            Option = 0x2,
40
41            All = Category | Option,
42        };
43    };
44
45    MarkdownCommandOptionsWriter(const Options& options)
46        : Super(options)
47    {
48    }
49
50protected:
51    // CommandOptionsWriter
52    virtual void appendDescriptionForCategoryImpl(Index categoryIndex) SLANG_OVERRIDE;
53    virtual void appendDescriptionImpl() SLANG_OVERRIDE;
54
55    void _appendParagraph(const UnownedStringSlice& text, LinkFlags flags = LinkFlag::All);
56    void _appendParagraph(
57        const ConstArrayView<UnownedStringSlice>& words,
58        LinkFlags flags = LinkFlag::All);
59
60    void _appendMaybeLink(const UnownedStringSlice& word, LinkFlags linkFlags);
61
62    void _appendText(const UnownedStringSlice& text);
63    void _appendDescriptionForCategory(Index categoryIndex);
64    UnownedStringSlice _getLinkName(CommandOptions::LookupKind kind, Index index);
65    UnownedStringSlice _getLinkName(const NameKey& key, Index index);
66
67    void _appendQuickLinks();
68
69    bool m_hasLinks = false;
70    Dictionary<NameKey, StringSlicePool::Handle> m_linkMap;
71};
72
73void MarkdownCommandOptionsWriter::appendDescriptionForCategoryImpl(Index categoryIndex)
74{
75    // No point doing links for a single category
76    m_hasLinks = false;
77    _appendDescriptionForCategory(categoryIndex);
78}
79
80
81void MarkdownCommandOptionsWriter::appendDescriptionImpl()
82{
83    m_hasLinks = _hasLinks(m_options.style);
84
85    if (m_hasLinks)
86    {
87        _appendQuickLinks();
88    }
89
90    // Go through categories in order
91    const auto& categories = m_commandOptions->getCategories();
92    for (Index categoryIndex = 0; categoryIndex < categories.getCount(); ++categoryIndex)
93    {
94        _appendDescriptionForCategory(categoryIndex);
95    }
96}
97
98static bool _needsMarkdownEscape(const UnownedStringSlice& text)
99{
100    for (auto c : text)
101    {
102        switch (c)
103        {
104        case '<':
105        case '>':
106        case '&':
107        case '[':
108        case ']':
109            {
110                return true;
111            }
112        default:
113            break;
114        }
115    }
116
117    return false;
118}
119
120void _appendEscapedMarkdown(const UnownedStringSlice& text, StringBuilder& ioBuf)
121{
122    if (_needsMarkdownEscape(text))
123    {
124        // Replace any < > &
125        for (auto c : text)
126        {
127            switch (c)
128            {
129            case '<':
130                ioBuf << "&lt;";
131                break;
132            case '>':
133                ioBuf << "&gt;";
134                break;
135            case '&':
136                ioBuf << "&amp;";
137                break;
138            case '[':
139                ioBuf << "\\[";
140                break;
141            case ']':
142                ioBuf << "\\]";
143                break;
144            default:
145                ioBuf << c;
146            }
147        }
148    }
149    else
150    {
151        ioBuf << text;
152    }
153}
154
155void MarkdownCommandOptionsWriter::_appendQuickLinks()
156{
157    const auto& categories = m_commandOptions->getCategories();
158    const auto count = categories.getCount();
159
160    m_builder << "### Quick Links\n\n";
161
162    for (Index categoryIndex = 0; categoryIndex < count; ++categoryIndex)
163    {
164        const auto& cat = categories[categoryIndex];
165
166        m_builder << "* [";
167        _appendEscapedMarkdown(cat.name, m_builder);
168        m_builder << "](#" << _getLinkName(LookupKind::Category, categoryIndex) << ")\n";
169    }
170
171    m_builder << "\n";
172}
173
174void MarkdownCommandOptionsWriter::_appendParagraph(
175    const UnownedStringSlice& text,
176    LinkFlags linkFlags)
177{
178    List<UnownedStringSlice> words;
179    StringUtil::splitOnWhitespace(text, words);
180    _appendParagraph(words.getArrayView(), linkFlags);
181}
182
183static bool _isEndPunctionation(char c)
184{
185    return c == '.' || c == ')' || c == ',';
186}
187
188static bool _isStartPunctionation(char c)
189{
190    return c == '(' || c == ',';
191}
192
193static UnownedStringSlice _trimPunctuation(const UnownedStringSlice& word)
194{
195    const char* start = word.begin();
196    const char* end = word.end();
197
198    while (start < end && _isStartPunctionation(*start))
199        start++;
200    while (end > start && _isEndPunctionation(end[-1]))
201        --end;
202    return UnownedStringSlice(start, end);
203}
204
205void MarkdownCommandOptionsWriter::_appendMaybeLink(
206    const UnownedStringSlice& inWord,
207    LinkFlags linkFlags)
208{
209    if (linkFlags)
210    {
211        auto trimmedWord = _trimPunctuation(inWord);
212
213        if (trimmedWord.getLength())
214        {
215            Index index = -1;
216            NameKey nameKey;
217
218            // Look for options
219            if (trimmedWord[0] == '-' && (linkFlags & LinkFlag::Option))
220            {
221                index = m_commandOptions->findTargetIndexByName(
222                    LookupKind::Option,
223                    trimmedWord,
224                    &nameKey);
225            }
226            else if (
227                trimmedWord[0] == '<' && trimmedWord[trimmedWord.getLength() - 1] == '>' &&
228                (linkFlags & LinkFlag::Category))
229            {
230                index = m_commandOptions->findTargetIndexByName(
231                    LookupKind::Category,
232                    trimmedWord.subString(1, trimmedWord.getLength() - 2),
233                    &nameKey);
234            }
235
236            if (index > 0)
237            {
238                // Append before the link
239                _appendEscapedMarkdown(
240                    UnownedStringSlice(inWord.begin(), trimmedWord.begin()),
241                    m_builder);
242
243                // Make into a link
244                m_builder << "[";
245                _appendEscapedMarkdown(trimmedWord, m_builder);
246                m_builder << "](#" << _getLinkName(nameKey, index) << ")";
247
248                // Append after the link
249                _appendEscapedMarkdown(
250                    UnownedStringSlice(trimmedWord.end(), inWord.end()),
251                    m_builder);
252                return;
253            }
254        }
255    }
256
257    _appendEscapedMarkdown(inWord, m_builder);
258}
259
260void MarkdownCommandOptionsWriter::_appendParagraph(
261    const ConstArrayView<UnownedStringSlice>& words,
262    LinkFlags linkFlags)
263{
264    if (m_hasLinks && linkFlags)
265    {
266        for (auto word : words)
267        {
268            _appendMaybeLink(word, linkFlags);
269            m_builder << " ";
270        }
271    }
272    else
273    {
274        for (auto word : words)
275        {
276            _appendEscapedMarkdown(word, m_builder);
277            m_builder << " ";
278        }
279    }
280}
281
282void MarkdownCommandOptionsWriter::_appendText(const UnownedStringSlice& text)
283{
284    List<UnownedStringSlice> lines;
285    StringUtil::calcLines(text, lines);
286    for (auto line : lines)
287    {
288        if (line.startsWith(toSlice(" ")))
289        {
290            // If prefixed means we want to display as is
291            m_builder << "> " << line << "\n";
292        }
293        else
294        {
295            _appendParagraph(line);
296            m_builder << "\n\n";
297        }
298    }
299}
300
301
302void MarkdownCommandOptionsWriter::_appendDescriptionForCategory(Index categoryIndex)
303{
304    auto& options = *m_commandOptions;
305
306    const auto& categories = options.getCategories();
307    const auto& category = categories[categoryIndex];
308
309    const bool isValue = (category.kind == CommandOptions::CategoryKind::Value);
310
311    // Header
312    {
313        if (m_hasLinks)
314        {
315            // Output anchor
316            m_builder << "<a id=\"" << _getLinkName(LookupKind::Category, categoryIndex)
317                      << "\"></a>\n";
318        }
319
320        m_builder << "## " << category.name << "\n\n";
321
322        // If there is a description output, making \n split paragraphs
323        if (category.description.getLength() > 0)
324        {
325            _appendText(category.description);
326        }
327    }
328
329    for (Index optionIndex = category.optionStartIndex; optionIndex < category.optionEndIndex;
330         ++optionIndex)
331    {
332        const auto& option = options.getOptionAt(optionIndex);
333
334        {
335            List<UnownedStringSlice> names;
336            StringUtil::split(option.names, ',', names);
337
338            if (isValue)
339            {
340                m_builder << "* ";
341                // Output all the names
342                m_builder << "`";
343                StringUtil::join(names.getBuffer(), names.getCount(), toSlice("`, `"), m_builder);
344                m_builder << "` ";
345            }
346            else
347            {
348                if (m_hasLinks)
349                {
350                    m_builder << "<a id=\"" << _getLinkName(LookupKind::Option, optionIndex)
351                              << "\"></a>\n";
352                }
353
354                m_builder << "### ";
355                StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), m_builder);
356                m_builder << "\n";
357
358                if (option.usage.getLength())
359                {
360                    m_builder << "\n**";
361
362                    if (m_hasLinks)
363                    {
364                        List<UnownedStringSlice> usedCategories;
365                        options.splitUsage(option.usage, usedCategories);
366
367                        const char* cur = option.usage.begin();
368                        for (auto usedCategory : usedCategories)
369                        {
370                            _appendEscapedMarkdown(
371                                UnownedStringSlice(cur, usedCategory.begin()),
372                                m_builder);
373
374                            // Now do the link
375                            const Index usedCategoryIndex =
376                                options.findCategoryByName(usedCategory);
377
378                            m_builder << "[" << usedCategory << "](#"
379                                      << _getLinkName(LookupKind::Category, usedCategoryIndex)
380                                      << ")";
381
382                            cur = usedCategory.end();
383                        }
384
385                        _appendEscapedMarkdown(
386                            UnownedStringSlice(cur, option.usage.end()),
387                            m_builder);
388                    }
389                    else
390                    {
391                        _appendEscapedMarkdown(option.usage, m_builder);
392                    }
393
394                    m_builder << "**\n\n";
395                }
396            }
397        }
398
399        if (option.description.getLength() > 0)
400        {
401            if (isValue)
402            {
403                m_builder << ": ";
404                _appendParagraph(option.description);
405            }
406            else
407            {
408                _appendText(option.description);
409            }
410        }
411
412        m_builder << "\n";
413    }
414
415    m_builder << "\n";
416}
417
418UnownedStringSlice MarkdownCommandOptionsWriter::_getLinkName(const NameKey& key, Index index)
419{
420    if (auto ptr = m_linkMap.tryGetValue(key))
421    {
422        return m_pool.getSlice(*ptr);
423    }
424
425    UnownedStringSlice prefix = (key.kind == CommandOptions::LookupKind::Category)
426                                    ? m_commandOptions->getFirstNameForCategory(index)
427                                    : m_commandOptions->getFirstNameForOption(index);
428    prefix = prefix.trim('-');
429
430    if (prefix.getLength() == 0)
431    {
432        prefix = toSlice("id");
433    }
434
435    StringBuilder buf;
436    buf << prefix;
437
438    const auto bufLen = buf.getLength();
439
440    for (Index i = 0; i < 1000; ++i)
441    {
442        buf.reduceLength(bufLen);
443
444        if (i > 0)
445        {
446            buf << "-" << i;
447        }
448
449        if (!m_pool.has(buf.getUnownedSlice()))
450        {
451            break;
452        }
453    }
454
455    const auto handle = m_pool.add(buf.getUnownedSlice());
456    m_linkMap.add(key, handle);
457
458    return m_pool.getSlice(handle);
459}
460
461UnownedStringSlice MarkdownCommandOptionsWriter::_getLinkName(
462    CommandOptions::LookupKind kind,
463    Index index)
464{
465    auto& options = *m_commandOptions;
466
467    // Set up the name key
468    const auto key = (kind == LookupKind::Category) ? options.getNameKeyForCategory(index)
469                                                    : options.getNameKeyForOption(index);
470
471    return _getLinkName(key, index);
472}
473
474/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TextCommandOptionsWriter !!!!!!!!!!!!!!!!!!!!!!!!!!! */
475
476class TextCommandOptionsWriter : public CommandOptionsWriter
477{
478public:
479    typedef CommandOptionsWriter Super;
480
481    TextCommandOptionsWriter(const Options& options)
482        : Super(options)
483    {
484    }
485
486protected:
487    // CommandOptionsWriter
488    virtual void appendDescriptionForCategoryImpl(Index categoryIndex) SLANG_OVERRIDE;
489    virtual void appendDescriptionImpl() SLANG_OVERRIDE;
490
491    void _appendText(Count indentCount, const UnownedStringSlice& text);
492    void _appendDescriptionForCategory(Index categoryIndex);
493};
494
495void TextCommandOptionsWriter::appendDescriptionForCategoryImpl(Index categoryIndex)
496{
497    _appendDescriptionForCategory(categoryIndex);
498}
499
500void TextCommandOptionsWriter::appendDescriptionImpl()
501{
502    const auto& categories = m_commandOptions->getCategories();
503    for (Index categoryIndex = 0; categoryIndex < categories.getCount(); ++categoryIndex)
504    {
505        const auto& category = categories[categoryIndex];
506
507        // Omit the value categories as well as the "Internal" and "Repro" categories from the text
508        // output
509        if (category.kind != CategoryKind::Value && category.name != toSlice("Internal") &&
510            category.name != toSlice("Repro"))
511        {
512            _appendDescriptionForCategory(categoryIndex);
513        }
514    }
515
516    // Add instructions for getting help for specific categories
517    m_builder << "Getting Help for Specific Categories\n";
518    m_builder << "=====================================\n\n";
519    m_builder << "To get help for a specific category of options or values, use: slangc -h "
520                 "<help-category>\n";
521    m_builder << m_options.indent << "<help-category> can be: ";
522
523    List<UnownedStringSlice> categoryNames;
524    for (const auto& category : categories)
525    {
526        categoryNames.add(category.name);
527    }
528
529    _appendWrappedIndented(1, categoryNames, toSlice(", "));
530    m_builder << "\n\n";
531}
532
533void TextCommandOptionsWriter::_appendDescriptionForCategory(Index categoryIndex)
534{
535    auto& options = *m_commandOptions;
536
537    const auto& categories = options.getCategories();
538    const auto& category = categories[categoryIndex];
539
540    // Header
541    {
542        const auto count = m_builder.getLength();
543        if (category.kind == CategoryKind::Value)
544        {
545            m_builder << "<" << category.name << ">";
546        }
547        else
548        {
549            m_builder << category.name;
550        }
551
552        const auto length = m_builder.getLength() - count;
553        m_builder << "\n";
554
555        m_builder.appendRepeatedChar('=', length);
556
557        m_builder << "\n\n";
558
559        // If there is a description output it
560        if (category.description.getLength() > 0)
561        {
562            _appendText(0, category.description);
563            m_builder << "\n";
564        }
565    }
566
567    for (auto& option : options.getOptionsForCategory(categoryIndex))
568    {
569        m_builder << m_options.indent;
570
571        if (option.usage.getLength())
572        {
573            m_builder << option.usage;
574        }
575        else
576        {
577            List<UnownedStringSlice> names;
578            StringUtil::split(option.names, ',', names);
579
580            _appendWrappedIndented(1, names, toSlice(", "));
581        }
582
583        if (option.description.getLength() == 0)
584        {
585            m_builder << "\n";
586            continue;
587        }
588
589        m_builder << ": ";
590
591        _appendText(2, option.description);
592
593        if (option.usage.getLength())
594        {
595            List<Index> usageCategoryIndices;
596            options.findCategoryIndicesFromUsage(option.usage, usageCategoryIndices);
597
598            for (auto usageCategoryIndex : usageCategoryIndices)
599            {
600                auto& usageCat = categories[usageCategoryIndex];
601
602                m_builder << m_options.indent << m_options.indent;
603
604                m_builder << "To get a list of values that can be used for <" << usageCat.name
605                          << ">, ";
606                m_builder << "use \"slangc -h " << usageCat.name << "\"\n";
607            }
608        }
609    }
610
611    m_builder << "\n";
612}
613
614void TextCommandOptionsWriter::_appendText(Count indentCount, const UnownedStringSlice& text)
615{
616    List<UnownedStringSlice> lines;
617    StringUtil::calcLines(text, lines);
618
619    // Remove very last line if it's empty
620    if (lines.getCount() > 1 && lines.getLast().trim().getLength() == 0)
621    {
622        lines.removeLast();
623    }
624
625    List<UnownedStringSlice> words;
626
627    for (auto line : lines)
628    {
629        if (line.startsWith(toSlice(" ")))
630        {
631            // Append the line as is after the indent
632            _requireIndent(indentCount);
633            m_builder << line;
634        }
635        else if (line.trim().getLength() == 0)
636        {
637        }
638        else
639        {
640            words.clear();
641            StringUtil::split(line, ' ', words);
642
643            _requireIndent(indentCount);
644            _appendWrappedIndented(indentCount, words, toSlice(" "));
645        }
646
647        m_builder << "\n";
648    }
649}
650
651/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CommandOptionsWriter !!!!!!!!!!!!!!!!!!!!!!!!!!! */
652
653typedef CommandOptionsWriter::Style Style;
654
655static const NamesDescriptionValue s_styleInfos[] = {
656    {ValueInt(Style::Text), "text", "Text suitable for output to a terminal"},
657    {ValueInt(Style::Markdown), "markdown", "Markdown"},
658    {ValueInt(Style::NoLinkMarkdown), "no-link-markdown", "Markdown without links"},
659};
660
661/* static */ ConstArrayView<NamesDescriptionValue> CommandOptionsWriter::getStyleInfos()
662{
663    return makeConstArrayView(s_styleInfos);
664}
665
666CommandOptionsWriter::CommandOptionsWriter(const Options& options)
667    : m_pool(StringSlicePool::Style::Default), m_options(options)
668{
669    m_options.indent = m_pool.addAndGetSlice(options.indent);
670}
671
672/* static */ RefPtr<CommandOptionsWriter> CommandOptionsWriter::create(const Options& options)
673{
674    if (_isMarkdown(options.style))
675    {
676        return new MarkdownCommandOptionsWriter(options);
677    }
678    else
679    {
680        return new TextCommandOptionsWriter(options);
681    }
682}
683
684void CommandOptionsWriter::appendDescriptionForCategory(
685    CommandOptions* options,
686    Index categoryIndex)
687{
688    m_commandOptions = options;
689    appendDescriptionForCategoryImpl(categoryIndex);
690    m_commandOptions = nullptr;
691}
692
693void CommandOptionsWriter::appendDescription(CommandOptions* options)
694{
695    m_commandOptions = options;
696    appendDescriptionImpl();
697    m_commandOptions = nullptr;
698}
699
700Count CommandOptionsWriter::_getCurrentLineLength()
701{
702    // Work out the current line length
703    const char* start = m_builder.begin();
704    const char* cur = m_builder.end();
705
706    Count lineLength = 0;
707
708    if (cur > start)
709    {
710        for (--cur; cur > start; --cur)
711        {
712            const auto c = *cur;
713            if (c == '\n' || c == '\r')
714            {
715                ++cur;
716                break;
717            }
718        }
719
720        lineLength = Count(ptrdiff_t(m_builder.end() - cur));
721    }
722
723    return lineLength;
724}
725
726void CommandOptionsWriter::_requireIndent(Count indentCount)
727{
728    const auto length = m_builder.getLength();
729    if (length)
730    {
731        const auto c = m_builder[length - 1];
732        if (c == '\n' || c == '\r')
733        {
734            for (Index j = 0; j < indentCount; j++)
735            {
736                m_builder.append(m_options.indent);
737            }
738        }
739    }
740}
741
742void CommandOptionsWriter::_appendWrappedIndented(
743    Count indentCount,
744    List<UnownedStringSlice>& slices,
745    const UnownedStringSlice& delimit)
746{
747    Count lineLength = _getCurrentLineLength();
748
749    const auto count = slices.getCount();
750
751    for (Index i = 0; i < count; ++i)
752    {
753        auto slice = slices[i];
754
755        auto sliceLength = slice.getLength();
756
757        if (i < count - 1)
758        {
759            sliceLength += delimit.getLength();
760        }
761
762        // If out of space onto the next line
763        if (lineLength + sliceLength > m_options.lineLength)
764        {
765            m_builder.append("\n");
766
767            lineLength = indentCount * m_options.indent.getLength();
768
769            for (Index j = 0; j < indentCount; j++)
770            {
771                m_builder.append(m_options.indent);
772            }
773        }
774
775        m_builder.append(slice);
776        if (i < count - 1)
777        {
778            m_builder.append(delimit);
779        }
780
781        lineLength += sliceLength;
782    }
783}
784
785} // namespace Slang