James J. Little

163 papers A* 29A 32B 1C 4Journal 55Unranked 40
YearRankTypeTitle / Venue / Authors
2026 J jnl
Trans. Mach. Learn. Res.
Mir Rayat Imtiaz Hossain, Mennatullah Siam, Leonid Sigal, James J. Little
2025 J jnl
CoRR
Shih-Han Chou, Shivam Chandhok, James J. Little, Leonid Sigal
2025 J jnl
CoRR
Mir Rayat Imtiaz Hossain, Mennatullah Siam, Leonid Sigal, James J. Little
2024 A conf
WACV
Mir Rayat Imtiaz Hossain, Leonid Sigal, James J. Little
2024 J jnl
Comput. Vis. Image Underst.
Shih-Han Chou, James J. Little, Leonid Sigal
2024 J jnl
CoRR
Shih-Han Chou, Shivam Chandhok, James J. Little, Leonid Sigal
2024 A* conf
CVPR
Mir Rayat Imtiaz Hossain, Mennatullah Siam, Leonid Sigal, James J. Little
2024 J jnl
CoRR
Mir Rayat Imtiaz Hossain, Mennatullah Siam, Leonid Sigal, James J. Little
2023 J jnl
CoRR
Shih-Han Chou, James J. Little, Leonid Sigal
2022 J jnl
CoRR
Aritro Roy Arko, James J. Little, Kwang Moo Yi
2022 A* conf
CVPR
Bastian Wandt, James J. Little, Helge Rhodin
2022 J jnl
CoRR
Mir Rayat Imtiaz Hossain, Leonid Sigal, James J. Little
2022 conf
CRV
Shih-Han Chou, Zicong Fan, James J. Little, Leonid Sigal
2022 J jnl
CoRR
Abiramy Kuganesan, Shih-Yang Su, James J. Little, Helge Rhodin
2021 A conf
WACV
Alireza Shafaei, James J. Little, Mark Schmidt
2021 J jnl
CoRR
Bastian Wandt, James J. Little, Helge Rhodin
2021 J jnl
Dagstuhl Reports
Ulf Brefeld, Jesse Davis, Martin Lames, James J. Little
2019 A conf
BMVC
Alireza Shafaei, Mark Schmidt, James J. Little
2019 A conf
WACV
Jianhui Chen, Keyu Lu, Sijia Tian, James J. Little
2019 J jnl
CoRR
Zicong Fan, Si Yi Meng, Leonid Sigal, James J. Little
2019 A conf
BMVC
Jikai Lu, Jianhui Chen, James J. Little
2019 J jnl
CoRR
Jikai Lu, Jianhui Chen, James J. Little
2019 A conf
BMVC
Gursimran Singh, Leonid Sigal, James J. Little
2019 conf
CVPR Workshops
Jianhui Chen, James J. Little
2018 A conf
WACV
Jianhui Chen, Fangrui Zhu, James J. Little
2018 J jnl
CoRR
Jianhui Chen, Fangrui Zhu, James J. Little
2018 A conf
WACV
Jianhui Chen, Lili Meng, James J. Little
2018 J jnl
CoRR
Alireza Shafaei, Mark Schmidt, James J. Little
2018 A conf
IROS
Lili Meng, Frederick Tung, James J. Little, Julien Valentin, Clarence W. de Silva
2018 conf
ECCV (10)
Mir Rayat Imtiaz Hossain, James J. Little
2018 conf
ECCV (16)
Julieta Martinez, Shobhit Zakhmi, Holger H. Hoos, James J. Little
2018 J jnl
CoRR
Jianhui Chen, Keyu Lu, Sijia Tian, James J. Little
2018 J jnl
Comput. Vis. Image Underst.
Keyu Lu, Jianhui Chen, James J. Little, Hangen He
2018 J jnl
IEEE Trans. Robotics
Sina Radmard, David Meger, James J. Little, Elizabeth A. Croft
2018 J jnl
CoRR
Jianhui Chen, James J. Little
2018 C conf
ISCAS
Anahita Shojaei-Hashemi, Panos Nasiopoulos, James J. Little, Mahsa T. Pourazad
2017 A* conf
ICCV
Julieta Martinez, Rayat Hossain, Javier Romero, James J. Little
2017 J jnl
CoRR
Julieta Martinez, Rayat Hossain, Javier Romero, James J. Little
2017 J jnl
CoRR
Lili Meng, Jianhui Chen, Frederick Tung, James J. Little, Julien Valentin, Clarence W. de Silva
2017 A conf
IROS
Lili Meng, Jianhui Chen, Frederick Tung, James J. Little, Julien Valentin, Clarence W. de Silva
2017 conf
CVPR Workshops
Moumita Roy Tora, Jianhui Chen, James J. Little
2017 J jnl
CoRR
Lili Meng, Frederick Tung, James J. Little, Julien Valentin, Clarence W. de Silva
2017 J jnl
CoRR
Mir Rayat Imtiaz Hossain, James J. Little
2017 A conf
BMVC
Keyu Lu, Jianhui Chen, James J. Little, Hangen He
2017 J jnl
CoRR
Keyu Lu, Jianhui Chen, James J. Little, Hangen He
2017 A* conf
ICRA
Frederick Tung, James J. Little
2017 J jnl
IEEE Robotics Autom. Lett.
Frederick Tung, Jianhui Chen, Lili Meng, James J. Little
2017 J jnl
Comput. Vis. Image Underst.
Jianhui Chen, James J. Little
2016 A conf
WACV
Ankur Gupta, John He, Julieta Martinez, James J. Little, Robert J. Woodham
2016 A conf
BMVC
Lili Meng, Jianhui Chen, Frederick Tung, James J. Little, Clarence W. de Silva
2016 A conf
BMVC
Frederick Tung, James J. Little
2016 A* conf
CVPR
Jianhui Chen, Hoang Minh Le, Peter Carr, Yisong Yue, James J. Little
2016 A conf
BMVC
Alireza Shafaei, James J. Little, Mark Schmidt
2016 J jnl
CoRR
Alireza Shafaei, James J. Little, Mark Schmidt
2016 conf
CRV
Alireza Shafaei, James J. Little
2016 J jnl
CoRR
Alireza Shafaei, James J. Little
2016 conf
ECCV (2)
Julieta Martinez, Joris Clement, Holger H. Hoos, James J. Little
2016 conf
ACCV (1)
Frederick Tung, James J. Little
2016 J jnl
Comput. Vis. Image Underst.
Frederick Tung, James J. Little
2016 conf
ECCV Workshops (1)
Julieta Martinez, Holger H. Hoos, James J. Little
2015 A conf
WACV
Frederick Tung, Julieta Martinez, Holger H. Hoos, James J. Little
2015 ch.
Encyclopedia of Biometrics
Jeffrey E. Boyd, James J. Little
2015 J jnl
Comput. Vis. Image Underst.
Frederick Tung, James J. Little
2014 A* conf
CVPR
Ankur Gupta, Julieta Martinez, James J. Little, Robert J. Woodham
2014 A conf
WACV
Julieta Martinez, James J. Little, Nando de Freitas
2014 conf
ECCV (6)
Frederick Tung, James J. Little
2014 A* conf
ICRA
Junaed Sattar, James J. Little
2014 J jnl
Int. J. Multim. Data Eng. Manag.
Masaki Takahashi, Masahide Naemura, Mahito Fujii, James J. Little
2014 J jnl
CoRR
Julieta Martinez, Holger H. Hoos, James J. Little
2014 A conf
WACV
Georgii Oleinikov, Gregor Miller, James J. Little, Sidney S. Fels
2014 A conf
BMVC
Ankur Gupta, Alireza Shafaei, James J. Little, Robert J. Woodham
2013 A* conf
ICRA
Tristram Southey, James J. Little
2013 conf
CRV
Daniel Troniak, Junaed Sattar, Ankur Gupta, James J. Little, Wesley P. Chan, Ergun Calisgan, Elizabeth A. Croft, H. F. Machiel Van der Loos
2013 J jnl
IEEE Trans. Cybern.
Maodi Hu, Yunhong Wang, Zhaoxiang Zhang, De Zhang, James J. Little
2013 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Wei-Lwun Lu, Jo-Anne Ting, James J. Little, Kevin P. Murphy
2013 A* conf
ICRA
Ambrose Chan, Elizabeth A. Croft, James J. Little
2013 A* conf
ICRA
Sina Radmard, David Meger, Elizabeth A. Croft, James J. Little
2013 C conf
ISM
Masaki Takahashi, Masahide Naemura, Mahito Fujii, James J. Little
2013 J jnl
CoRR
Kenji Okuma, David G. Lowe, James J. Little
2013 J jnl
IEEE Trans. Inf. Forensics Secur.
Maodi Hu, Yunhong Wang, Zhaoxiang Zhang, James J. Little, Di Huang
2012 conf
AAAI Fall Symposium: Artificial Intelligence for Gerontechnology
Pooja Viswanathan, James J. Little, Alan K. Mackworth, Alex Mihailidis
2012 conf
ACCV (3)
Maodi Hu, Yunhong Wang, James J. Little
2012 A conf
BMVC
Michael Stark, Jonathan Krause, Bojan Pepik, David Meger, James J. Little, Bernt Schiele, Daphne Koller
2012 C conf
ACC
Sina Radmard, David Meger, Elizabeth A. Croft, James J. Little
2012 conf
ISER
David Meger, James J. Little
2011 conf
VISAPP
Kenji Okuma, Eric Brochu, David G. Lowe, James J. Little
2011 C conf
ACC
Ambrose Chan, Simon Léonard, Elizabeth A. Croft, James J. Little
2011 A conf
IROS
Ambrose Chan, Elizabeth A. Croft, James J. Little
2011 A conf
BMVC
David Meger, Christian Wojek, James J. Little, Bernt Schiele
2011 A* conf
CVPR
Wei-Lwun Lu, Jo-Anne Ting, Kevin P. Murphy, James J. Little
2011 A conf
IROS
David Meger, James J. Little
2011 A conf
ASSETS
Pooja Viswanathan, James J. Little, Alan K. Mackworth, Alex Mihailidis
2011 conf
CRV
Pooja Viswanathan, Tristram Southey, James J. Little, Alan K. Mackworth
2011 conf
CRV
Ankur Gupta, James J. Little, Robert J. Woodham
2010 conf
CRV
Pooja Viswanathan, Tristram Southey, James J. Little, Alan K. Mackworth
2010 conf
CRV
David Meger, Marius Muja, Scott Helmer, Ankur Gupta, Catherine Gamroth, Tomas Hoffman, Matthew A. Baumann, Tristram Southey, Pooyan Fazli, Walter Wohlkinger, Pooja Viswanathan, James J. Little, David G. Lowe, James Orwell
2010 conf
ACCV (1)
Scott Helmer, David Meger, Marius Muja, James J. Little, David G. Lowe
2010 J jnl
IEEE Trans. Robotics
Matthew A. Baumann, Simon Léonard, Elizabeth A. Croft, James J. Little
2010 A* conf
ICRA
David Meger, Ankur Gupta, James J. Little
2009 J jnl
IEEE Trans. Geosci. Remote. Sens.
Wei-Lwun Lu, Kevin P. Murphy, James J. Little, Alla Sheffer, Hongbo Fu
2009 conf
CRV
Pooja Viswanathan, David Meger, Tristram Southey, James J. Little, Alan K. Mackworth
2009 J jnl
Image Vis. Comput.
Robert Sim, James J. Little
2009 ch.
Encyclopedia of Biometrics
Jeffrey E. Boyd, James J. Little
2009 A conf
IROS
Simon Léonard, Elizabeth A. Croft, James J. Little
2009 J jnl
Image Vis. Comput.
Wei-Lwun Lu, Kenji Okuma, James J. Little
2008 J jnl
Robotics Auton. Syst.
David Meger, Per-Erik Forssén, Kevin Lai, Scott Helmer, Sancho McCann, Tristram Southey, Matthew A. Baumann, James J. Little, David G. Lowe
2008 conf
CRV
Wei-Lwun Lu, James J. Little, Alla Sheffer, Hongbo Fu
2008 A* conf
ICRA
Simon Léonard, Elizabeth A. Croft, James J. Little
2008 A* conf
ICRA
Per-Erik Forssén, David Meger, Kevin Lai, Scott Helmer, James J. Little, David G. Lowe
2008 A conf
IROS
Matthew A. Baumann, Donna C. Dupuis, Simon Léonard, Elizabeth A. Croft, James J. Little
2008 J jnl
IEEE Trans. Multim.
Hao Jiang, Sidney S. Fels, James J. Little
2008 A* conf
ICRA
Ambrose Chan, Elizabeth A. Croft, James J. Little
2007 A* conf
CVPR
Hao Jiang, Sidney S. Fels, James J. Little
2007 J jnl
Int. J. Comput. Vis.
Robert Sim, Pantelis Elinas, James J. Little
2007 A conf
IROS
Pantelis Elinas, James J. Little
2007 A* conf
CVPR
Changsong Shen, Sidney S. Fels, James J. Little
2007 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Jesse Hoey, James J. Little
2006 A conf
IROS
Robert Sim, James J. Little
2006 conf
CRV
Robert Sim, Pantelis Elinas, Matt Griffin, Alex Shyr, James J. Little
2006 conf
ECCV (4)
Yizheng Cai, Nando de Freitas, James J. Little
2006 conf
CRV
Wei-Lwun Lu, James J. Little
2006 conf
Cognitive Vision Systems
James J. Little, Jesse Hoey, Pantelis Elinas
2006 A* conf
ICRA
Pantelis Elinas, Robert Sim, James J. Little
2005 conf
WACV/MOTION
Don Ray Murray, James J. Little
2005 conf
CRV
Lin Zhong, James J. Little
2005 J jnl
IEEE Trans. Robotics
Stephen Se, David G. Lowe, James J. Little
2005 conf
Robotics: Science and Systems
Pantelis Elinas, James J. Little
2004 conf
ECCV (1)
Kenji Okuma, Ali Taleghani, Nando de Freitas, James J. Little, David G. Lowe
2004 conf
ECCV (3)
Jesse Hoey, James J. Little
2004 A conf
IROS
Don Ray Murray, James J. Little
2004 conf
3DPVT
Don Ray Murray, James J. Little
2004 conf
CVPR (2)
Jesse Hoey, James J. Little
2003 A* conf
ICCV
Jesse Hoey, James J. Little
2003 conf
Advanced Studies in Biometrics
Jeffrey E. Boyd, James J. Little
2003 J jnl
GeoInformatica
James J. Little, Ping Shi
2002 A* conf
ICRA
Pantelis Elinas, Jesse Hoey, Darrell Lahey, Jefferson D. Montgomery, Don Ray Murray, Stephen Se, James J. Little
2001 J jnl
Algorithmica
James J. Little, Ping Shi
2001 conf
Storage and Retrieval for Media Databases
James J. Little, Zhe Gu
2000 B conf
ICPR
James J. Little
2000 conf
Workshop on Human Motion
Jeffrey E. Boyd, James J. Little
2000 A* conf
CVPR
Jesse Hoey, James J. Little
2000 J jnl
Auton. Robots
Don Ray Murray, James J. Little
1999 A* conf
ICRA
Cullen Jennings, Don Ray Murray, James J. Little
1999 J jnl
Int. J. Comput. Vis.
Jiping Lu, James J. Little
1998 A conf
IROS
James J. Little, Jiping Lu, Don Ray Murray
1998 A conf
WACV
James J. Little, Ping Shi
1994 A conf
WACV
Andrew Naiberg, James J. Little
1994 A* conf
CVPR
Jeffrey E. Boyd, James J. Little
1994 conf
ICIP (3)
Esfandiar Bandari, James J. Little
1994 J jnl
J. Comput. Syst. Sci.
Guy E. Blelloch, James J. Little
1994 conf
ICPR (3)
James J. Little
1993 J jnl
Int. J. Comput. Vis.
Michael Bolotski, Rod Barman, James J. Little, Daniel Camporese
1993 A* conf
ICCV
Esfandiar Bandari, James J. Little
1990 A* conf
ECCV
James J. Little, Walter E. Gillett
1990 J jnl
Image Vis. Comput.
James J. Little, Walter E. Gillett
1990 conf
ICPR (2)
Rod Barman, Michael Bolotski, Daniel Camporese, James J. Little
1989 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
James J. Little, Guy E. Blelloch, Todd A. Cass
1988 J jnl
Neural Networks
Heinrich H. Bülthoff, James J. Little, Hanspeter A. Mallot
1988 A* conf
ICCV
James J. Little, Heinrich H. Bülthoff, Tomaso A. Poggio
1985 A* conf
IJCAI
James J. Little
1985 conf
SCG
James J. Little
1983 A* conf
AAAI
James J. Little
1979 A* conf
SIGGRAPH
Robert J. Fowler, James J. Little
src/main.rs
← Index src/main.rs rust
mod highlight;

use std::collections::BTreeMap;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::Instant;

use walkdir::WalkDir;

use highlight::highlight_code;

/// Above this threshold the file is read in full and classified
/// (text or binary) based on its complete content.
const FULL_READ_LIMIT: u64 = 4_000_000; // 4 MB
/// For files larger than the threshold above, we only read a preview
/// of this size (so we never load huge files fully into memory).
const PREVIEW_BYTES: usize = 16_384;
/// How many bytes of binary content to show in the hex dump.
const HEXDUMP_BYTES: usize = 4096;

struct FileResult {
    rel_path: PathBuf, // relative path of the original file
    out_rel: PathBuf,  // relative path of the generated .html file (inside out/)
    label: String,     // label shown in the badge (language, or "binary")
}

enum Unreadable {
    IoError(PathBuf),
}

enum Content {
    /// Successfully decoded text. `truncated` indicates whether the file
    /// is larger than what was actually read (very large files: preview only).
    Text { content: String, truncated: bool, total_size: u64 },
    /// Content that isn't valid UTF-8 text: we show a hex dump instead
    /// of trying to decode it.
    Binary { preview: Vec<u8>, total_size: u64, truncated: bool },
}

/// Reads a file safely: for small files, reads everything and classifies
/// text/binary based on the full content; for large files, reads only a
/// prefix, to avoid loading huge files into memory.
fn read_content(path: &Path, total_size: u64) -> std::io::Result<Content> {
    if total_size <= FULL_READ_LIMIT {
        let bytes = fs::read(path)?;
        match String::from_utf8(bytes) {
            Ok(s) => Ok(Content::Text {
                content: s,
                truncated: false,
                total_size,
            }),
            Err(e) => Ok(Content::Binary {
                preview: e.into_bytes(),
                total_size,
                truncated: false,
            }),
        }
    } else {
        let mut f = fs::File::open(path)?;
        let mut buf = vec![0u8; PREVIEW_BYTES];
        let n = f.read(&mut buf)?;
        buf.truncate(n);

        match std::str::from_utf8(&buf) {
            Ok(s) => Ok(Content::Text {
                content: s.to_string(),
                truncated: true,
                total_size,
            }),
            Err(e) => {
                let valid_up_to = e.valid_up_to();
                // If the error is only due to cutting a multi-byte
                // character in half (common when reading an arbitrary
                // prefix of a text file), still treat it as text.
                if valid_up_to > 0 && buf.len() - valid_up_to <= 4 {
                    let s = std::str::from_utf8(&buf[..valid_up_to]).unwrap().to_string();
                    Ok(Content::Text {
                        content: s,
                        truncated: true,
                        total_size,
                    })
                } else {
                    Ok(Content::Binary {
                        preview: buf,
                        total_size,
                        truncated: true,
                    })
                }
            }
        }
    }
}

/// Tree node used to build the index (index.html)
#[derive(Default)]
struct Node {
    dirs: BTreeMap<String, Node>,
    files: Vec<(String, String, PathBuf)>, // (display_name, label, href relative to the index)
}

impl Node {
    fn insert_file(&mut self, components: &[String], display_name: String, label: String, href: PathBuf) {
        if components.is_empty() {
            self.files.push((display_name, label, href));
        } else {
            let head = &components[0];
            let child = self.dirs.entry(head.clone()).or_default();
            child.insert_file(&components[1..], display_name, label, href);
        }
    }

    fn render(&self, out: &mut String) {
        out.push_str("<ul class=\"tree\">\n");
        for (name, node) in &self.dirs {
            out.push_str(&format!(
                "<li class=\"dir\"><span class=\"dir-name\">\u{1F4C1} {}</span>\n",
                escape_html(name)
            ));
            node.render(out);
            out.push_str("</li>\n");
        }
        for (name, label, href) in &self.files {
            out.push_str(&format!(
                "<li class=\"file\"><a href=\"{}\">\u{1F4C4} {}</a> <span class=\"badge\">{}</span></li>\n",
                href.to_string_lossy().replace('\\', "/"),
                escape_html(name),
                escape_html(label)
            ));
        }
        out.push_str("</ul>\n");
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let input_dir = args.get(1).cloned().unwrap_or_else(|| ".".to_string());
    let output_dir = args.get(2).cloned().unwrap_or_else(|| "out".to_string());
    // Extra directory names to exclude, optional, comma-separated
    // (e.g. "node_modules,.git"). Nothing is excluded by default:
    // the generator includes everything it finds, including
    // __pycache__, compiled files, binaries, etc.
    let extra_excludes: Vec<String> = args
        .get(3)
        .map(|s| s.split(',').map(|p| p.trim().to_string()).filter(|p| !p.is_empty()).collect())
        .unwrap_or_default();

    let input = PathBuf::from(&input_dir);
    let output = PathBuf::from(&output_dir);

    if !input.is_dir() {
        eprintln!("Error: '{}' is not a valid directory.", input.display());
        std::process::exit(1);
    }

    if let Err(e) = fs::create_dir_all(&output) {
        eprintln!("Error creating the output directory: {e}");
        std::process::exit(1);
    }

    // Absolute path of the output directory, so we can exclude it from the
    // scan in case it's nested inside the input directory (e.g. input=".",
    // output="out"). This is the only automatic exclusion and it can't be
    // turned off: it only prevents the program from processing the files
    // it is currently writing.
    let output_abs = fs::canonicalize(&output).unwrap_or_else(|_| output.clone());

    println!("Scanning '{}' -> '{}'", input.display(), output.display());
    if !extra_excludes.is_empty() {
        println!("Directories excluded on request: {}", extra_excludes.join(", "));
    }
    let start = Instant::now();

    let mut results: Vec<FileResult> = Vec::new();
    let mut unreadable: Vec<Unreadable> = Vec::new();
    let mut n_binary = 0usize;
    let mut n_truncated = 0usize;

    for entry in WalkDir::new(&input)
        .into_iter()
        .filter_entry(|e| {
            if e.file_type().is_dir() {
                if let Some(name) = e.file_name().to_str() {
                    if extra_excludes.iter().any(|x| x == name) {
                        return false;
                    }
                }
                if let Ok(abs) = fs::canonicalize(e.path()) {
                    if abs == output_abs {
                        return false;
                    }
                }
            }
            true
        })
    {
        let entry = match entry {
            Ok(e) => e,
            Err(_) => continue,
        };

        if !entry.file_type().is_file() {
            continue;
        }

        let path = entry.path();
        let rel_path = match path.strip_prefix(&input) {
            Ok(p) => p.to_path_buf(),
            Err(_) => path.to_path_buf(),
        };

        let meta = match fs::metadata(path) {
            Ok(m) => m,
            Err(_) => {
                unreadable.push(Unreadable::IoError(rel_path));
                continue;
            }
        };

        let content = match read_content(path, meta.len()) {
            Ok(c) => c,
            Err(_) => {
                unreadable.push(Unreadable::IoError(rel_path));
                continue;
            }
        };

        let syntax_lang = detect_language(path);

        let (body_html, label, note): (String, String, Option<String>) = match &content {
            Content::Text { content, truncated, total_size } => {
                let body = highlight_code(content, syntax_lang);
                let note = if *truncated {
                    n_truncated += 1;
                    Some(format!(
                        "Truncated preview: showing the first {} bytes out of {} total.",
                        content.len(),
                        total_size
                    ))
                } else {
                    None
                };
                (body, syntax_lang.to_string(), note)
            }
            Content::Binary { preview, total_size, truncated } => {
                n_binary += 1;
                let dump = hex_dump(preview, HEXDUMP_BYTES);
                let shown = preview.len().min(HEXDUMP_BYTES);
                let note = Some(format!(
                    "Binary file: {} bytes total. Showing the first {} bytes as hex.{}",
                    total_size,
                    shown,
                    if *truncated { " (only the first bytes of the file were read)" } else { "" }
                ));
                (escape_html(&dump), "binary".to_string(), note)
            }
        };

        let mut out_rel = rel_path.clone();
        let new_name = format!(
            "{}.html",
            out_rel.file_name().unwrap_or_default().to_string_lossy()
        );
        out_rel.set_file_name(new_name);

        let out_path = output.join(&out_rel);
        if let Some(parent) = out_path.parent() {
            let _ = fs::create_dir_all(parent);
        }

        let depth = out_rel.components().count().saturating_sub(1);
        let back_to_index = "../".repeat(depth);

        let html = render_file_html(
            &rel_path.to_string_lossy(),
            &label,
            &body_html,
            note.as_deref(),
            &back_to_index,
        );

        if fs::write(&out_path, html).is_ok() {
            results.push(FileResult {
                rel_path,
                out_rel,
                label,
            });
        }
    }

    // Build the tree for the index
    let mut root = Node::default();
    for r in &results {
        let components: Vec<String> = r
            .rel_path
            .parent()
            .map(|p| {
                p.components()
                    .map(|c| c.as_os_str().to_string_lossy().to_string())
                    .filter(|s| !s.is_empty())
                    .collect()
            })
            .unwrap_or_default();
        let display_name = r
            .rel_path
            .file_name()
            .unwrap_or_default()
            .to_string_lossy()
            .to_string();
        root.insert_file(
            &components,
            display_name,
            r.label.clone(),
            r.out_rel.clone(),
        );
    }

    let mut tree_html = String::new();
    root.render(&mut tree_html);

    let index_html = render_index_html(&tree_html, results.len(), n_binary, n_truncated);
    let _ = fs::write(output.join("index.html"), index_html);

    let elapsed = start.elapsed();
    println!(
        "Done in {:.2?}: {} files included ({} binary, {} with truncated preview), {} unreadable.",
        elapsed,
        results.len(),
        n_binary,
        n_truncated,
        unreadable.len()
    );
    for u in &unreadable {
        match u {
            Unreadable::IoError(p) => println!("  unreadable (I/O error): {}", p.display()),
        }
    }
    println!("Open {}/index.html in your browser.", output.display());
}

/// Produces a hex dump (in the style of `xxd -C`) of the first `limit` bytes.
fn hex_dump(bytes: &[u8], limit: usize) -> String {
    let show = &bytes[..bytes.len().min(limit)];
    let mut out = String::with_capacity(show.len() * 4);
    for (i, chunk) in show.chunks(16).enumerate() {
        let offset = i * 16;
        out.push_str(&format!("{:08x}  ", offset));
        for (j, b) in chunk.iter().enumerate() {
            out.push_str(&format!("{:02x} ", b));
            if j == 7 {
                out.push(' ');
            }
        }
        let missing = 16usize.saturating_sub(chunk.len());
        for _ in 0..missing {
            out.push_str("   ");
        }
        out.push_str(" |");
        for b in chunk {
            let c = *b;
            if (0x20..0x7f).contains(&c) {
                out.push(c as char);
            } else {
                out.push('.');
            }
        }
        out.push_str("|\n");
    }
    out
}

/// Determines the language (used to pick syntax-highlighting rules)
/// from the file's name/extension. This is only used when the content
/// turns out to actually be text; binary files are classified as such
/// regardless by read_content.
fn detect_language(path: &Path) -> &'static str {
    let file_name = path
        .file_name()
        .map(|s| s.to_string_lossy().to_lowercase())
        .unwrap_or_default();

    match file_name.as_str() {
        "dockerfile" => return "dockerfile",
        "makefile" | "gnumakefile" => return "makefile",
        "cmakelists.txt" => return "cmake",
        ".gitignore" | ".dockerignore" | ".npmignore" => return "plaintext",
        "cargo.toml" | "cargo.lock" => return "ini",
        _ => {}
    }

    let ext = path
        .extension()
        .map(|s| s.to_string_lossy().to_lowercase())
        .unwrap_or_default();

    match ext.as_str() {
        "py" | "pyw" | "pyi" => "python",
        "rs" => "rust",
        "js" | "mjs" | "cjs" => "javascript",
        "jsx" => "javascript",
        "ts" => "typescript",
        "tsx" => "typescript",
        "java" => "java",
        "c" => "c",
        "h" => "c",
        "hpp" | "hh" | "hxx" => "cpp",
        "cpp" | "cc" | "cxx" => "cpp",
        "cs" => "csharp",
        "go" => "go",
        "rb" => "ruby",
        "php" => "php",
        "html" | "htm" => "xml",
        "css" => "css",
        "scss" | "sass" => "scss",
        "less" => "less",
        "json" => "json",
        "xml" => "xml",
        "yaml" | "yml" => "yaml",
        "toml" | "ini" | "cfg" | "conf" => "ini",
        "sh" | "bash" | "zsh" => "bash",
        "sql" => "sql",
        "md" | "markdown" => "markdown",
        "kt" | "kts" => "kotlin",
        "swift" => "swift",
        "lua" => "lua",
        "r" => "r",
        "pl" | "pm" => "perl",
        "groovy" | "gradle" => "groovy",
        "vb" => "vbnet",
        "graphql" | "gql" => "graphql",
        "diff" | "patch" => "diff",
        "bat" | "cmd" => "dos",
        "ps1" => "powershell",
        "vue" => "xml",
        "txt" => "plaintext",
        _ => "plaintext",
    }
}

fn escape_html(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    for c in s.chars() {
        match c {
            '&' => out.push_str("&amp;"),
            '<' => out.push_str("&lt;"),
            '>' => out.push_str("&gt;"),
            '"' => out.push_str("&quot;"),
            '\'' => out.push_str("&#39;"),
            _ => out.push(c),
        }
    }
    out
}

const STYLE: &str = r#"
:root {
  --bg: #0d1117;
  --panel: #161b22;
  --text: #c9d1d9;
  --muted: #8b949e;
  --accent: #58a6ff;
  --border: #30363d;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
header {
  padding: 14px 20px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
header a { color: var(--accent); text-decoration: none; font-weight: 600; }
header a:hover { text-decoration: underline; }
.path { color: var(--muted); font-size: 0.9em; word-break: break-all; }
.note {
  padding: 10px 20px;
  background: #21262d;
  border-bottom: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.85em;
}
main { padding: 0; }
pre {
  margin: 0;
  padding: 16px 20px 40px 20px;
  overflow-x: auto;
  font-size: 13px;
  line-height: 1.5;
}
code { font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; }
.container { max-width: 1000px; margin: 30px auto; padding: 0 20px 60px; }
h1 { font-size: 1.6em; }
.meta { color: var(--muted); font-size: 0.9em; margin-bottom: 20px; }
ul.tree { list-style: none; padding-left: 18px; margin: 6px 0; }
ul.tree > li { margin: 4px 0; }
.dir-name { font-weight: 600; color: var(--text); }
.file a { color: var(--accent); text-decoration: none; }
.file a:hover { text-decoration: underline; }
.badge {
  display: inline-block;
  font-size: 0.72em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1px 8px;
  margin-left: 6px;
}
.skipped { color: var(--muted); font-size: 0.85em; margin-top: 30px; }

/* Syntax highlighting: generated in Rust, no JavaScript involved. */
.c { color: #8b949e; font-style: italic; }
.s { color: #a5d6ff; }
.n { color: #79c0ff; }
.k { color: #ff7b72; font-weight: 600; }
"#;

fn render_file_html(rel_path: &str, label: &str, body_html: &str, note: Option<&str>, back_to_index: &str) -> String {
    let title = escape_html(rel_path);
    let note_html = match note {
        Some(n) => format!("<div class=\"note\">{}</div>\n", escape_html(n)),
        None => String::new(),
    };
    format!(
        r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{title}</title>
<style>{STYLE}</style>
</head>
<body>
<header>
  <a href="{back_to_index}index.html">&larr; Index</a>
  <span class="path">{title}</span>
  <span class="badge">{label}</span>
</header>
{note_html}<main>
<pre><code>{body_html}</code></pre>
</main>
</body>
</html>
"#
    )
}

fn render_index_html(tree_html: &str, n_ok: usize, n_binary: usize, n_truncated: usize) -> String {
    let mut extra = String::new();
    if n_binary > 0 {
        extra.push_str(&format!(
            "<p class=\"skipped\">{} binary files included with a hex-dump preview.</p>",
            n_binary
        ));
    }
    if n_truncated > 0 {
        extra.push_str(&format!(
            "<p class=\"skipped\">{} very large files shown as a truncated preview only.</p>",
            n_truncated
        ));
    }

    let folder_emoji = '\u{1F4C2}';
    format!(
        r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Source index</title>
<style>{STYLE}</style>
</head>
<body>
<div class="container">
<h1>{folder_emoji} Source index</h1>
<p class="meta">{n_ok} files included in total.</p>
{tree_html}
{extra}
</div>
</body>
</html>
"#
    )
}