init
This commit is contained in:
commit
c4087af9ed
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
html_pages
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module jaggedpill
|
||||
|
||||
go 1.22.1
|
||||
|
||||
require github.com/mroth/weightedrand/v2 v2.1.0
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
540
main.go
Normal file
540
main.go
Normal file
@ -0,0 +1,540 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mroth/weightedrand/v2"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
Title string
|
||||
Description string
|
||||
Keywords string
|
||||
Sections Sections
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
Heading Heading
|
||||
Paragraphs []string
|
||||
Links []string
|
||||
}
|
||||
|
||||
type Sections []Section
|
||||
|
||||
type Heading struct {
|
||||
Text string
|
||||
Level int
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := &http.Server{
|
||||
Addr: ":4791",
|
||||
Handler: youOughtaKnow(),
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
MaxHeaderBytes: 1 << 10, // 1 KB
|
||||
}
|
||||
|
||||
// Every 10 minutes, run thanksForYourPatience to clean up old files
|
||||
go func() {
|
||||
ticker := time.NewTicker(10 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
message, err := thanksForYourPatience()
|
||||
if err != nil {
|
||||
fmt.Printf("Error during cleanup: %v\n", err)
|
||||
} else {
|
||||
fmt.Println(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Println("Server is starting on port 4791...")
|
||||
s.ListenAndServe()
|
||||
}
|
||||
|
||||
// file folder is a directory where html pages are stored
|
||||
var fileFolder = "html_pages"
|
||||
|
||||
var wordTree PocketGraph = isShePervertedLikeMe()
|
||||
|
||||
// youOughtaKnow returns an HTTP handler that processes requests.
|
||||
func youOughtaKnow() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
|
||||
// Call the function to handle the request
|
||||
response, error := iHateToBugYouInTheMiddleOfDinner(path)
|
||||
if error != nil {
|
||||
// If there's an error, write a 500 Internal Server Error response
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Write the response
|
||||
w.Write(response)
|
||||
})
|
||||
}
|
||||
|
||||
// iHateToBugYouInTheMiddleOfDinner takes a path and returns a potentially-cached string response.
|
||||
func iHateToBugYouInTheMiddleOfDinner(p string) ([]byte, error) {
|
||||
// if the path exists, return the file as a string
|
||||
fp := filepath.Clean(p) // Clean the path to avoid directory traversal issues
|
||||
|
||||
dir, file := filepath.Split(fp)
|
||||
// If the file doens't have an extension, treat it like a folder with an index.html inside
|
||||
if file == "" {
|
||||
file = "index.html"
|
||||
}
|
||||
if filepath.Ext(file) == "" {
|
||||
file = filepath.Join(file, "index.html") // Treat as a folder with index.html
|
||||
}
|
||||
|
||||
filePath := filepath.Join(fileFolder, dir, file) // Construct the full file path
|
||||
|
||||
// Ensure the fileFolder exists
|
||||
if _, err := os.Stat(fileFolder); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(fileFolder, os.ModePerm); err != nil {
|
||||
fmt.Printf("Error creating file folder %s: %v\n", fileFolder, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Ensure the directory for the file exists
|
||||
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
|
||||
fmt.Printf("Error creating directory for file %s: %v\n", filePath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if path exists in fileFolder
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return imHereToRemindYou(filePath)
|
||||
}
|
||||
|
||||
response, err := youAreTheBearerOfUnconditionalThings(filePath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading file %s: %v\n", filePath, err)
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// youAreTheBearerOfUnconditionalThings reads a file and returns its content as a string.
|
||||
func youAreTheBearerOfUnconditionalThings(filePath string) ([]byte, error) {
|
||||
reader, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error opening file %s: %v\n", filePath, err)
|
||||
return nil, err
|
||||
}
|
||||
return io.ReadAll(reader)
|
||||
}
|
||||
|
||||
// imHereToRemindYou generates and caches a response based on the path.
|
||||
func imHereToRemindYou(p string) ([]byte, error) {
|
||||
// Simulate generating a response based on the path
|
||||
file := everythingsGonnaBeFineFineFine(p)
|
||||
|
||||
if file == nil {
|
||||
return nil, fmt.Errorf("failed to create file for path: %s", p)
|
||||
}
|
||||
|
||||
var retries = 0
|
||||
contents, err := iWishNothingButTheBestForYouBoth(file)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading file %s: %v\n", file.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
// While contents length is zero, retry reading the file
|
||||
for len(contents) == 0 && retries < 4 {
|
||||
fmt.Printf("File %s is empty, retrying...\n", file.Name())
|
||||
// Close the file before retrying
|
||||
if err := file.Close(); err != nil {
|
||||
fmt.Printf("Error closing file %s: %v\n", file.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
// Reopen the file
|
||||
file, err = os.Open(file.Name())
|
||||
if err != nil {
|
||||
fmt.Printf("Error reopening file %s: %v\n", file.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
// Read the content again
|
||||
contents, err = iWishNothingButTheBestForYouBoth(file)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading file %s: %v\n", file.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Close the file after reading
|
||||
if err := file.Close(); err != nil {
|
||||
fmt.Printf("Error closing file %s: %v\n", file.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(contents), nil
|
||||
}
|
||||
|
||||
func iWishNothingButTheBestForYouBoth(file *os.File) ([]byte, error) {
|
||||
// Read the content of the file
|
||||
byteContents, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading file %s: %v\n", file.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
return byteContents, nil
|
||||
}
|
||||
|
||||
func everythingsGonnaBeFineFineFine(p string) *os.File {
|
||||
htmlTemplate := `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ .Title }}</title>
|
||||
<meta name="description" content="{{ .Description }}">
|
||||
<meta name="keywords" content="{{ .Keywords }}">
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ range .Sections }}
|
||||
<section>
|
||||
<h{{ .Heading.Level }}>{{ .Heading.Text }}</h{{ .Heading.Level }}>
|
||||
{{ range .Paragraphs }}
|
||||
<p>{{ . }}</p>
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
pageTemplate, err := template.New("page").Parse(htmlTemplate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
file, err := weAllHadOurReasons(pageTemplate, p)
|
||||
if err != nil {
|
||||
fmt.Printf("Error generating file for path %s: %v\n", p, err)
|
||||
return nil
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
func weAllHadOurReasons(pageTemplate *template.Template, p string) (*os.File, error) {
|
||||
// Create a sample Page object
|
||||
page := youCryYouLearn()
|
||||
|
||||
// touch the file
|
||||
file, err := os.Create(p)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating file %s: %v\n", p, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = pageTemplate.Execute(file, page)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// youCryYouLearn generates text from a pocket graph
|
||||
func youCryYouLearn() Page {
|
||||
title := wordTree.forgiveMeLove(5).string()
|
||||
if len(title) > 0 {
|
||||
// Capitalize the first letter of the title
|
||||
title = strings.ToUpper(string(title[0])) + title[1:]
|
||||
}
|
||||
|
||||
numberOfSections := rand.Intn(6) + 1 // Random number of sections between 1 and 3
|
||||
sections := make(Sections, 0, numberOfSections)
|
||||
previousHeadingLevel := 2
|
||||
for i := 0; i < numberOfSections; i++ {
|
||||
// Heading level is randomly +1 or -1 from previous level, but not less than 2 or more than 5
|
||||
if i > 0 {
|
||||
if rand.Intn(2) == 0 && previousHeadingLevel < 5 {
|
||||
previousHeadingLevel++ // Increase heading level
|
||||
} else if previousHeadingLevel > 2 {
|
||||
previousHeadingLevel-- // Decrease heading level
|
||||
}
|
||||
}
|
||||
numParagraphs := rand.Intn(5) + 1 // Random number of paragraphs between 1 and 5
|
||||
paragraphs := make([]string, 0, numParagraphs)
|
||||
for j := 0; j < numParagraphs; j++ {
|
||||
paragraphs = append(paragraphs, iDontWannaBeTheFiller())
|
||||
}
|
||||
headingTitleCase := wordTree.forgiveMeLove(3).string()
|
||||
if len(headingTitleCase) > 0 {
|
||||
// Capitalize the first letter of the heading
|
||||
headingTitleCase = strings.ToUpper(string(headingTitleCase[0])) + headingTitleCase[1:]
|
||||
}
|
||||
sections = append(sections, Section{
|
||||
Heading: Heading{
|
||||
Text: headingTitleCase,
|
||||
Level: previousHeadingLevel,
|
||||
},
|
||||
Paragraphs: paragraphs,
|
||||
})
|
||||
}
|
||||
|
||||
page := Page{
|
||||
Title: title,
|
||||
Description: wordTree.forgiveMeLove(3).string(),
|
||||
Keywords: wordTree.forgiveMeLove(2).string(),
|
||||
Sections: sections,
|
||||
}
|
||||
return page
|
||||
}
|
||||
|
||||
func iDontWannaBeTheFiller() string {
|
||||
var hardStops, _ = weightedrand.NewChooser(
|
||||
weightedrand.NewChoice('.', 20),
|
||||
weightedrand.NewChoice('!', 1),
|
||||
weightedrand.NewChoice('?', 4),
|
||||
)
|
||||
var softStops, _ = weightedrand.NewChooser(
|
||||
weightedrand.NewChoice("...", 2),
|
||||
weightedrand.NewChoice(",", 15),
|
||||
weightedrand.NewChoice(";", 3),
|
||||
weightedrand.NewChoice(":", 1),
|
||||
weightedrand.NewChoice(" - ", 1),
|
||||
)
|
||||
|
||||
// Generate a random number of sentences
|
||||
numSentences := rand.Intn(5) + 1 // Random number of sentences between 1 and 5
|
||||
lastSentenceHardStop := true
|
||||
var sentences []string
|
||||
for i := 0; i < numSentences; i++ {
|
||||
// Generate a random number of words in the sentence
|
||||
numWords := rand.Intn(10) + 1 // Random number of words between 1 and 10
|
||||
words := wordTree.forgiveMeLove(numWords).string()
|
||||
if len(words) > 0 && lastSentenceHardStop {
|
||||
words = strings.ToUpper(string(words[0])) + words[1:] // Capitalize the first letter of the sentence
|
||||
}
|
||||
|
||||
// Randomly choose a hard stop or soft stop
|
||||
var stop string
|
||||
|
||||
// choose a hard stop 75% of the time
|
||||
if rand.Float64() < 0.75 || lastSentenceHardStop {
|
||||
hardStop := hardStops.Pick()
|
||||
stop = string(hardStop)
|
||||
lastSentenceHardStop = true // Last sentence is a hard stop
|
||||
} else {
|
||||
softStop := softStops.Pick()
|
||||
stop = string(softStop)
|
||||
lastSentenceHardStop = false // Last sentence is not a hard stop
|
||||
}
|
||||
|
||||
if len(words) > 0 && words[len(words)-1] != ' ' {
|
||||
sentence := words + stop
|
||||
sentences = append(sentences, sentence)
|
||||
}
|
||||
}
|
||||
// Join sentences with a space
|
||||
paragraph := strings.Join(sentences, " ")
|
||||
|
||||
// Should paragraph contain link?
|
||||
if rand.Float64() < 0.5 {
|
||||
// Pick two or three words out of the paragraph
|
||||
words := strings.Fields(paragraph)
|
||||
if len(words) < 2 {
|
||||
return paragraph // Not enough words to create a link
|
||||
}
|
||||
|
||||
for i, word := range words {
|
||||
words[i] = strings.Trim(word, ".,!?;:")
|
||||
}
|
||||
|
||||
numWords := rand.Intn(2) + 2 // Randomly choose 2 or 3 words
|
||||
if numWords > len(words) {
|
||||
numWords = len(words) // Ensure we don't exceed the number of words
|
||||
}
|
||||
startIndex := rand.Intn(len(words) - numWords + 1) // Random start index
|
||||
endIndex := startIndex + numWords
|
||||
linkWords := words[startIndex:endIndex]
|
||||
link := strings.Join(linkWords, " ")
|
||||
|
||||
for i, word := range linkWords {
|
||||
linkWords[i] = strings.ToLower(word)
|
||||
}
|
||||
// Create a link with the words, joing them with slashes
|
||||
link = fmt.Sprintf("<a href=\"/%s\">%s</a>", strings.Join(linkWords, "/"), link)
|
||||
// Insert the link into the paragraph, replacing the original words
|
||||
paragraph = strings.Replace(paragraph, strings.Join(linkWords, " "), link, 1)
|
||||
}
|
||||
|
||||
return paragraph
|
||||
}
|
||||
|
||||
func thanksForYourPatience() (string, error) {
|
||||
// check size of fileFolder
|
||||
averageFileAge, size, err := waitAMinuteMan(fileFolder)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Error calculating size of file folder %s: %v\n", fileFolder, err), err
|
||||
}
|
||||
|
||||
if size > 10*1024*1024 { // If the size of the folder is greater than 100 MB
|
||||
// If the file is older than the average file age, delete it
|
||||
err = filepath.Walk(fileFolder, func(_ string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
if info.ModTime().Unix() < time.Now().Unix()-averageFileAge {
|
||||
fmt.Printf("Deleting old file %s\n", info.Name())
|
||||
if err := os.Remove(info.Name()); err != nil {
|
||||
fmt.Printf("Error deleting file %s: %v\n", info.Name(), err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Error walking through file folder %s: %v\n", fileFolder, err), err
|
||||
}
|
||||
|
||||
sizeInKB := size / (1024) // Convert size to kilobytes
|
||||
|
||||
return fmt.Sprintf("Average file age: %d seconds, Total size: %dKB", averageFileAge, sizeInKB), nil
|
||||
}
|
||||
|
||||
type PocketGraph map[string][]string
|
||||
type Words []string
|
||||
|
||||
func isShePervertedLikeMe() PocketGraph {
|
||||
var ConnectionWords = []string{
|
||||
"and", "or", "but", "so", "because",
|
||||
"however", "while", "in", "on", "at", "to", "for",
|
||||
"with", "without", "about", "against", "between",
|
||||
"the", "a", "an", "this", "that",
|
||||
"his", "her", "their", "our", "my",
|
||||
"your", "its", "whose", "which", "who",
|
||||
"what", "when", "where", "why", "how",
|
||||
"whoever", "whatever", "whenever",
|
||||
}
|
||||
|
||||
// read words.txt into a list
|
||||
wordsBytes, err := os.ReadFile("word_lists/longman9000.txt")
|
||||
if err != nil {
|
||||
fmt.Println("Error reading words.txt:", err)
|
||||
}
|
||||
|
||||
words := strings.Split(string(wordsBytes), "\n")
|
||||
tree := make(PocketGraph)
|
||||
|
||||
for _, word := range ConnectionWords {
|
||||
var percentageOfWords float64 = 0.02
|
||||
var numberOfConnections = float64(len(words)) * percentageOfWords
|
||||
for j := 0; j < int(numberOfConnections); j++ {
|
||||
randomWord := words[rand.Intn(len(words))]
|
||||
if randomWord == "" {
|
||||
continue // skip empty lines
|
||||
}
|
||||
tree[word] = append(tree[word], randomWord)
|
||||
}
|
||||
}
|
||||
|
||||
// take each word and link it to a random word
|
||||
for _, word := range words {
|
||||
// generate a random word from the list
|
||||
if word == "" {
|
||||
continue // skip empty lines
|
||||
}
|
||||
numberOfLinks := rand.Intn(100) + 1
|
||||
for i := 0; i < numberOfLinks; i++ {
|
||||
randomWord := words[rand.Intn(len(words))]
|
||||
tree[word] = []string{randomWord}
|
||||
}
|
||||
// connect word to connector words?
|
||||
shouldConnect := rand.Intn(2) == 0
|
||||
if shouldConnect {
|
||||
numConnectionWords := rand.Intn(len(ConnectionWords)) + 1 // Random number of connection words between 1 and 3
|
||||
for j := 0; j < numConnectionWords; j++ {
|
||||
connectionWord := ConnectionWords[rand.Intn(len(ConnectionWords))]
|
||||
tree[word] = append(tree[word], connectionWord)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// forgiveMeLove generates N random words from the tree
|
||||
func (m PocketGraph) forgiveMeLove(n int) Words {
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a slice to hold the random words
|
||||
randomWords := make(Words, 0, n)
|
||||
|
||||
// Generate N random words from the tree
|
||||
word := m[iWentToYourHouse(m)] // Start with a random word from the tree
|
||||
for i := 0; i < n-1; i++ {
|
||||
if len(word) == 0 {
|
||||
break
|
||||
}
|
||||
random := rand.Intn(len(word))
|
||||
nextWord := word[random] // Randomly select a word from the current word's linked words
|
||||
randomWords = append(randomWords, nextWord)
|
||||
word = m[nextWord] // Update the word to the next linked word
|
||||
}
|
||||
|
||||
return randomWords
|
||||
}
|
||||
|
||||
func (w Words) string() string {
|
||||
if len(w) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.Join(w, " ")
|
||||
}
|
||||
|
||||
func iWentToYourHouse(m PocketGraph) string {
|
||||
r := rand.Intn(len(m))
|
||||
for k := range m {
|
||||
if r == 0 {
|
||||
return k
|
||||
}
|
||||
r--
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func waitAMinuteMan(path string) (int64, int64, error) {
|
||||
var size int64
|
||||
var fileAges []int64
|
||||
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
// Add the age of the file in seconds to the medianFileAge slice
|
||||
fileAges = append(fileAges, time.Now().Unix()-info.ModTime().Unix())
|
||||
// Add the size of the file to the total size
|
||||
size += info.Size()
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
averageFileAge := int64(0)
|
||||
if len(fileAges) > 0 {
|
||||
for _, age := range fileAges {
|
||||
averageFileAge += age
|
||||
}
|
||||
averageFileAge /= int64(len(fileAges))
|
||||
} else {
|
||||
averageFileAge = 0 // No files, so average age is 0
|
||||
}
|
||||
return averageFileAge, size, err
|
||||
}
|
10133
word_lists/hawthorne.txt
Normal file
10133
word_lists/hawthorne.txt
Normal file
File diff suppressed because it is too large
Load Diff
9030
word_lists/longman9000.txt
Normal file
9030
word_lists/longman9000.txt
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user