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