4242
4343 mdCodeFenceStartRegexp = regexp .MustCompile ("^\\ w*`{3,}[^`]*$" )
4444
45- errNoREADMECodeBlocksFound = fmt .Errorf ("lifecycle.extractCodeBlocks: no code blocks immediately preceded by %s found" , codeTag )
45+ errNoReadmeCodeBlocksFound = fmt .Errorf ("lifecycle.extractCodeBlocks: no code blocks immediately preceded by %s found" , codeTag )
46+ errCodeBlockNotClosed = fmt .Errorf ("unexpected EOF: code block not closed" )
47+ errCodeBlockStartNotFound = fmt .Errorf ("expecting start of code block immediately after code tag" )
48+ errEOFAfterCodeTag = fmt .Errorf ("unexpected EOF: file ended immediately after code tag" )
49+ errCodeBlockEndAfterLineCont = "end of code block: expecting command line continuation"
4650)
4751
4852// codeBlock is a slice of strings containing terminal commands. codeBlocks, for example, could be used to hold the
@@ -68,7 +72,7 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error)
6872
6973 i ++
7074 if i >= len (cb ) {
71- return nil , fmt .Errorf ("unexpected end of code block: expecting command line continuation ; code block dump:\n %s" , strings .Join (cb , "\n " ))
75+ return nil , fmt .Errorf ("%s ; code block dump:\n %s" , errCodeBlockEndAfterLineCont , strings .Join (cb , "\n " ))
7276 }
7377
7478 l := cb [i ]
@@ -98,9 +102,10 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error)
98102 return cmds , nil
99103}
100104
101- // parseREADME parses a README file with the given name. It reads terminal commands surrounded by one of the codeTags
102- // listed above and loads them into a Lifecycle. In the process, it replaces the Cloud Run service name and Container
103- // Registry tag with the provided inputs.
105+ // parseREADME parses a README file with the given name. It parses terminal commands in code blocks annotated by the
106+ // codeTag and loads them into a Lifecycle. In the process, it replaces the Cloud Run service name and Container
107+ // Registry tag with the provided inputs. It also expands environment variables and supports bash-style line
108+ // continuations.
104109func parseREADME (filename , serviceName , gcrURL string ) (Lifecycle , error ) {
105110 file , err := os .Open (filename )
106111 if err != nil {
@@ -109,20 +114,29 @@ func parseREADME(filename, serviceName, gcrURL string) (Lifecycle, error) {
109114 defer file .Close ()
110115
111116 scanner := bufio .NewScanner (file )
117+
118+ return extractLifecycle (scanner , serviceName , gcrURL )
119+ }
120+
121+ // extractLifecycle is a helper function for parseREADME. It takes a scanner that reads from a Markdown file and parses
122+ // terminal commands in code blocks annotated by the codeTag and loads them into a Lifecycle. In the process, it
123+ // replaces the Cloud Run service name and Container Registry tag with the provided inputs. It also expands environment
124+ // variables and supports bash-style line continuations.
125+ func extractLifecycle (scanner * bufio.Scanner , serviceName , gcrURL string ) (Lifecycle , error ) {
112126 codeBlocks , err := extractCodeBlocks (scanner )
113127 if err != nil {
114- return nil , fmt .Errorf ("lifecycle.extractCodeBlocks: %s: %w" , filename , err )
128+ return nil , fmt .Errorf ("lifecycle.extractCodeBlocks: %w" , err )
115129 }
116130
117131 if len (codeBlocks ) == 0 {
118- return nil , errNoREADMECodeBlocksFound
132+ return nil , errNoReadmeCodeBlocksFound
119133 }
120134
121135 var l Lifecycle
122136 for _ , b := range codeBlocks {
123137 cmds , err := b .toCommands (serviceName , gcrURL )
124138 if err != nil {
125- return l , fmt .Errorf ("codeBlock.toCommands: code blocks in %s: %w" , filename , err )
139+ return l , fmt .Errorf ("codeBlock.toCommands: %w" , err )
126140 }
127141
128142 l = append (l , cmds ... )
@@ -147,14 +161,14 @@ func extractCodeBlocks(scanner *bufio.Scanner) ([]codeBlock, error) {
147161 if err := scanner .Err (); err != nil {
148162 return nil , fmt .Errorf ("line %d: bufio.Scanner.Scan: %w" , lineNum , err )
149163 }
150- return nil , fmt . Errorf ( "unexpected EOF: file ended immediately after code tag" )
164+ return nil , errEOFAfterCodeTag
151165 }
152166 lineNum ++
153167
154168 startCodeBlockLine := scanner .Text ()
155169 m := mdCodeFenceStartRegexp .MatchString (startCodeBlockLine )
156170 if ! m {
157- return nil , fmt .Errorf ("line %d: expecting start of code block immediately after code tag " , lineNum )
171+ return nil , fmt .Errorf ("line %d: %w " , lineNum , errCodeBlockStartNotFound )
158172 }
159173
160174 c := strings .Count (startCodeBlockLine , "`" )
@@ -178,7 +192,7 @@ func extractCodeBlocks(scanner *bufio.Scanner) ([]codeBlock, error) {
178192 }
179193
180194 if ! blockClosed {
181- return nil , fmt . Errorf ( "unexpected EOF: code block not closed" )
195+ return nil , errCodeBlockNotClosed
182196 }
183197
184198 blocks = append (blocks , block )
@@ -204,15 +218,15 @@ func replaceServiceName(command, serviceName string) string {
204218 sp := strings .Split (command , " " )
205219
206220 // Detects if the user specified the Cloud Run service name in an environment variable
207- for i := 0 ; i < len (sp ); i ++ {
221+ for i := 0 ; i < len (sp ); i ++ {
208222 if sp [i ] == os .ExpandEnv ("$CLOUD_RUN_SERVICE_NAME" ) {
209223 sp [i ] = serviceName
210224 return strings .Join (sp , " " )
211225 }
212226 }
213227
214228 // Searches for specific gcloud keywords and takes service name from them
215- for i := 0 ; i < len (sp ) - 1 ; i ++ {
229+ for i := 0 ; i < len (sp )- 1 ; i ++ {
216230 if sp [i ] == "deploy" || sp [i ] == "update" {
217231 sp [i + 1 ] = serviceName
218232 return strings .Join (sp , " " )
0 commit comments