@@ -389,14 +389,37 @@ func (p *GemfileParser) parseSource(line string) (Source, bool, error) {
389389// parseGroups parses group declarations
390390// Examples: group :development, :test do
391391func (p * GemfileParser ) parseGroups (line string ) []string {
392- // Extract group names using regex
393- re := regexp .MustCompile (`:(\w+)` )
392+ // Strip inline comments first
393+ if idx := strings .Index (line , "#" ); idx != - 1 {
394+ line = line [:idx ]
395+ }
396+
397+ // Remove the 'group' keyword
398+ line = strings .TrimSpace (line )
399+ line = strings .TrimPrefix (line , "group " )
400+
401+ // Trim everything after the 'do' token (not just suffix)
402+ if idx := strings .Index (line , " do" ); idx != - 1 {
403+ line = line [:idx ]
404+ } else if idx := strings .Index (line , "do" ); idx != - 1 {
405+ // Handle cases like 'group(:dev)do' - although rare in Gemfiles
406+ // Check if 'do' is a separate word or at least not part of a symbol/string
407+ // For simplicity, we can check if it's preceded by space or closing paren/quote
408+ line = line [:idx ]
409+ }
410+
411+ // Extract group names using a more precise regex
412+ // Supports symbols like :test or strings like "test"
413+ // match :(\w+) and ['"](\w+)['"]
414+ re := regexp .MustCompile (`:(\w+)|['"](\w+)['"]` )
394415 matches := re .FindAllStringSubmatch (line , - 1 )
395416
396417 groups := make ([]string , 0 , len (matches ))
397418 for _ , match := range matches {
398- if len (match ) > 1 {
419+ if len (match ) > 1 && match [ 1 ] != "" {
399420 groups = append (groups , match [1 ])
421+ } else if len (match ) > 2 && match [2 ] != "" {
422+ groups = append (groups , match [2 ])
400423 }
401424 }
402425
@@ -461,35 +484,16 @@ func (p *GemfileParser) extractVersionConstraints(line string) []string {
461484 nameRe := regexp .MustCompile (`gem\s+['"][^'"]+['"],?\s*` )
462485 remaining := nameRe .ReplaceAllString (line , "" )
463486
464- // Pattern to match version strings (not including options like require:, github:, etc.)
465- // Stop at first option keyword
466- optionKeys := []string {
467- "require:" ,
468- "github:" ,
469- "git:" ,
470- "path:" ,
471- "groups:" ,
472- "group:" ,
473- "platforms:" ,
474- "platform:" ,
475- "source:" ,
476- }
477-
478- optionsStart := - 1
479- for _ , key := range optionKeys {
480- if idx := strings .Index (remaining , key ); idx != - 1 && (optionsStart == - 1 || idx < optionsStart ) {
481- optionsStart = idx
482- }
483- }
484-
485- versionPart := remaining
486- if optionsStart != - 1 {
487- versionPart = remaining [:optionsStart ]
487+ // Stop at first option keyword or hash rocket
488+ // Supports both symbolized hashes and hash rockets
489+ optionRe := regexp .MustCompile (`(?::\w+\s*=>|[\w:]+:)` )
490+ if loc := optionRe .FindStringIndex (remaining ); loc != nil {
491+ remaining = remaining [:loc [0 ]]
488492 }
489493
490494 // Extract all quoted strings from the version part
491495 re := regexp .MustCompile (`['"]([^'"]+)['"]` )
492- matches := re .FindAllStringSubmatch (versionPart , - 1 )
496+ matches := re .FindAllStringSubmatch (remaining , - 1 )
493497
494498 constraints := make ([]string , 0 , len (matches ))
495499 for _ , match := range matches {
@@ -503,8 +507,8 @@ func (p *GemfileParser) extractVersionConstraints(line string) []string {
503507
504508// extractSource extracts git/path source information
505509func (p * GemfileParser ) extractSource (line string ) * Source {
506- // Check for github source: github: 'user/repo'
507- if githubRe := regexp .MustCompile (`github: \s*['"]([^'"]+)['"]` ); githubRe .MatchString (line ) {
510+ // Check for github source: github: 'user/repo' or :github => 'user/repo'
511+ if githubRe := regexp .MustCompile (`(?:: github\s*=>|github:) \s*['"]([^'"]+)['"]` ); githubRe .MatchString (line ) {
508512 matches := githubRe .FindStringSubmatch (line )
509513 if len (matches ) > 1 {
510514 source := & Source {
@@ -513,7 +517,7 @@ func (p *GemfileParser) extractSource(line string) *Source {
513517 }
514518
515519 // Extract branch/tag/ref
516- if branchRe := regexp .MustCompile (`branch: \s*['"]([^'"]+)['"]` ); branchRe .MatchString (line ) {
520+ if branchRe := regexp .MustCompile (`(?:: branch\s*=>|branch:) \s*['"]([^'"]+)['"]` ); branchRe .MatchString (line ) {
517521 branchMatches := branchRe .FindStringSubmatch (line )
518522 if len (branchMatches ) > 1 {
519523 source .Branch = branchMatches [1 ]
@@ -524,19 +528,29 @@ func (p *GemfileParser) extractSource(line string) *Source {
524528 }
525529 }
526530
527- // Check for git source: git: 'https://...'
528- if gitRe := regexp .MustCompile (`git: \s*['"]([^'"]+)['"]` ); gitRe .MatchString (line ) {
531+ // Check for git source: git: 'https://...' or :git => 'https://...'
532+ if gitRe := regexp .MustCompile (`(?:: git\s*=>|git:) \s*['"]([^'"]+)['"]` ); gitRe .MatchString (line ) {
529533 matches := gitRe .FindStringSubmatch (line )
530534 if len (matches ) > 1 {
531- return & Source {
535+ source := & Source {
532536 Type : "git" ,
533537 URL : matches [1 ],
534538 }
539+
540+ // Extract branch/tag/ref for generic git source too
541+ if branchRe := regexp .MustCompile (`(?::branch\s*=>|branch:)\s*['"]([^'"]+)['"]` ); branchRe .MatchString (line ) {
542+ branchMatches := branchRe .FindStringSubmatch (line )
543+ if len (branchMatches ) > 1 {
544+ source .Branch = branchMatches [1 ]
545+ }
546+ }
547+
548+ return source
535549 }
536550 }
537551
538- // Check for path source: path: 'local/path'
539- if pathRe := regexp .MustCompile (`path: \s*['"]([^'"]+)['"]` ); pathRe .MatchString (line ) {
552+ // Check for path source: path: 'local/path' or :path => 'local/path'
553+ if pathRe := regexp .MustCompile (`(?:: path\s*=>|path:) \s*['"]([^'"]+)['"]` ); pathRe .MatchString (line ) {
540554 matches := pathRe .FindStringSubmatch (line )
541555 if len (matches ) > 1 {
542556 return & Source {
@@ -546,8 +560,8 @@ func (p *GemfileParser) extractSource(line string) *Source {
546560 }
547561 }
548562
549- // Check for inline rubygems source: source: 'https://...'
550- if sourceRe := regexp .MustCompile (`source: \s*['"]([^'"]+)['"]` ); sourceRe .MatchString (line ) {
563+ // Check for inline rubygems source: source: 'https://...' or :source => 'https://...'
564+ if sourceRe := regexp .MustCompile (`(?:: source\s*=>|source:) \s*['"]([^'"]+)['"]` ); sourceRe .MatchString (line ) {
551565 matches := sourceRe .FindStringSubmatch (line )
552566 if len (matches ) > 1 {
553567 return & Source {
@@ -562,8 +576,8 @@ func (p *GemfileParser) extractSource(line string) *Source {
562576
563577// extractRequire extracts require option
564578func (p * GemfileParser ) extractRequire (line string ) * string {
565- // require: false
566- if requireRe := regexp .MustCompile (`require: \s*(false|['"][^'"]*['"])` ); requireRe .MatchString (line ) {
579+ // require: false or :require => false or require: 'foo' or :require => 'foo'
580+ if requireRe := regexp .MustCompile (`(?:: require\s*=>|require:) \s*(false|['"][^'"]*['"])` ); requireRe .MatchString (line ) {
567581 matches := requireRe .FindStringSubmatch (line )
568582 if len (matches ) > 1 {
569583 require := matches [1 ]
@@ -582,55 +596,38 @@ func (p *GemfileParser) extractRequire(line string) *string {
582596
583597// extractGroupOverrides extracts group overrides from gem line
584598func (p * GemfileParser ) extractGroupOverrides (line string ) []string {
585- // groups: [:development, :test]
586- if groupsRe := regexp .MustCompile (`groups?:\s*\[([^\]]+)\]` ); groupsRe .MatchString (line ) {
587- matches := groupsRe .FindStringSubmatch (line )
588- if len (matches ) > 1 {
589- groupStr := matches [1 ]
590- groupRe := regexp .MustCompile (`:(\w+)` )
591- groupMatches := groupRe .FindAllStringSubmatch (groupStr , - 1 )
592-
593- groups := make ([]string , 0 , len (groupMatches ))
594- for _ , match := range groupMatches {
595- if len (match ) > 1 {
596- groups = append (groups , match [1 ])
597- }
598- }
599- return groups
600- }
601- }
602-
603- return nil
599+ // groups: [:development, :test] or :groups => [:development, :test]
600+ pattern := `(?::groups?\s*=>|groups?:\s*)\s*\[([^\]]+)\]`
601+ return p .extractArrayFromOption (line , pattern )
604602}
605603
606604// extractPlatforms extracts platform restrictions from gem line
607605func (p * GemfileParser ) extractPlatforms (line string ) []string {
608- // platforms: [:windows_31, :jruby]
609- if platformsRe := regexp .MustCompile (`platforms?:\s*\[([^\]]+)\]` ); platformsRe .MatchString (line ) {
610- matches := platformsRe .FindStringSubmatch (line )
611- if len (matches ) > 1 {
612- platformStr := matches [1 ]
613- platformRe := regexp .MustCompile (`:(\w+)` )
614- platformMatches := platformRe .FindAllStringSubmatch (platformStr , - 1 )
615-
616- platforms := make ([]string , 0 , len (platformMatches ))
617- for _ , match := range platformMatches {
618- if len (match ) > 1 {
619- platforms = append (platforms , match [1 ])
620- }
621- }
622- return platforms
623- }
624- }
606+ // platforms: [:windows_31, :jruby] or :platforms => [:windows_31, :jruby]
607+ pattern := `(?::platforms?\s*=>|platforms?:\s*)\s*\[([^\]]+)\]`
608+ return p .extractArrayFromOption (line , pattern )
609+ }
625610
626- // platforms: :jruby (single platform)
627- if platformRe := regexp .MustCompile (`platforms?:\s*:(\w+)` ); platformRe .MatchString (line ) {
628- matches := platformRe .FindStringSubmatch (line )
629- if len (matches ) > 1 {
630- return []string {matches [1 ]}
611+ // extractArrayFromOption extracts an array of symbols/strings from a line using the given pattern
612+ func (p * GemfileParser ) extractArrayFromOption (line , optionPattern string ) []string {
613+ re := regexp .MustCompile (optionPattern )
614+ matches := re .FindStringSubmatch (line )
615+ if len (matches ) > 1 {
616+ content := matches [1 ]
617+ // Match :symbol or "string" or 'string'
618+ itemRe := regexp .MustCompile (`:(\w+)|['"](\w+)['"]` )
619+ itemMatches := itemRe .FindAllStringSubmatch (content , - 1 )
620+
621+ items := make ([]string , 0 , len (itemMatches ))
622+ for _ , match := range itemMatches {
623+ if len (match ) > 1 && match [1 ] != "" {
624+ items = append (items , match [1 ])
625+ } else if len (match ) > 2 && match [2 ] != "" {
626+ items = append (items , match [2 ])
627+ }
631628 }
629+ return items
632630 }
633-
634631 return nil
635632}
636633
@@ -645,13 +642,6 @@ func (p *GemfileParser) parseRubyVersion(line string) string {
645642}
646643
647644// parseGemspecDirective parses gemspec directive
648- // Examples:
649- //
650- // gemspec
651- // gemspec path: "components/payment"
652- // gemspec name: "payment_core"
653- // gemspec development_group: :ci
654- // gemspec path: ".", name: "my_gem", development_group: :test
655645func (p * GemfileParser ) parseGemspecDirective (line string ) * GemspecReference {
656646 gemspecRef := & GemspecReference {
657647 Path : "." ,
@@ -664,39 +654,29 @@ func (p *GemfileParser) parseGemspecDirective(line string) *GemspecReference {
664654 return gemspecRef
665655 }
666656
667- // Parse path option
668- if pathRe := regexp .MustCompile (`path:\s*['"]([^'"]+)['"]` ); pathRe .MatchString (line ) {
669- matches := pathRe .FindStringSubmatch (line )
670- if len (matches ) > 1 {
671- gemspecRef .Path = matches [1 ]
672- }
673- }
657+ // Parse various options
658+ p .parseGemspecOption (line , "path" , & gemspecRef .Path )
659+ p .parseGemspecOption (line , "name" , & gemspecRef .Name )
660+ p .parseGemspecOption (line , "development_group" , & gemspecRef .DevelopmentGroup )
661+ p .parseGemspecOption (line , "glob" , & gemspecRef .Glob )
674662
675- // Parse name option
676- if nameRe := regexp .MustCompile (`name:\s*['"]([^'"]+)['"]` ); nameRe .MatchString (line ) {
677- matches := nameRe .FindStringSubmatch (line )
678- if len (matches ) > 1 {
679- gemspecRef .Name = matches [1 ]
680- }
681- }
682-
683- // Parse development_group option
684- if devGroupRe := regexp .MustCompile (`development_group:\s*:(\w+)` ); devGroupRe .MatchString (line ) {
685- matches := devGroupRe .FindStringSubmatch (line )
686- if len (matches ) > 1 {
687- gemspecRef .DevelopmentGroup = matches [1 ]
688- }
689- }
663+ return gemspecRef
664+ }
690665
691- // Parse glob option
692- if globRe := regexp .MustCompile (`glob:\s*['"]([^'"]+)['"]` ); globRe .MatchString (line ) {
693- matches := globRe .FindStringSubmatch (line )
694- if len (matches ) > 1 {
695- gemspecRef .Glob = matches [1 ]
666+ // parseGemspecOption parses a single option from a gemspec directive
667+ func (p * GemfileParser ) parseGemspecOption (line , optionName string , target * string ) {
668+ // Pattern for both symbol and keyword syntax:
669+ // :option => "value", :option => :value, option: "value", option: :value
670+ pattern := fmt .Sprintf (`(?::%s\s*=>|%s:)\s*(?:['"]([^'"]+)['"]|:?(\w+))` , optionName , optionName )
671+ re := regexp .MustCompile (pattern )
672+ matches := re .FindStringSubmatch (line )
673+ if len (matches ) > 1 {
674+ if matches [1 ] != "" {
675+ * target = matches [1 ]
676+ } else if len (matches ) > 2 && matches [2 ] != "" {
677+ * target = matches [2 ]
696678 }
697679 }
698-
699- return gemspecRef
700680}
701681
702682// parseVariable parses variable assignments like: rails_version = '~> 8.0.1'
0 commit comments