@@ -12,6 +12,7 @@ import (
1212 "os"
1313 "os/exec"
1414 "path/filepath"
15+ "reflect"
1516 "regexp"
1617 "strconv"
1718 "strings"
@@ -197,19 +198,78 @@ func getGoVersion(version string) (int64, int64, error) {
197198 return major , minor , nil
198199}
199200
201+ var (
202+ rxValidPythonName = regexp .MustCompile (`^[\pL_][\pL_\pN]+$` )
203+ )
204+
200205func extractPythonName (gname , gdoc string ) (string , string , error ) {
201- const PythonName = "\n gopy:name "
202- i := strings .Index (gdoc , PythonName )
206+ const (
207+ PythonName = "gopy:name "
208+ NLPythonName = "\n " + PythonName
209+ )
210+ i := - 1
211+ var tag string
212+ // Check for either a doc string that starts with our tag,
213+ // or as the first token of a newline
214+ if strings .HasPrefix (gdoc , PythonName ) {
215+ i = 0
216+ tag = PythonName
217+ } else {
218+ i = strings .Index (gdoc , NLPythonName )
219+ tag = NLPythonName
220+ }
203221 if i < 0 {
204222 return gname , gdoc , nil
205223 }
206- s := gdoc [i + len (PythonName ):]
224+ s := gdoc [i + len (tag ):]
207225 if end := strings .Index (s , "\n " ); end > 0 {
208- validIdPattern := regexp .MustCompile (`^[\pL_][\pL_\pN]+$` )
209- if ! validIdPattern .MatchString (s [:end ]) {
226+ if ! isValidPythonName (s [:end ]) {
210227 return "" , "" , fmt .Errorf ("gopy: invalid identifier: %s" , s [:end ])
211228 }
212229 return s [:end ], gdoc [:i ] + s [end :], nil
213230 }
214231 return gname , gdoc , nil
215232}
233+
234+ // extractPythonNameFieldTag parses a struct field tag and returns
235+ // a new python name. If the tag is not defined then the original
236+ // name is returned.
237+ // If the tag name is specified but is an invalid python identifier,
238+ // then an error is returned.
239+ func extractPythonNameFieldTag (gname , tag string ) (string , error ) {
240+ const tagKey = "gopy"
241+ if tag == "" {
242+ return gname , nil
243+ }
244+ tagVal := reflect .StructTag (tag ).Get (tagKey )
245+ if tagVal == "" {
246+ return gname , nil
247+ }
248+ if ! isValidPythonName (tagVal ) {
249+ return "" , fmt .Errorf ("gopy: invalid identifier for struct field tag: %s" , tagVal )
250+ }
251+ return tagVal , nil
252+ }
253+
254+ // isValidPythonName returns true if the string is a valid
255+ // python identifier name
256+ func isValidPythonName (name string ) bool {
257+ if name == "" {
258+ return false
259+ }
260+ return rxValidPythonName .MatchString (name )
261+ }
262+
263+ var (
264+ rxMatchFirstCap = regexp .MustCompile ("([A-Z])([A-Z][a-z])" )
265+ rxMatchAllCap = regexp .MustCompile ("([a-z0-9])([A-Z])" )
266+ )
267+
268+ // toSnakeCase converts the provided string to snake_case.
269+ // Based on https://gist.github.com/stoewer/fbe273b711e6a06315d19552dd4d33e6
270+ func toSnakeCase (input string ) string {
271+ output := rxMatchFirstCap .ReplaceAllString (input , "${1}_${2}" )
272+ output = rxMatchAllCap .ReplaceAllString (output , "${1}_${2}" )
273+ output = strings .ReplaceAll (output , "-" , "_" )
274+ return strings .ToLower (output )
275+ }
0 commit comments