@@ -3,7 +3,9 @@ package config
3
3
4
4
import Settings ._
5
5
import core .Contexts ._
6
+ import printing .Highlighting
6
7
8
+ import scala .util .chaining .given
7
9
import scala .PartialFunction .cond
8
10
9
11
trait CliCommand :
@@ -49,59 +51,31 @@ trait CliCommand:
49
51
end distill
50
52
51
53
/** Creates a help message for a subset of options based on cond */
52
- protected def availableOptionsMsg (cond : Setting [? ] => Boolean )(using settings : ConcreteSettings )(using SettingsState ): String =
53
- val ss = (settings.allSettings filter cond).toList sortBy (_.name)
54
- val maxNameWidth = 30
55
- val nameWidths = ss.map(_.name.length).partition(_ < maxNameWidth)._1
56
- val width = if nameWidths.nonEmpty then nameWidths.max else maxNameWidth
57
- val terminalWidth = settings.pageWidth.value
58
- val (nameWidth, descriptionWidth) = {
59
- val w1 =
60
- if width < maxNameWidth then width
61
- else maxNameWidth
62
- val w2 =
63
- if terminalWidth < w1 + maxNameWidth then 0
64
- else terminalWidth - w1 - 1
65
- (w1, w2)
66
- }
67
- def formatName (name : String ) =
68
- if name.length <= nameWidth then (" %-" + nameWidth + " s" ) format name
69
- else (name + " \n %-" + nameWidth + " s" ) format " "
70
- def formatDescription (text : String ): String =
71
- if descriptionWidth == 0 then text
72
- else if text.length < descriptionWidth then text
73
- else {
74
- val inx = text.substring(0 , descriptionWidth).lastIndexOf(" " )
75
- if inx < 0 then text
76
- else
77
- val str = text.substring(0 , inx)
78
- s " ${str}\n ${formatName(" " )} ${formatDescription(text.substring(inx + 1 ))}"
79
- }
80
- def formatSetting (name : String , value : String ) =
81
- if (value.nonEmpty)
82
- // the format here is helping to make empty padding and put the additional information exactly under the description.
83
- s " \n ${formatName(" " )} $name: $value. "
84
- else
85
- " "
86
- def helpStr (s : Setting [? ]) =
54
+ protected def availableOptionsMsg (p : Setting [? ] => Boolean )(using settings : ConcreteSettings )(using SettingsState ): String =
55
+ // result is (Option Name, descrption\ndefault: value\nchoices: x, y, z
56
+ def help (s : Setting [? ]): (String , String ) =
57
+ // For now, skip the default values that do not make sense for the end user, such as 'false' for the version command.
87
58
def defaultValue = s.default match
88
59
case _ : Int | _ : String => s.default.toString
89
- case _ =>
90
- // For now, skip the default values that do not make sense for the end user.
91
- // For example 'false' for the version command.
92
- " "
93
- s " ${formatName(s.name)} ${formatDescription(shortHelp(s))}${formatSetting(" Default" , defaultValue)}${formatSetting(" Choices" , s.legalChoices)}"
94
- ss.map(helpStr).mkString(" " , " \n " , s " \n ${formatName(" @<file>" )} ${formatDescription(" A text file containing compiler arguments (options and source files)." )}\n " )
60
+ case _ => " "
61
+ val info = List (shortHelp(s), if defaultValue.nonEmpty then s " Default $defaultValue" else " " , if s.legalChoices.nonEmpty then s " Choices ${s.legalChoices}" else " " )
62
+ (s.name, info.filter(_.nonEmpty).mkString(" \n " ))
63
+ end help
64
+
65
+ val ss = settings.allSettings.filter(p).toList.sortBy(_.name)
66
+ val formatter = Columnator (" " , " " , maxField = 30 )
67
+ val fresh = ContextBase ().initialCtx.fresh.setSettings(summon[SettingsState ])
68
+ formatter(List (ss.map(help) :+ (" @<file>" , " A text file containing compiler arguments (options and source files)." )))(using fresh)
95
69
end availableOptionsMsg
96
70
97
71
protected def shortUsage : String = s " Usage: $cmdName <options> <source files> "
98
72
99
73
protected def createUsageMsg (label : String , shouldExplain : Boolean , cond : Setting [? ] => Boolean )(using settings : ConcreteSettings )(using SettingsState ): String =
100
74
val prefix = List (
101
75
Some (shortUsage),
102
- Some (explainAdvanced) filter (_ => shouldExplain),
76
+ Some (explainAdvanced). filter(_ => shouldExplain),
103
77
Some (label + " options include:" )
104
- ).flatten mkString " \n "
78
+ ).flatten. mkString( " \n " )
105
79
106
80
prefix + " \n " + availableOptionsMsg(cond)
107
81
@@ -136,31 +110,10 @@ trait CliCommand:
136
110
createUsageMsg(" Possible private" , shouldExplain = true , isPrivate)
137
111
138
112
/** Used for the formatted output of -Xshow-phases */
139
- protected def phasesMessage (using ctx : Context ): String =
140
-
113
+ protected def phasesMessage (using Context ): String =
141
114
val phases = new Compiler ().phases
142
- val nameLimit = 25
143
- val maxCol = ctx.settings.pageWidth.value
144
- val maxName = phases.flatten.map(_.phaseName.length).max
145
- val width = maxName.min(nameLimit)
146
- val maxDesc = maxCol - (width + 6 )
147
- val fmt = s " % ${width}. ${width}s %. ${maxDesc}s%n "
148
-
149
- val sb = new StringBuilder
150
- sb ++= fmt.format(" phase name" , " description" )
151
- sb ++= fmt.format(" ----------" , " -----------" )
152
-
153
- phases.foreach {
154
- case List (single) =>
155
- sb ++= fmt.format(single.phaseName, single.description)
156
- case Nil => ()
157
- case more =>
158
- sb ++= fmt.format(s " { " , " " )
159
- more.foreach { mini => sb ++= fmt.format(mini.phaseName, mini.description) }
160
- sb ++= fmt.format(s " } " , " " )
161
- }
162
- sb.mkString
163
-
115
+ val formatter = Columnator (" phase name" , " description" , maxField = 25 )
116
+ formatter(phases.map(mega => mega.map(p => (p.phaseName, p.description))))
164
117
165
118
/** Provide usage feedback on argument summary, assuming that all settings
166
119
* are already applied in context.
@@ -188,3 +141,56 @@ trait CliCommand:
188
141
189
142
extension [T ](setting : Setting [T ])
190
143
protected def value (using ss : SettingsState ): T = setting.valueIn(ss)
144
+
145
+ extension (s : String )
146
+ def padLeft (width : Int ): String = String .format(s " % ${width}s " , s)
147
+
148
+ // Formatting for -help and -Vphases in two columns, handling long field1 and wrapping long field2
149
+ class Columnator (heading1 : String , heading2 : String , maxField : Int , separation : Int = 2 ):
150
+ def apply (texts : List [List [(String , String )]])(using Context ): String = StringBuilder ().tap(columnate(_, texts)).toString
151
+
152
+ private def columnate (sb : StringBuilder , texts : List [List [(String , String )]])(using Context ): Unit =
153
+ import Highlighting .*
154
+ val colors = Seq (Green (_), Yellow (_), Magenta (_), Cyan (_), Red (_))
155
+ val nocolor = texts.length == 1
156
+ def color (index : Int ): String => Highlight = if nocolor then NoColor (_) else colors(index % colors.length)
157
+ val maxCol = ctx.settings.pageWidth.value
158
+ val field1 = maxField.min(texts.flatten.map(_._1.length).filter(_ < maxField).max) // widest field under maxField
159
+ val field2 = if field1 + separation + maxField < maxCol then maxCol - field1 - separation else 0 // skinny window -> terminal wrap
160
+ val separator = " " * separation
161
+ val EOL = " \n "
162
+ def formatField1 (text : String ): String = if text.length <= field1 then text.padLeft(field1) else text + EOL + " " .padLeft(field1)
163
+ def formatField2 (text : String ): String =
164
+ def loopOverField2 (fld : String ): List [String ] =
165
+ if field2 == 0 || fld.length <= field2 then List (fld)
166
+ else
167
+ fld.lastIndexOf(" " , field2) match
168
+ case - 1 => List (fld)
169
+ case i => val (prefix, rest) = fld.splitAt(i) ; prefix :: loopOverField2(rest.trim)
170
+ text.split(" \n " ).toList.flatMap(loopOverField2).filter(_.nonEmpty).mkString(EOL + " " .padLeft(field1) + separator)
171
+ end formatField2
172
+ def format (first : String , second : String , index : Int , colorPicker : Int => String => Highlight ) =
173
+ sb.append(colorPicker(index)(formatField1(first)).show)
174
+ .append(separator)
175
+ .append(formatField2(second))
176
+ .append(EOL ): Unit
177
+ def fancy (first : String , second : String , index : Int ) = format(first, second, index, color)
178
+ def plain (first : String , second : String ) = format(first, second, 0 , _ => NoColor (_))
179
+
180
+ if heading1.nonEmpty then
181
+ plain(heading1, heading2)
182
+ plain(" -" * heading1.length, " -" * heading2.length)
183
+
184
+ def emit (index : Int )(textPair : (String , String )): Unit = fancy(textPair._1, textPair._2, index)
185
+ def group (index : Int )(body : Int => Unit ): Unit =
186
+ if ! ctx.useColors then plain(s " { " , " " )
187
+ body(index)
188
+ if ! ctx.useColors then plain(s " } " , " " )
189
+
190
+ texts.zipWithIndex.foreach { (text, index) =>
191
+ text match
192
+ case List (single) => emit(index)(single)
193
+ case Nil =>
194
+ case mega => group(index)(i => mega.foreach(emit(i)))
195
+ }
196
+ end Columnator
0 commit comments