package cmd import ( "fmt" "strings" "github.com/runescape-wiki/rsw/internal/htmlconv" "github.com/runescape-wiki/rsw/internal/render" "github.com/runescape-wiki/rsw/internal/wiki" "github.com/spf13/cobra" ) func newSkillCmd() *cobra.Command { var levelRange string cmd := &cobra.Command{ Use: "skill ", Short: "Look up skill training methods", Long: `Fetches training guide information for a skill. Optionally filter to a specific level range. With --ironman, emphasizes training methods viable without GE access. Examples: rsw osrs skill mining rsw rs3 skill "prayer" --level 50-70 rsw osrs skill slayer --ironman`, Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { skillName := args[0] wikiClient := wiki.NewClient(GameBaseURL()) trainingTitle := capitalizeFirst(strings.ToLower(skillName)) + " training" if Raw() { page, err := wikiClient.GetPage(trainingTitle) if err != nil { page, err = wikiClient.GetPage(capitalizeFirst(strings.ToLower(skillName))) if err != nil { return fmt.Errorf("failed to fetch skill page: %w", err) } } fmt.Println(page.Wikitext) return nil } page, err := wikiClient.GetPageHTML(trainingTitle) if err != nil { page, err = wikiClient.GetPageHTML(capitalizeFirst(strings.ToLower(skillName))) if err != nil { return fmt.Errorf("failed to fetch skill page: %w", err) } } md := render.New() md.H1(fmt.Sprintf("%s Training Guide", page.Title)) if Ironman() { md.P("*Showing methods suitable for ironman accounts (no GE access).*") } sections := htmlconv.ListSections(page.HTML) if len(sections) > 0 { md.H2("Contents") for _, s := range sections { if s.Level == 2 { md.Bullet(s.Name) } } md.Newline() } if levelRange != "" { found := false for _, s := range sections { if strings.Contains(strings.ToLower(s.Name), strings.ToLower(levelRange)) || sectionMatchesLevelRange(s.Name, levelRange) { body := htmlconv.ExtractSection(page.HTML, s.Name) if strings.TrimSpace(body) != "" { md.H2(s.Name) md.Line(body) found = true } } } if !found { md.P(fmt.Sprintf("*No section found matching level range %q. Showing full guide.*", levelRange)) renderFullGuideHTML(md, page.HTML, sections) } } else { renderFullGuideHTML(md, page.HTML, sections) } fmt.Print(md.String()) return nil }, } cmd.Flags().StringVar(&levelRange, "level", "", "Filter to a level range (e.g., '50-70')") return cmd } func renderFullGuideHTML(md *render.Builder, pageHTML string, sections []htmlconv.SectionInfo) { for _, s := range sections { if s.Level != 2 { continue } body := htmlconv.ExtractSection(pageHTML, s.Name) if strings.TrimSpace(body) != "" { md.H2(s.Name) md.Line(body) } } } func sectionMatchesLevelRange(heading, levelRange string) bool { h := strings.ToLower(heading) h = strings.ReplaceAll(h, "–", "-") h = strings.ReplaceAll(h, "—", "-") h = strings.ReplaceAll(h, " ", "") lr := strings.ToLower(levelRange) lr = strings.ReplaceAll(lr, " ", "") return strings.Contains(h, lr) } func capitalizeFirst(s string) string { if s == "" { return s } return strings.ToUpper(s[:1]) + s[1:] } func init() { RegisterCommand(newSkillCmd) }