File: dirlist.go

package info (click to toggle)
molly-brown 0.0~git20230820.2068c3b-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 224 kB
  • sloc: sh: 67; makefile: 4
file content (132 lines) | stat: -rw-r--r-- 3,337 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

func generateDirectoryListing(URL *url.URL, path string, config UserConfig) (string, error) {
	var listing string
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return listing, err
	}
	listing = "# Directory listing\n\n"
	// Override with .mollyhead file
	header_path := filepath.Join(path, ".mollyhead")
	_, err = os.Stat(header_path)
	if err == nil {
		header, err := ioutil.ReadFile(header_path)
		if err != nil {
			return listing, err
		}
		listing = string(header)
	}
	// Do "up" link first
	if URL.Path != "/" {
		if strings.HasSuffix(URL.Path, "/") {
			URL.Path = URL.Path[:len(URL.Path)-1]
		}
		up := filepath.Dir(URL.Path)
		listing += fmt.Sprintf("=> %s %s\n", up, "..")
	}
	// Sort files by criteria first
	sort.SliceStable(files, func(i, j int) bool {
		if config.DirectoryReverse {
			i, j = j, i
		}
		if config.DirectorySort == "Name" {
			return files[i].Name() < files[j].Name()
		} else if config.DirectorySort == "Size" {
			return files[i].Size() < files[j].Size()
		} else if config.DirectorySort == "Time" {
			return files[i].ModTime().Before(files[j].ModTime())
		}
		return false // Should not happen
	})
	// Sort directories before file
	if config.DirectorySubdirsFirst {
		sort.SliceStable(files, func(i, j int) bool {
			// If i is a dir and j is a file, i < j
			if files[i].IsDir() && !files[j].IsDir() {
				return true
			} else {
				return false
			}
		})
	}
	// Format lines
	for _, file := range files {
		// Skip dotfiles
		if strings.HasPrefix(file.Name(), ".") {
			continue
		}
		// Only list world readable files
		if uint64(file.Mode().Perm())&0444 != 0444 {
			continue
		}
		// Make sure links to directories have a trailing slash,
		// to avoid needless redirects
		relativeUrl := url.PathEscape(file.Name())
		if file.IsDir() {
			relativeUrl += "/"
		}
		listing += fmt.Sprintf("=> %s %s\n", relativeUrl, generatePrettyFileLabel(file, path, config))
	}
	return listing, nil
}

func generatePrettyFileLabel(info os.FileInfo, path string, config UserConfig) string {
	var size string
	if info.IsDir() {
		size = "        "
	} else if info.Size() < 1024 {
		size = fmt.Sprintf("%4d   B", info.Size())
	} else if info.Size() < (1024 << 10) {
		size = fmt.Sprintf("%4d KiB", info.Size()>>10)
	} else if info.Size() < 1024<<20 {
		size = fmt.Sprintf("%4d MiB", info.Size()>>20)
	} else if info.Size() < 1024<<30 {
		size = fmt.Sprintf("%4d GiB", info.Size()>>30)
	} else if info.Size() < 1024<<40 {
		size = fmt.Sprintf("%4d TiB", info.Size()>>40)
	} else {
		size = "GIGANTIC"
	}

	name := info.Name()
	if config.DirectoryTitles && filepath.Ext(name) == "."+config.GeminiExt {
		name = readHeading(path, info)
	}
	if len(name) > 40 {
		name = name[:36] + "..."
	}
	if info.IsDir() {
		name += "/"
	}
	return fmt.Sprintf("%-40s    %s   %v", name, size, info.ModTime().Format("Jan _2 2006"))
}

func readHeading(path string, info os.FileInfo) string {
	filePath := filepath.Join(path, info.Name())
	file, err := os.Open(filePath)
	if err != nil {
		return info.Name()
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "# ") {
			return strings.TrimSpace(line[1:])
		}
	}
	return info.Name()
}