feat: add buildmanual target, faker demo, subprocess examples, and contributing sidebar to manual

Add Makefile buildmanual target for manual generation and include faker_demo.wren, subprocess_async_demo.wren, subprocess_concurrent_demo.wren, and subprocess_fiber_demo.wren example scripts. Update manual sidebar across api pages to link new web-server tutorial and contributing section with module architecture, pure-wren/c-backed modules, foreign classes, async patterns, testing, and documentation pages.
This commit is contained in:
2026-01-25 18:02:02 +00:00
parent 819bbd5091
commit 5c2269f6a7
92 changed files with 10955 additions and 621 deletions
+25
View File
@@ -12,6 +12,7 @@
#include "datetime.wren.inc"
#include "dns.wren.inc"
#include "env.wren.inc"
#include "faker.wren.inc"
#include "fswatch.wren.inc"
#include "html.wren.inc"
#include "http.wren.inc"
@@ -179,6 +180,16 @@ extern void sqliteClose(WrenVM* vm);
extern void sqliteLastInsertId(WrenVM* vm);
extern void sqliteChanges(WrenVM* vm);
extern void subprocessRun(WrenVM* vm);
extern void popenAllocate(WrenVM* vm);
extern void popenFinalize(void* data);
extern void popenPid(WrenVM* vm);
extern void popenIsRunning(WrenVM* vm);
extern void popenWait(WrenVM* vm);
extern void popenKill(WrenVM* vm);
extern void popenReadStream(WrenVM* vm);
extern void popenWriteStream(WrenVM* vm);
extern void popenCloseStream(WrenVM* vm);
extern void popenIsStreamOpen(WrenVM* vm);
extern void regexAllocate(WrenVM* vm);
extern void regexFinalize(void* data);
extern void regexTest(WrenVM* vm);
@@ -351,6 +362,8 @@ static ModuleRegistry modules[] =
STATIC_METHOD("all", envAll)
END_CLASS
END_MODULE
MODULE(faker)
END_MODULE
MODULE(fswatch)
CLASS(FileWatcher)
ALLOCATE(fswatchAllocate)
@@ -576,6 +589,18 @@ static ModuleRegistry modules[] =
CLASS(Subprocess)
STATIC_METHOD("run_(_,_)", subprocessRun)
END_CLASS
CLASS(Popen)
ALLOCATE(popenAllocate)
FINALIZE(popenFinalize)
METHOD("pid", popenPid)
METHOD("isRunning", popenIsRunning)
METHOD("wait_(_)", popenWait)
METHOD("kill_(_)", popenKill)
METHOD("readStream_(_,_)", popenReadStream)
METHOD("writeStream_(_,_,_)", popenWriteStream)
METHOD("closeStream_(_)", popenCloseStream)
METHOD("isStreamOpen_(_)", popenIsStreamOpen)
END_CLASS
END_MODULE
MODULE(tempfile)
END_MODULE
+890
View File
@@ -0,0 +1,890 @@
// retoor <retoor@molodetz.nl>
import "crypto" for Crypto, Hash
import "uuid" for Uuid
import "datetime" for DateTime, Duration
import "strutil" for Str
class FakerRandom {
static seed_ { __seed }
static state_ { __state }
static seed(value) {
__seed = value
__state = value
}
static reset() {
__seed = null
__state = null
}
static next(min, max) {
if (__seed == null) {
return Crypto.randomInt(min, max + 1)
}
if (__state == null) __state = __seed
__state = ((__state * 1103515245) + 12345) % 2147483648
var range = max - min + 1
return min + (__state % range)
}
static nextFloat() {
if (__seed == null) {
var bytes = Crypto.randomBytes(4)
var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]
if (value < 0) value = -value
return value / 2147483648
}
if (__state == null) __state = __seed
__state = ((__state * 1103515245) + 12345) % 2147483648
return __state / 2147483648
}
}
class FakerData {
static firstNamesMale {
return [
"James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Charles",
"Christopher", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven", "Paul", "Andrew", "Joshua",
"Kenneth", "Kevin", "Brian", "George", "Timothy", "Ronald", "Edward", "Jason", "Jeffrey", "Ryan",
"Jacob", "Gary", "Nicholas", "Eric", "Jonathan", "Stephen", "Larry", "Justin", "Scott", "Brandon",
"Benjamin", "Samuel", "Raymond", "Gregory", "Frank", "Alexander", "Patrick", "Jack", "Dennis", "Jerry",
"Tyler", "Aaron", "Jose", "Adam", "Nathan", "Henry", "Douglas", "Zachary", "Peter", "Kyle",
"Noah", "Ethan", "Jeremy", "Walter", "Christian", "Keith", "Roger", "Terry", "Austin", "Sean",
"Gerald", "Carl", "Harold", "Dylan", "Arthur", "Lawrence", "Jordan", "Jesse", "Bryan", "Billy",
"Bruce", "Gabriel", "Joe", "Logan", "Albert", "Willie", "Alan", "Vincent", "Eugene", "Russell",
"Elijah", "Randy", "Philip", "Harry", "Howard", "Roy", "Louis", "Russell", "Bobby", "Johnny"
]
}
static firstNamesFemale {
return [
"Mary", "Patricia", "Jennifer", "Linda", "Barbara", "Elizabeth", "Susan", "Jessica", "Sarah", "Karen",
"Lisa", "Nancy", "Betty", "Margaret", "Sandra", "Ashley", "Kimberly", "Emily", "Donna", "Michelle",
"Dorothy", "Carol", "Amanda", "Melissa", "Deborah", "Stephanie", "Rebecca", "Sharon", "Laura", "Cynthia",
"Kathleen", "Amy", "Angela", "Shirley", "Anna", "Brenda", "Pamela", "Emma", "Nicole", "Helen",
"Samantha", "Katherine", "Christine", "Debra", "Rachel", "Carolyn", "Janet", "Catherine", "Maria", "Heather",
"Diane", "Ruth", "Julie", "Olivia", "Joyce", "Virginia", "Victoria", "Kelly", "Lauren", "Christina",
"Joan", "Evelyn", "Judith", "Megan", "Andrea", "Cheryl", "Hannah", "Jacqueline", "Martha", "Gloria",
"Teresa", "Ann", "Sara", "Madison", "Frances", "Kathryn", "Janice", "Jean", "Abigail", "Alice",
"Judy", "Sophia", "Grace", "Denise", "Amber", "Doris", "Marilyn", "Danielle", "Beverly", "Isabella",
"Theresa", "Diana", "Natalie", "Brittany", "Charlotte", "Marie", "Kayla", "Alexis", "Lori", "Jane"
]
}
static firstNames {
return firstNamesMale + firstNamesFemale
}
static lastNames {
return [
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez",
"Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin",
"Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson",
"Walker", "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores",
"Green", "Adams", "Nelson", "Baker", "Hall", "Rivera", "Campbell", "Mitchell", "Carter", "Roberts",
"Gomez", "Phillips", "Evans", "Turner", "Diaz", "Parker", "Cruz", "Edwards", "Collins", "Reyes",
"Stewart", "Morris", "Morales", "Murphy", "Cook", "Rogers", "Gutierrez", "Ortiz", "Morgan", "Cooper",
"Peterson", "Bailey", "Reed", "Kelly", "Howard", "Ramos", "Kim", "Cox", "Ward", "Richardson",
"Watson", "Brooks", "Chavez", "Wood", "James", "Bennett", "Gray", "Mendoza", "Ruiz", "Hughes",
"Price", "Alvarez", "Castillo", "Sanders", "Patel", "Myers", "Long", "Ross", "Foster", "Jimenez",
"Powell", "Jenkins", "Perry", "Russell", "Sullivan", "Bell", "Coleman", "Butler", "Henderson", "Barnes",
"Gonzales", "Fisher", "Vasquez", "Simmons", "Graham", "Patterson", "Jordan", "Reynolds", "Hamilton", "Graham",
"Wallace", "Cole", "West", "Stone", "Holmes", "Meyer", "Boyd", "Mills", "Warren", "Fox",
"Rose", "Rice", "Moreno", "Schmidt", "Patel", "Ferguson", "Nichols", "Herrera", "Medina", "Ryan",
"Fernandez", "Weaver", "Daniels", "Stephens", "Knight", "Hudson", "Spencer", "Elliott", "Bishop", "Craig",
"Hunter", "Hart", "Armstrong", "Carpenter", "Harvey", "Hawkins", "Harrison", "Fields", "Webb", "Tucker",
"Mason", "Porter", "Burns", "Gibson", "Ellis", "Gordon", "Kennedy", "Willis", "Chapman", "Palmer",
"Arnold", "Lane", "Dean", "Matthews", "Wagner", "Austin", "Murray", "Stanley", "Fowler", "Grant",
"Kelley", "Black", "Lynch", "Barnes", "Dunn", "Shaw", "Olson", "Simpson", "George", "Marshall",
"Watkins", "Owens", "Hunt", "Hicks", "Freeman", "Pierce", "Snyder", "Franklin", "Soto", "Newman"
]
}
static emailDomains {
return [
"gmail.com", "yahoo.com", "hotmail.com", "outlook.com", "icloud.com",
"mail.com", "protonmail.com", "aol.com", "zoho.com", "yandex.com",
"gmx.com", "fastmail.com", "tutanota.com", "inbox.com", "live.com",
"example.com", "example.org", "example.net", "test.com", "demo.com",
"company.com", "corp.com", "business.com", "enterprise.com", "work.com",
"office.com", "professional.com", "service.com", "support.com", "info.com"
]
}
static loremWords {
return [
"lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "sed", "do",
"eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua", "enim",
"ad", "minim", "veniam", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "aliquip",
"ex", "ea", "commodo", "consequat", "duis", "aute", "irure", "in", "reprehenderit", "voluptate",
"velit", "esse", "cillum", "fugiat", "nulla", "pariatur", "excepteur", "sint", "occaecat", "cupidatat",
"non", "proident", "sunt", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id",
"est", "laborum", "at", "vero", "eos", "accusamus", "iusto", "odio", "dignissimos", "ducimus",
"blanditiis", "praesentium", "voluptatum", "deleniti", "atque", "corrupti", "quos", "quas", "molestias",
"excepturi", "occaecati", "cupiditate", "provident", "similique", "mollitia", "animi", "quasi", "architecto",
"beatae", "vitae", "dicta", "explicabo", "nemo", "ipsam", "quia", "voluptas", "aspernatur", "aut",
"odit", "fugit", "consequuntur", "magni", "dolores", "ratione", "sequi", "nesciunt", "neque", "porro",
"quisquam", "dolorem", "adipisci", "numquam", "eius", "modi", "tempora", "magnam", "quaerat", "rem",
"aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "quasi", "perspiciatis",
"unde", "omnis", "iste", "natus", "error", "accusantium", "doloremque", "laudantium", "totam", "recusandae",
"saepe", "eveniet", "voluptatem", "alias", "autem", "minima", "eligendi", "optio", "cumque", "nihil",
"impedit", "quo", "minus", "quod", "maxime", "placeat", "facere", "possimus", "assumenda", "repellendus",
"temporibus", "debitis", "rerum", "hic", "tenetur", "sapiente", "delectus", "reiciendis", "perferendis",
"doloribus", "asperiores", "repellat", "nam", "libero", "soluta", "nobis", "cum", "corporis", "suscipit",
"officiis", "laboriosam", "harum", "necessitatibus", "praesentium", "voluptates", "repudiandae", "fuga", "sint"
]
}
static cities {
return [
"New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "San Diego",
"Dallas", "San Jose", "Austin", "Jacksonville", "Fort Worth", "Columbus", "Charlotte", "San Francisco",
"Indianapolis", "Seattle", "Denver", "Washington", "Boston", "El Paso", "Nashville", "Detroit", "Portland",
"Memphis", "Oklahoma City", "Las Vegas", "Louisville", "Baltimore", "Milwaukee", "Albuquerque", "Tucson",
"Fresno", "Sacramento", "Kansas City", "Mesa", "Atlanta", "Omaha", "Colorado Springs", "Raleigh", "Miami",
"Long Beach", "Virginia Beach", "Oakland", "Minneapolis", "Tulsa", "Tampa", "Arlington", "New Orleans",
"Wichita", "Cleveland", "Bakersfield", "Aurora", "Anaheim", "Honolulu", "Santa Ana", "Riverside", "Corpus Christi",
"Lexington", "Henderson", "Stockton", "Saint Paul", "Cincinnati", "St. Louis", "Pittsburgh", "Greensboro",
"Lincoln", "Anchorage", "Plano", "Orlando", "Irvine", "Newark", "Durham", "Chula Vista", "Toledo", "Fort Wayne",
"St. Petersburg", "Laredo", "Jersey City", "Chandler", "Madison", "Lubbock", "Scottsdale", "Reno", "Buffalo",
"Gilbert", "Glendale", "North Las Vegas", "Winston-Salem", "Chesapeake", "Norfolk", "Fremont", "Garland",
"Irving", "Hialeah", "Richmond", "Boise", "Spokane", "Baton Rouge", "Tacoma", "San Bernardino", "Modesto",
"Fontana", "Des Moines", "Moreno Valley", "Santa Clarita", "Fayetteville", "Birmingham", "Oxnard", "Rochester",
"Port St. Lucie", "Grand Rapids", "Huntsville", "Salt Lake City", "Frisco", "Yonkers", "Amarillo", "Glendale",
"Huntington Beach", "McKinney", "Montgomery", "Augusta", "Aurora", "Akron", "Little Rock", "Tempe", "Columbus",
"Overland Park", "Grand Prairie", "Tallahassee", "Cape Coral", "Mobile", "Knoxville", "Shreveport", "Worcester",
"Ontario", "Vancouver", "Sioux Falls", "Chattanooga", "Brownsville", "Fort Lauderdale", "Providence", "Newport News",
"Rancho Cucamonga", "Santa Rosa", "Peoria", "Oceanside", "Elk Grove", "Salem", "Pembroke Pines", "Eugene", "Garden Grove",
"Cary", "Fort Collins", "Corona", "Springfield", "Jackson", "Alexandria", "Hayward", "Lancaster", "Lakewood",
"Clarksville", "Palmdale", "Salinas", "Springfield", "Hollywood", "Pasadena", "Sunnyvale", "Macon", "Kansas City",
"Pomona", "Escondido", "Killeen", "Naperville", "Joliet", "Bellevue", "Rockford", "Savannah", "Paterson", "Torrance",
"Bridgeport", "McAllen", "Mesquite", "Syracuse", "Midland", "Pasadena", "Murfreesboro", "Miramar", "Dayton", "Fullerton"
]
}
static states {
return [
["Alabama", "AL"], ["Alaska", "AK"], ["Arizona", "AZ"], ["Arkansas", "AR"], ["California", "CA"],
["Colorado", "CO"], ["Connecticut", "CT"], ["Delaware", "DE"], ["Florida", "FL"], ["Georgia", "GA"],
["Hawaii", "HI"], ["Idaho", "ID"], ["Illinois", "IL"], ["Indiana", "IN"], ["Iowa", "IA"],
["Kansas", "KS"], ["Kentucky", "KY"], ["Louisiana", "LA"], ["Maine", "ME"], ["Maryland", "MD"],
["Massachusetts", "MA"], ["Michigan", "MI"], ["Minnesota", "MN"], ["Mississippi", "MS"], ["Missouri", "MO"],
["Montana", "MT"], ["Nebraska", "NE"], ["Nevada", "NV"], ["New Hampshire", "NH"], ["New Jersey", "NJ"],
["New Mexico", "NM"], ["New York", "NY"], ["North Carolina", "NC"], ["North Dakota", "ND"], ["Ohio", "OH"],
["Oklahoma", "OK"], ["Oregon", "OR"], ["Pennsylvania", "PA"], ["Rhode Island", "RI"], ["South Carolina", "SC"],
["South Dakota", "SD"], ["Tennessee", "TN"], ["Texas", "TX"], ["Utah", "UT"], ["Vermont", "VT"],
["Virginia", "VA"], ["Washington", "WA"], ["West Virginia", "WV"], ["Wisconsin", "WI"], ["Wyoming", "WY"]
]
}
static countries {
return [
["United States", "US"], ["United Kingdom", "GB"], ["Canada", "CA"], ["Australia", "AU"], ["Germany", "DE"],
["France", "FR"], ["Italy", "IT"], ["Spain", "ES"], ["Netherlands", "NL"], ["Belgium", "BE"],
["Switzerland", "CH"], ["Austria", "AT"], ["Sweden", "SE"], ["Norway", "NO"], ["Denmark", "DK"],
["Finland", "FI"], ["Ireland", "IE"], ["Portugal", "PT"], ["Greece", "GR"], ["Poland", "PL"],
["Czech Republic", "CZ"], ["Hungary", "HU"], ["Romania", "RO"], ["Bulgaria", "BG"], ["Croatia", "HR"],
["Slovakia", "SK"], ["Slovenia", "SI"], ["Estonia", "EE"], ["Latvia", "LV"], ["Lithuania", "LT"],
["Japan", "JP"], ["China", "CN"], ["South Korea", "KR"], ["India", "IN"], ["Singapore", "SG"],
["Malaysia", "MY"], ["Thailand", "TH"], ["Indonesia", "ID"], ["Philippines", "PH"], ["Vietnam", "VN"],
["Brazil", "BR"], ["Mexico", "MX"], ["Argentina", "AR"], ["Chile", "CL"], ["Colombia", "CO"],
["Peru", "PE"], ["Venezuela", "VE"], ["Ecuador", "EC"], ["Uruguay", "UY"], ["Paraguay", "PY"],
["South Africa", "ZA"], ["Egypt", "EG"], ["Nigeria", "NG"], ["Kenya", "KE"], ["Morocco", "MA"],
["Israel", "IL"], ["Turkey", "TR"], ["Saudi Arabia", "SA"], ["United Arab Emirates", "AE"], ["Russia", "RU"],
["Ukraine", "UA"], ["New Zealand", "NZ"], ["Iceland", "IS"], ["Luxembourg", "LU"], ["Malta", "MT"]
]
}
static streetSuffixes {
return [
"Street", "Avenue", "Boulevard", "Drive", "Lane", "Road", "Way", "Place", "Court", "Circle",
"Trail", "Parkway", "Terrace", "Plaza", "Commons", "Alley", "Path", "Run", "View", "Ridge"
]
}
static streetNames {
return [
"Main", "Oak", "Maple", "Cedar", "Pine", "Elm", "Washington", "Lake", "Hill", "Park",
"River", "Spring", "Valley", "Forest", "Sunset", "Highland", "Meadow", "Church", "Mill", "Union",
"Market", "Water", "Bridge", "School", "North", "South", "East", "West", "Center", "High",
"Front", "Broad", "College", "Prospect", "Lincoln", "Franklin", "Jefferson", "Adams", "Madison", "Jackson",
"Monroe", "Wilson", "Harrison", "Cleveland", "Grant", "Lee", "King", "Queen", "Duke", "Prince",
"Cherry", "Walnut", "Chestnut", "Birch", "Willow", "Poplar", "Spruce", "Hickory", "Ash", "Beech",
"Summit", "Ridge", "Crest", "Mount", "Valley", "Canyon", "Creek", "Brook", "Falls", "Grove",
"Orchard", "Garden", "Field", "Woods", "Acres", "Estate", "Manor", "Villa", "Terrace", "Glen",
"Bay", "Harbor", "Shore", "Beach", "Ocean", "Sea", "Coral", "Palm", "Sunset", "Sunrise",
"Colonial", "Liberty", "Independence", "Victory", "Heritage", "Pioneer", "Frontier", "Commerce", "Industrial", "Technology"
]
}
static companyPrefixes {
return [
"Global", "United", "International", "National", "American", "Pacific", "Atlantic", "Northern", "Southern",
"Western", "Eastern", "Central", "Premier", "Prime", "Elite", "Apex", "Summit", "Peak", "Pinnacle",
"First", "Advanced", "Modern", "Future", "Next", "New", "Innovative", "Dynamic", "Strategic", "Smart",
"Digital", "Tech", "Cyber", "Data", "Cloud", "Net", "Web", "Quantum", "Fusion", "Synergy"
]
}
static companySuffixes {
return [
"Inc.", "LLC", "Corp.", "Ltd.", "Co.", "Group", "Holdings", "Industries", "Enterprises", "Solutions",
"Services", "Systems", "Technologies", "Partners", "Associates", "Consulting", "International", "Global", "Worldwide"
]
}
static companyNouns {
return [
"Tech", "Systems", "Solutions", "Data", "Software", "Digital", "Media", "Networks", "Communications", "Industries",
"Dynamics", "Logic", "Works", "Labs", "Innovations", "Ventures", "Capital", "Resources", "Management", "Consulting"
]
}
static jobTitles {
return [
"Software Engineer", "Product Manager", "Data Scientist", "Marketing Manager", "Sales Representative",
"Financial Analyst", "Human Resources Manager", "Operations Manager", "Project Manager", "Business Analyst",
"Accountant", "Graphic Designer", "Content Writer", "Customer Service Representative", "Administrative Assistant",
"Executive Assistant", "Office Manager", "Quality Assurance Engineer", "DevOps Engineer", "System Administrator",
"Network Engineer", "Database Administrator", "Security Analyst", "UX Designer", "UI Developer",
"Full Stack Developer", "Frontend Developer", "Backend Developer", "Mobile Developer", "Cloud Architect",
"Solutions Architect", "Technical Lead", "Engineering Manager", "Director of Engineering", "VP of Engineering",
"Chief Technology Officer", "Chief Executive Officer", "Chief Financial Officer", "Chief Operating Officer", "Chief Marketing Officer"
]
}
static jobDescriptors {
return ["Lead", "Senior", "Junior", "Associate", "Principal", "Staff", "Chief", "Head", "Director", "Manager"]
}
static productCategories {
return [
"Electronics", "Clothing", "Home & Garden", "Sports & Outdoors", "Books", "Toys & Games",
"Health & Beauty", "Automotive", "Food & Grocery", "Office Supplies", "Pet Supplies", "Jewelry",
"Music", "Movies", "Software", "Tools & Hardware", "Baby Products", "Furniture", "Art & Crafts", "Industrial"
]
}
static productAdjectives {
return [
"Incredible", "Fantastic", "Amazing", "Gorgeous", "Practical", "Sleek", "Elegant", "Rustic", "Modern",
"Handmade", "Refined", "Premium", "Ergonomic", "Intelligent", "Smart", "Licensed", "Recycled", "Unbranded", "Generic", "Tasty"
]
}
static productMaterials {
return [
"Steel", "Wooden", "Concrete", "Plastic", "Cotton", "Granite", "Rubber", "Metal", "Soft", "Fresh",
"Frozen", "Bronze", "Silver", "Gold", "Copper", "Marble", "Leather", "Silk", "Wool", "Linen"
]
}
static productNouns {
return [
"Chair", "Car", "Computer", "Keyboard", "Mouse", "Bike", "Ball", "Gloves", "Pants", "Shirt",
"Table", "Shoes", "Hat", "Towels", "Soap", "Tuna", "Chicken", "Fish", "Cheese", "Bacon",
"Pizza", "Salad", "Sausages", "Chips", "Watch", "Phone", "Tablet", "Camera", "Lamp", "Clock"
]
}
static creditCardTypes {
return [
["Visa", "4"],
["Mastercard", "5"],
["American Express", "3"],
["Discover", "6"]
]
}
static currencies {
return [
["USD", "US Dollar", "$"], ["EUR", "Euro", "€"], ["GBP", "British Pound", "£"],
["JPY", "Japanese Yen", "¥"], ["AUD", "Australian Dollar", "A$"], ["CAD", "Canadian Dollar", "C$"],
["CHF", "Swiss Franc", "CHF"], ["CNY", "Chinese Yuan", "¥"], ["INR", "Indian Rupee", "₹"],
["MXN", "Mexican Peso", "$"], ["BRL", "Brazilian Real", "R$"], ["RUB", "Russian Ruble", "₽"]
]
}
static colorNames {
return [
"Red", "Blue", "Green", "Yellow", "Orange", "Purple", "Pink", "Brown", "Black", "White",
"Gray", "Cyan", "Magenta", "Lime", "Maroon", "Navy", "Olive", "Teal", "Aqua", "Silver",
"Crimson", "Coral", "Salmon", "Tomato", "Gold", "Khaki", "Indigo", "Violet", "Plum", "Orchid",
"Tan", "Beige", "Ivory", "Lavender", "Mint", "Turquoise", "Azure", "Slate", "Charcoal", "Pearl"
]
}
static months {
return ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
}
static daysOfWeek {
return ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
}
static topLevelDomains {
return ["com", "org", "net", "edu", "gov", "io", "co", "biz", "info", "me", "us", "uk", "de", "fr", "jp"]
}
static httpMethods {
return ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
}
static protocols {
return ["http", "https"]
}
static userAgents {
return [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1"
]
}
}
class Faker {
static seed(value) { FakerRandom.seed(value) }
static reset() { FakerRandom.reset() }
static randomElement(list) {
if (list.count == 0) return null
return list[FakerRandom.next(0, list.count - 1)]
}
static randomElements(list, count) {
var result = []
for (i in 0...count) {
result.add(randomElement(list))
}
return result
}
static randomInt(min, max) { FakerRandom.next(min, max) }
static randomFloat(min, max) {
var f = FakerRandom.nextFloat()
return min + (f * (max - min))
}
static randomFloat(min, max, precision) {
var f = randomFloat(min, max)
var mult = 1
for (i in 0...precision) mult = mult * 10
return (f * mult).round / mult
}
static boolean() { FakerRandom.next(0, 1) == 1 }
static bool() { boolean() }
static numerify(format) {
var result = ""
for (char in format) {
if (char == "#") {
result = result + FakerRandom.next(0, 9).toString
} else {
result = result + char
}
}
return result
}
static letterify(format) {
var letters = "abcdefghijklmnopqrstuvwxyz"
var result = ""
for (char in format) {
if (char == "?") {
result = result + letters[FakerRandom.next(0, 25)]
} else {
result = result + char
}
}
return result
}
static bothify(format) { letterify(numerify(format)) }
static firstName() { randomElement(FakerData.firstNames) }
static firstNameMale() { randomElement(FakerData.firstNamesMale) }
static firstNameFemale() { randomElement(FakerData.firstNamesFemale) }
static lastName() { randomElement(FakerData.lastNames) }
static name() { "%(firstName()) %(lastName())" }
static nameMale() { "%(firstNameMale()) %(lastName())" }
static nameFemale() { "%(firstNameFemale()) %(lastName())" }
static prefix() { randomElement(["Mr.", "Mrs.", "Ms.", "Miss", "Dr."]) }
static namePrefix() { prefix() }
static suffix() { randomElement(["Jr.", "Sr.", "I", "II", "III", "IV", "V", "MD", "PhD"]) }
static nameSuffix() { suffix() }
static gender() { randomElement(["Male", "Female"]) }
static sex() { gender() }
static username() {
var first = Str.toLower(firstName())
var last = Str.toLower(lastName())
var patterns = [
"%(first)%(last)",
"%(first).%(last)",
"%(first)_%(last)",
"%(first)%(FakerRandom.next(1, 99))",
"%(first).%(last)%(FakerRandom.next(1, 99))",
"%(first)_%(FakerRandom.next(100, 999))"
]
return randomElement(patterns)
}
static email() {
var user = username()
var domain = randomElement(FakerData.emailDomains)
return "%(user)@%(domain)"
}
static exampleEmail() {
var user = username()
var domain = randomElement(["example.com", "example.org", "example.net"])
return "%(user)@%(domain)"
}
static city() { randomElement(FakerData.cities) }
static state() {
var s = randomElement(FakerData.states)
return s[0]
}
static stateAbbr() {
var s = randomElement(FakerData.states)
return s[1]
}
static stateFull() { state() }
static stateName() { state() }
static country() {
var c = randomElement(FakerData.countries)
return c[0]
}
static countryCode() {
var c = randomElement(FakerData.countries)
return c[1]
}
static countryName() { country() }
static zipCode() { numerify("#####") }
static postcode() { zipCode() }
static buildingNumber() { FakerRandom.next(1, 9999).toString }
static streetName() {
var name = randomElement(FakerData.streetNames)
var suffix = randomElement(FakerData.streetSuffixes)
return "%(name) %(suffix)"
}
static streetAddress() { "%(buildingNumber()) %(streetName())" }
static address() {
return "%(streetAddress()), %(city()), %(stateAbbr()) %(zipCode())"
}
static latitude() { randomFloat(-90, 90, 6) }
static longitude() { randomFloat(-180, 180, 6) }
static latitude(min, max) { randomFloat(min, max, 6) }
static longitude(min, max) { randomFloat(min, max, 6) }
static ipv4() {
var a = FakerRandom.next(1, 255)
var b = FakerRandom.next(0, 255)
var c = FakerRandom.next(0, 255)
var d = FakerRandom.next(1, 254)
return "%(a).%(b).%(c).%(d)"
}
static ipv6() {
var parts = []
for (i in 0...8) {
var hex = ""
for (j in 0...4) {
var digit = FakerRandom.next(0, 15)
hex = hex + "0123456789abcdef"[digit]
}
parts.add(hex)
}
return parts.join(":")
}
static macAddress() {
var parts = []
for (i in 0...6) {
var hex = ""
for (j in 0...2) {
var digit = FakerRandom.next(0, 15)
hex = hex + "0123456789abcdef"[digit]
}
parts.add(hex)
}
return parts.join(":")
}
static port() { FakerRandom.next(1, 65535) }
static tld() { randomElement(FakerData.topLevelDomains) }
static topLevelDomain() { tld() }
static domainWord() {
var words = [Str.toLower(firstName()), Str.toLower(lastName()), Str.toLower(randomElement(FakerData.companyNouns))]
return randomElement(words)
}
static domainName() { "%(domainWord()).%(tld())" }
static protocol() { randomElement(FakerData.protocols) }
static url() { "%(protocol())://%(domainName())" }
static password() { password(12) }
static password(length) {
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#^&*-_+="
var result = ""
for (i in 0...length) {
result = result + chars[FakerRandom.next(0, chars.count - 1)]
}
return result
}
static httpMethod() { randomElement(FakerData.httpMethods) }
static httpStatusCode() { randomElement([200, 201, 204, 301, 302, 400, 401, 403, 404, 500, 502, 503]) }
static userAgent() { randomElement(FakerData.userAgents) }
static hostname() { "%(domainWord())-%(FakerRandom.next(1, 99)).%(domainName())" }
static uuid() { Uuid.v4() }
static md5() { Hash.toHex(Hash.md5(password(32))) }
static sha1() { Hash.toHex(Hash.sha1(password(32))) }
static sha256() { Hash.toHex(Hash.sha256(password(32))) }
static hexColor() {
var hex = ""
for (i in 0...6) {
var digit = FakerRandom.next(0, 15)
hex = hex + "0123456789abcdef"[digit]
}
return "#" + hex
}
static rgbColor() {
var r = FakerRandom.next(0, 255)
var g = FakerRandom.next(0, 255)
var b = FakerRandom.next(0, 255)
return "rgb(%(r), %(g), %(b))"
}
static rgb() { rgbColor() }
static rgbaCssColor() {
var r = FakerRandom.next(0, 255)
var g = FakerRandom.next(0, 255)
var b = FakerRandom.next(0, 255)
var a = randomFloat(0, 1, 2)
return "rgba(%(r), %(g), %(b), %(a))"
}
static colorName() { randomElement(FakerData.colorNames) }
static date() {
var now = DateTime.now()
var start = now - Duration.fromDays(365 * 10)
var end = now
return dateBetween(start, end)
}
static dateTime() {
var now = DateTime.now()
var start = now - Duration.fromDays(365 * 10)
var range = 365 * 10 * 24 * 60 * 60
var offset = FakerRandom.next(0, range)
var dt = start + Duration.fromSeconds(offset)
return dt.toIso8601
}
static pastDate() { pastDate(1) }
static pastDate(years) {
var now = DateTime.now()
var start = now - Duration.fromDays(365 * years)
return dateBetween(start, now)
}
static futureDate() { futureDate(1) }
static futureDate(years) {
var now = DateTime.now()
var end = now + Duration.fromDays(365 * years)
return dateBetween(now, end)
}
static dateBetween(start, end) {
var startTs = start.timestamp
var endTs = end.timestamp
if (endTs <= startTs) return start.format("\%Y-\%m-\%d")
var range = endTs - startTs
var offset = FakerRandom.next(0, range.floor)
var dt = DateTime.fromTimestamp(startTs + offset)
return dt.format("\%Y-\%m-\%d")
}
static dateOfBirth() { dateOfBirth(18, 80) }
static dateOfBirth(minAge, maxAge) {
var now = DateTime.now()
var end = now - Duration.fromDays(365 * minAge)
var start = now - Duration.fromDays(365 * maxAge)
return dateBetween(start, end)
}
static year() { FakerRandom.next(1950, 2025) }
static month() { FakerRandom.next(1, 12) }
static monthName() { FakerData.months[FakerRandom.next(0, 11)] }
static dayOfWeek() { randomElement(FakerData.daysOfWeek) }
static dayOfMonth() { FakerRandom.next(1, 28) }
static hour() { FakerRandom.next(0, 23) }
static minute() { FakerRandom.next(0, 59) }
static second() { FakerRandom.next(0, 59) }
static amPm() { randomElement(["AM", "PM"]) }
static time() {
var h = hour()
var m = minute()
var s = second()
return "%(h < 10 ? "0" : "")%(h):%(m < 10 ? "0" : "")%(m):%(s < 10 ? "0" : "")%(s)"
}
static word() { randomElement(FakerData.loremWords) }
static words(count) {
var result = []
for (i in 0...count) {
result.add(word())
}
return result
}
static sentence() { sentence(FakerRandom.next(5, 12)) }
static sentence(wordCount) {
var w = words(wordCount)
var first = w[0]
w[0] = first[0].bytes[0] >= 97 && first[0].bytes[0] <= 122 ? String.fromCodePoint(first[0].bytes[0] - 32) + first[1..-1] : first
return w.join(" ") + "."
}
static sentences(count) {
var result = []
for (i in 0...count) {
result.add(sentence())
}
return result.join(" ")
}
static paragraph() { paragraph(FakerRandom.next(3, 6)) }
static paragraph(sentenceCount) { sentences(sentenceCount) }
static paragraphs(count) {
var result = []
for (i in 0...count) {
result.add(paragraph())
}
return result.join("\n\n")
}
static text() { text(200) }
static text(maxChars) {
var result = ""
while (result.count < maxChars) {
result = result + sentence() + " "
}
return result[0...maxChars]
}
static slug() { slug(3) }
static slug(wordCount) {
var w = words(wordCount)
var lower = []
for (word in w) {
lower.add(Str.toLower(word))
}
return lower.join("-")
}
static company() {
var patterns = [
"%(lastName()) %(randomElement(FakerData.companySuffixes))",
"%(lastName())-%(lastName())",
"%(randomElement(FakerData.companyPrefixes)) %(randomElement(FakerData.companyNouns))",
"%(lastName()) %(randomElement(FakerData.companyNouns))"
]
return randomElement(patterns)
}
static companyName() { company() }
static companySuffix() { randomElement(FakerData.companySuffixes) }
static job() { randomElement(FakerData.jobTitles) }
static jobTitle() { job() }
static jobDescriptor() { randomElement(FakerData.jobDescriptors) }
static product() {
var adj = randomElement(FakerData.productAdjectives)
var mat = randomElement(FakerData.productMaterials)
var noun = randomElement(FakerData.productNouns)
return "%(adj) %(mat) %(noun)"
}
static productName() { product() }
static productCategory() { randomElement(FakerData.productCategories) }
static price() { price(1, 1000) }
static price(min, max) { randomFloat(min, max, 2) }
static currency() {
var c = randomElement(FakerData.currencies)
return c[0]
}
static currencyName() {
var c = randomElement(FakerData.currencies)
return c[1]
}
static currencySymbol() {
var c = randomElement(FakerData.currencies)
return c[2]
}
static creditCardType() {
var c = randomElement(FakerData.creditCardTypes)
return c[0]
}
static creditCardNumber() {
var type = randomElement(FakerData.creditCardTypes)
var prefix = type[1]
return prefix + numerify("###-####-####-###")
}
static creditCardCVV() { numerify("###") }
static creditCardExpiryDate() {
var m = FakerRandom.next(1, 12)
var y = FakerRandom.next(25, 30)
return "%(m < 10 ? "0" : "")%(m)/%(y)"
}
static phoneNumber() { numerify("(###) ###-####") }
static phone() { phoneNumber() }
static iban() {
var country = randomElement(["DE", "FR", "GB", "ES", "IT", "NL"])
var check = numerify("##")
var bban = numerify("####################")
return country + check + bban
}
static accountNumber() { accountNumber(10) }
static accountNumber(length) {
var result = ""
for (i in 0...length) {
result = result + FakerRandom.next(0, 9).toString
}
return result
}
static routingNumber() { numerify("#########") }
static fileName() {
var extensions = ["txt", "pdf", "doc", "xls", "jpg", "png", "gif", "mp3", "mp4", "zip"]
var name = Str.toLower(word())
var ext = randomElement(extensions)
return "%(name).%(ext)"
}
static fileExtension() { randomElement(["txt", "pdf", "doc", "xls", "jpg", "png", "gif", "mp3", "mp4", "zip"]) }
static mimeType() {
return randomElement([
"application/json", "application/xml", "application/pdf", "application/zip",
"text/html", "text/plain", "text/css", "text/javascript",
"image/jpeg", "image/png", "image/gif", "image/svg+xml",
"audio/mpeg", "video/mp4"
])
}
static semver() { "%(FakerRandom.next(0, 9)).%(FakerRandom.next(0, 20)).%(FakerRandom.next(0, 50))" }
static digit() { FakerRandom.next(0, 9) }
static randomDigit() { digit() }
static digits(count) {
var result = []
for (i in 0...count) {
result.add(digit())
}
return result
}
static letter() { "abcdefghijklmnopqrstuvwxyz"[FakerRandom.next(0, 25)] }
static letters(count) {
var result = ""
for (i in 0...count) {
result = result + letter()
}
return result
}
static shuffle(list) {
var result = list.toList
for (i in (result.count - 1)..1) {
var j = FakerRandom.next(0, i)
var temp = result[i]
result[i] = result[j]
result[j] = temp
}
return result
}
static profile() {
return {
"username": username(),
"name": name(),
"email": email(),
"address": address(),
"phone": phoneNumber(),
"job": jobTitle(),
"company": company(),
"birthdate": dateOfBirth()
}
}
static simpleProfile() {
return {
"username": username(),
"name": name(),
"email": email(),
"address": address()
}
}
static locale() { "en_US" }
}
+894
View File
@@ -0,0 +1,894 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/faker.wren` using `util/wren_to_c_string.py`
static const char* fakerModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"crypto\" for Crypto, Hash\n"
"import \"uuid\" for Uuid\n"
"import \"datetime\" for DateTime, Duration\n"
"import \"strutil\" for Str\n"
"\n"
"class FakerRandom {\n"
" static seed_ { __seed }\n"
" static state_ { __state }\n"
"\n"
" static seed(value) {\n"
" __seed = value\n"
" __state = value\n"
" }\n"
"\n"
" static reset() {\n"
" __seed = null\n"
" __state = null\n"
" }\n"
"\n"
" static next(min, max) {\n"
" if (__seed == null) {\n"
" return Crypto.randomInt(min, max + 1)\n"
" }\n"
" if (__state == null) __state = __seed\n"
" __state = ((__state * 1103515245) + 12345) % 2147483648\n"
" var range = max - min + 1\n"
" return min + (__state % range)\n"
" }\n"
"\n"
" static nextFloat() {\n"
" if (__seed == null) {\n"
" var bytes = Crypto.randomBytes(4)\n"
" var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]\n"
" if (value < 0) value = -value\n"
" return value / 2147483648\n"
" }\n"
" if (__state == null) __state = __seed\n"
" __state = ((__state * 1103515245) + 12345) % 2147483648\n"
" return __state / 2147483648\n"
" }\n"
"}\n"
"\n"
"class FakerData {\n"
" static firstNamesMale {\n"
" return [\n"
" \"James\", \"John\", \"Robert\", \"Michael\", \"William\", \"David\", \"Richard\", \"Joseph\", \"Thomas\", \"Charles\",\n"
" \"Christopher\", \"Daniel\", \"Matthew\", \"Anthony\", \"Mark\", \"Donald\", \"Steven\", \"Paul\", \"Andrew\", \"Joshua\",\n"
" \"Kenneth\", \"Kevin\", \"Brian\", \"George\", \"Timothy\", \"Ronald\", \"Edward\", \"Jason\", \"Jeffrey\", \"Ryan\",\n"
" \"Jacob\", \"Gary\", \"Nicholas\", \"Eric\", \"Jonathan\", \"Stephen\", \"Larry\", \"Justin\", \"Scott\", \"Brandon\",\n"
" \"Benjamin\", \"Samuel\", \"Raymond\", \"Gregory\", \"Frank\", \"Alexander\", \"Patrick\", \"Jack\", \"Dennis\", \"Jerry\",\n"
" \"Tyler\", \"Aaron\", \"Jose\", \"Adam\", \"Nathan\", \"Henry\", \"Douglas\", \"Zachary\", \"Peter\", \"Kyle\",\n"
" \"Noah\", \"Ethan\", \"Jeremy\", \"Walter\", \"Christian\", \"Keith\", \"Roger\", \"Terry\", \"Austin\", \"Sean\",\n"
" \"Gerald\", \"Carl\", \"Harold\", \"Dylan\", \"Arthur\", \"Lawrence\", \"Jordan\", \"Jesse\", \"Bryan\", \"Billy\",\n"
" \"Bruce\", \"Gabriel\", \"Joe\", \"Logan\", \"Albert\", \"Willie\", \"Alan\", \"Vincent\", \"Eugene\", \"Russell\",\n"
" \"Elijah\", \"Randy\", \"Philip\", \"Harry\", \"Howard\", \"Roy\", \"Louis\", \"Russell\", \"Bobby\", \"Johnny\"\n"
" ]\n"
" }\n"
"\n"
" static firstNamesFemale {\n"
" return [\n"
" \"Mary\", \"Patricia\", \"Jennifer\", \"Linda\", \"Barbara\", \"Elizabeth\", \"Susan\", \"Jessica\", \"Sarah\", \"Karen\",\n"
" \"Lisa\", \"Nancy\", \"Betty\", \"Margaret\", \"Sandra\", \"Ashley\", \"Kimberly\", \"Emily\", \"Donna\", \"Michelle\",\n"
" \"Dorothy\", \"Carol\", \"Amanda\", \"Melissa\", \"Deborah\", \"Stephanie\", \"Rebecca\", \"Sharon\", \"Laura\", \"Cynthia\",\n"
" \"Kathleen\", \"Amy\", \"Angela\", \"Shirley\", \"Anna\", \"Brenda\", \"Pamela\", \"Emma\", \"Nicole\", \"Helen\",\n"
" \"Samantha\", \"Katherine\", \"Christine\", \"Debra\", \"Rachel\", \"Carolyn\", \"Janet\", \"Catherine\", \"Maria\", \"Heather\",\n"
" \"Diane\", \"Ruth\", \"Julie\", \"Olivia\", \"Joyce\", \"Virginia\", \"Victoria\", \"Kelly\", \"Lauren\", \"Christina\",\n"
" \"Joan\", \"Evelyn\", \"Judith\", \"Megan\", \"Andrea\", \"Cheryl\", \"Hannah\", \"Jacqueline\", \"Martha\", \"Gloria\",\n"
" \"Teresa\", \"Ann\", \"Sara\", \"Madison\", \"Frances\", \"Kathryn\", \"Janice\", \"Jean\", \"Abigail\", \"Alice\",\n"
" \"Judy\", \"Sophia\", \"Grace\", \"Denise\", \"Amber\", \"Doris\", \"Marilyn\", \"Danielle\", \"Beverly\", \"Isabella\",\n"
" \"Theresa\", \"Diana\", \"Natalie\", \"Brittany\", \"Charlotte\", \"Marie\", \"Kayla\", \"Alexis\", \"Lori\", \"Jane\"\n"
" ]\n"
" }\n"
"\n"
" static firstNames {\n"
" return firstNamesMale + firstNamesFemale\n"
" }\n"
"\n"
" static lastNames {\n"
" return [\n"
" \"Smith\", \"Johnson\", \"Williams\", \"Brown\", \"Jones\", \"Garcia\", \"Miller\", \"Davis\", \"Rodriguez\", \"Martinez\",\n"
" \"Hernandez\", \"Lopez\", \"Gonzalez\", \"Wilson\", \"Anderson\", \"Thomas\", \"Taylor\", \"Moore\", \"Jackson\", \"Martin\",\n"
" \"Lee\", \"Perez\", \"Thompson\", \"White\", \"Harris\", \"Sanchez\", \"Clark\", \"Ramirez\", \"Lewis\", \"Robinson\",\n"
" \"Walker\", \"Young\", \"Allen\", \"King\", \"Wright\", \"Scott\", \"Torres\", \"Nguyen\", \"Hill\", \"Flores\",\n"
" \"Green\", \"Adams\", \"Nelson\", \"Baker\", \"Hall\", \"Rivera\", \"Campbell\", \"Mitchell\", \"Carter\", \"Roberts\",\n"
" \"Gomez\", \"Phillips\", \"Evans\", \"Turner\", \"Diaz\", \"Parker\", \"Cruz\", \"Edwards\", \"Collins\", \"Reyes\",\n"
" \"Stewart\", \"Morris\", \"Morales\", \"Murphy\", \"Cook\", \"Rogers\", \"Gutierrez\", \"Ortiz\", \"Morgan\", \"Cooper\",\n"
" \"Peterson\", \"Bailey\", \"Reed\", \"Kelly\", \"Howard\", \"Ramos\", \"Kim\", \"Cox\", \"Ward\", \"Richardson\",\n"
" \"Watson\", \"Brooks\", \"Chavez\", \"Wood\", \"James\", \"Bennett\", \"Gray\", \"Mendoza\", \"Ruiz\", \"Hughes\",\n"
" \"Price\", \"Alvarez\", \"Castillo\", \"Sanders\", \"Patel\", \"Myers\", \"Long\", \"Ross\", \"Foster\", \"Jimenez\",\n"
" \"Powell\", \"Jenkins\", \"Perry\", \"Russell\", \"Sullivan\", \"Bell\", \"Coleman\", \"Butler\", \"Henderson\", \"Barnes\",\n"
" \"Gonzales\", \"Fisher\", \"Vasquez\", \"Simmons\", \"Graham\", \"Patterson\", \"Jordan\", \"Reynolds\", \"Hamilton\", \"Graham\",\n"
" \"Wallace\", \"Cole\", \"West\", \"Stone\", \"Holmes\", \"Meyer\", \"Boyd\", \"Mills\", \"Warren\", \"Fox\",\n"
" \"Rose\", \"Rice\", \"Moreno\", \"Schmidt\", \"Patel\", \"Ferguson\", \"Nichols\", \"Herrera\", \"Medina\", \"Ryan\",\n"
" \"Fernandez\", \"Weaver\", \"Daniels\", \"Stephens\", \"Knight\", \"Hudson\", \"Spencer\", \"Elliott\", \"Bishop\", \"Craig\",\n"
" \"Hunter\", \"Hart\", \"Armstrong\", \"Carpenter\", \"Harvey\", \"Hawkins\", \"Harrison\", \"Fields\", \"Webb\", \"Tucker\",\n"
" \"Mason\", \"Porter\", \"Burns\", \"Gibson\", \"Ellis\", \"Gordon\", \"Kennedy\", \"Willis\", \"Chapman\", \"Palmer\",\n"
" \"Arnold\", \"Lane\", \"Dean\", \"Matthews\", \"Wagner\", \"Austin\", \"Murray\", \"Stanley\", \"Fowler\", \"Grant\",\n"
" \"Kelley\", \"Black\", \"Lynch\", \"Barnes\", \"Dunn\", \"Shaw\", \"Olson\", \"Simpson\", \"George\", \"Marshall\",\n"
" \"Watkins\", \"Owens\", \"Hunt\", \"Hicks\", \"Freeman\", \"Pierce\", \"Snyder\", \"Franklin\", \"Soto\", \"Newman\"\n"
" ]\n"
" }\n"
"\n"
" static emailDomains {\n"
" return [\n"
" \"gmail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\", \"icloud.com\",\n"
" \"mail.com\", \"protonmail.com\", \"aol.com\", \"zoho.com\", \"yandex.com\",\n"
" \"gmx.com\", \"fastmail.com\", \"tutanota.com\", \"inbox.com\", \"live.com\",\n"
" \"example.com\", \"example.org\", \"example.net\", \"test.com\", \"demo.com\",\n"
" \"company.com\", \"corp.com\", \"business.com\", \"enterprise.com\", \"work.com\",\n"
" \"office.com\", \"professional.com\", \"service.com\", \"support.com\", \"info.com\"\n"
" ]\n"
" }\n"
"\n"
" static loremWords {\n"
" return [\n"
" \"lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\", \"consectetur\", \"adipiscing\", \"elit\", \"sed\", \"do\",\n"
" \"eiusmod\", \"tempor\", \"incididunt\", \"ut\", \"labore\", \"et\", \"dolore\", \"magna\", \"aliqua\", \"enim\",\n"
" \"ad\", \"minim\", \"veniam\", \"quis\", \"nostrud\", \"exercitation\", \"ullamco\", \"laboris\", \"nisi\", \"aliquip\",\n"
" \"ex\", \"ea\", \"commodo\", \"consequat\", \"duis\", \"aute\", \"irure\", \"in\", \"reprehenderit\", \"voluptate\",\n"
" \"velit\", \"esse\", \"cillum\", \"fugiat\", \"nulla\", \"pariatur\", \"excepteur\", \"sint\", \"occaecat\", \"cupidatat\",\n"
" \"non\", \"proident\", \"sunt\", \"culpa\", \"qui\", \"officia\", \"deserunt\", \"mollit\", \"anim\", \"id\",\n"
" \"est\", \"laborum\", \"at\", \"vero\", \"eos\", \"accusamus\", \"iusto\", \"odio\", \"dignissimos\", \"ducimus\",\n"
" \"blanditiis\", \"praesentium\", \"voluptatum\", \"deleniti\", \"atque\", \"corrupti\", \"quos\", \"quas\", \"molestias\",\n"
" \"excepturi\", \"occaecati\", \"cupiditate\", \"provident\", \"similique\", \"mollitia\", \"animi\", \"quasi\", \"architecto\",\n"
" \"beatae\", \"vitae\", \"dicta\", \"explicabo\", \"nemo\", \"ipsam\", \"quia\", \"voluptas\", \"aspernatur\", \"aut\",\n"
" \"odit\", \"fugit\", \"consequuntur\", \"magni\", \"dolores\", \"ratione\", \"sequi\", \"nesciunt\", \"neque\", \"porro\",\n"
" \"quisquam\", \"dolorem\", \"adipisci\", \"numquam\", \"eius\", \"modi\", \"tempora\", \"magnam\", \"quaerat\", \"rem\",\n"
" \"aperiam\", \"eaque\", \"ipsa\", \"quae\", \"ab\", \"illo\", \"inventore\", \"veritatis\", \"quasi\", \"perspiciatis\",\n"
" \"unde\", \"omnis\", \"iste\", \"natus\", \"error\", \"accusantium\", \"doloremque\", \"laudantium\", \"totam\", \"recusandae\",\n"
" \"saepe\", \"eveniet\", \"voluptatem\", \"alias\", \"autem\", \"minima\", \"eligendi\", \"optio\", \"cumque\", \"nihil\",\n"
" \"impedit\", \"quo\", \"minus\", \"quod\", \"maxime\", \"placeat\", \"facere\", \"possimus\", \"assumenda\", \"repellendus\",\n"
" \"temporibus\", \"debitis\", \"rerum\", \"hic\", \"tenetur\", \"sapiente\", \"delectus\", \"reiciendis\", \"perferendis\",\n"
" \"doloribus\", \"asperiores\", \"repellat\", \"nam\", \"libero\", \"soluta\", \"nobis\", \"cum\", \"corporis\", \"suscipit\",\n"
" \"officiis\", \"laboriosam\", \"harum\", \"necessitatibus\", \"praesentium\", \"voluptates\", \"repudiandae\", \"fuga\", \"sint\"\n"
" ]\n"
" }\n"
"\n"
" static cities {\n"
" return [\n"
" \"New York\", \"Los Angeles\", \"Chicago\", \"Houston\", \"Phoenix\", \"Philadelphia\", \"San Antonio\", \"San Diego\",\n"
" \"Dallas\", \"San Jose\", \"Austin\", \"Jacksonville\", \"Fort Worth\", \"Columbus\", \"Charlotte\", \"San Francisco\",\n"
" \"Indianapolis\", \"Seattle\", \"Denver\", \"Washington\", \"Boston\", \"El Paso\", \"Nashville\", \"Detroit\", \"Portland\",\n"
" \"Memphis\", \"Oklahoma City\", \"Las Vegas\", \"Louisville\", \"Baltimore\", \"Milwaukee\", \"Albuquerque\", \"Tucson\",\n"
" \"Fresno\", \"Sacramento\", \"Kansas City\", \"Mesa\", \"Atlanta\", \"Omaha\", \"Colorado Springs\", \"Raleigh\", \"Miami\",\n"
" \"Long Beach\", \"Virginia Beach\", \"Oakland\", \"Minneapolis\", \"Tulsa\", \"Tampa\", \"Arlington\", \"New Orleans\",\n"
" \"Wichita\", \"Cleveland\", \"Bakersfield\", \"Aurora\", \"Anaheim\", \"Honolulu\", \"Santa Ana\", \"Riverside\", \"Corpus Christi\",\n"
" \"Lexington\", \"Henderson\", \"Stockton\", \"Saint Paul\", \"Cincinnati\", \"St. Louis\", \"Pittsburgh\", \"Greensboro\",\n"
" \"Lincoln\", \"Anchorage\", \"Plano\", \"Orlando\", \"Irvine\", \"Newark\", \"Durham\", \"Chula Vista\", \"Toledo\", \"Fort Wayne\",\n"
" \"St. Petersburg\", \"Laredo\", \"Jersey City\", \"Chandler\", \"Madison\", \"Lubbock\", \"Scottsdale\", \"Reno\", \"Buffalo\",\n"
" \"Gilbert\", \"Glendale\", \"North Las Vegas\", \"Winston-Salem\", \"Chesapeake\", \"Norfolk\", \"Fremont\", \"Garland\",\n"
" \"Irving\", \"Hialeah\", \"Richmond\", \"Boise\", \"Spokane\", \"Baton Rouge\", \"Tacoma\", \"San Bernardino\", \"Modesto\",\n"
" \"Fontana\", \"Des Moines\", \"Moreno Valley\", \"Santa Clarita\", \"Fayetteville\", \"Birmingham\", \"Oxnard\", \"Rochester\",\n"
" \"Port St. Lucie\", \"Grand Rapids\", \"Huntsville\", \"Salt Lake City\", \"Frisco\", \"Yonkers\", \"Amarillo\", \"Glendale\",\n"
" \"Huntington Beach\", \"McKinney\", \"Montgomery\", \"Augusta\", \"Aurora\", \"Akron\", \"Little Rock\", \"Tempe\", \"Columbus\",\n"
" \"Overland Park\", \"Grand Prairie\", \"Tallahassee\", \"Cape Coral\", \"Mobile\", \"Knoxville\", \"Shreveport\", \"Worcester\",\n"
" \"Ontario\", \"Vancouver\", \"Sioux Falls\", \"Chattanooga\", \"Brownsville\", \"Fort Lauderdale\", \"Providence\", \"Newport News\",\n"
" \"Rancho Cucamonga\", \"Santa Rosa\", \"Peoria\", \"Oceanside\", \"Elk Grove\", \"Salem\", \"Pembroke Pines\", \"Eugene\", \"Garden Grove\",\n"
" \"Cary\", \"Fort Collins\", \"Corona\", \"Springfield\", \"Jackson\", \"Alexandria\", \"Hayward\", \"Lancaster\", \"Lakewood\",\n"
" \"Clarksville\", \"Palmdale\", \"Salinas\", \"Springfield\", \"Hollywood\", \"Pasadena\", \"Sunnyvale\", \"Macon\", \"Kansas City\",\n"
" \"Pomona\", \"Escondido\", \"Killeen\", \"Naperville\", \"Joliet\", \"Bellevue\", \"Rockford\", \"Savannah\", \"Paterson\", \"Torrance\",\n"
" \"Bridgeport\", \"McAllen\", \"Mesquite\", \"Syracuse\", \"Midland\", \"Pasadena\", \"Murfreesboro\", \"Miramar\", \"Dayton\", \"Fullerton\"\n"
" ]\n"
" }\n"
"\n"
" static states {\n"
" return [\n"
" [\"Alabama\", \"AL\"], [\"Alaska\", \"AK\"], [\"Arizona\", \"AZ\"], [\"Arkansas\", \"AR\"], [\"California\", \"CA\"],\n"
" [\"Colorado\", \"CO\"], [\"Connecticut\", \"CT\"], [\"Delaware\", \"DE\"], [\"Florida\", \"FL\"], [\"Georgia\", \"GA\"],\n"
" [\"Hawaii\", \"HI\"], [\"Idaho\", \"ID\"], [\"Illinois\", \"IL\"], [\"Indiana\", \"IN\"], [\"Iowa\", \"IA\"],\n"
" [\"Kansas\", \"KS\"], [\"Kentucky\", \"KY\"], [\"Louisiana\", \"LA\"], [\"Maine\", \"ME\"], [\"Maryland\", \"MD\"],\n"
" [\"Massachusetts\", \"MA\"], [\"Michigan\", \"MI\"], [\"Minnesota\", \"MN\"], [\"Mississippi\", \"MS\"], [\"Missouri\", \"MO\"],\n"
" [\"Montana\", \"MT\"], [\"Nebraska\", \"NE\"], [\"Nevada\", \"NV\"], [\"New Hampshire\", \"NH\"], [\"New Jersey\", \"NJ\"],\n"
" [\"New Mexico\", \"NM\"], [\"New York\", \"NY\"], [\"North Carolina\", \"NC\"], [\"North Dakota\", \"ND\"], [\"Ohio\", \"OH\"],\n"
" [\"Oklahoma\", \"OK\"], [\"Oregon\", \"OR\"], [\"Pennsylvania\", \"PA\"], [\"Rhode Island\", \"RI\"], [\"South Carolina\", \"SC\"],\n"
" [\"South Dakota\", \"SD\"], [\"Tennessee\", \"TN\"], [\"Texas\", \"TX\"], [\"Utah\", \"UT\"], [\"Vermont\", \"VT\"],\n"
" [\"Virginia\", \"VA\"], [\"Washington\", \"WA\"], [\"West Virginia\", \"WV\"], [\"Wisconsin\", \"WI\"], [\"Wyoming\", \"WY\"]\n"
" ]\n"
" }\n"
"\n"
" static countries {\n"
" return [\n"
" [\"United States\", \"US\"], [\"United Kingdom\", \"GB\"], [\"Canada\", \"CA\"], [\"Australia\", \"AU\"], [\"Germany\", \"DE\"],\n"
" [\"France\", \"FR\"], [\"Italy\", \"IT\"], [\"Spain\", \"ES\"], [\"Netherlands\", \"NL\"], [\"Belgium\", \"BE\"],\n"
" [\"Switzerland\", \"CH\"], [\"Austria\", \"AT\"], [\"Sweden\", \"SE\"], [\"Norway\", \"NO\"], [\"Denmark\", \"DK\"],\n"
" [\"Finland\", \"FI\"], [\"Ireland\", \"IE\"], [\"Portugal\", \"PT\"], [\"Greece\", \"GR\"], [\"Poland\", \"PL\"],\n"
" [\"Czech Republic\", \"CZ\"], [\"Hungary\", \"HU\"], [\"Romania\", \"RO\"], [\"Bulgaria\", \"BG\"], [\"Croatia\", \"HR\"],\n"
" [\"Slovakia\", \"SK\"], [\"Slovenia\", \"SI\"], [\"Estonia\", \"EE\"], [\"Latvia\", \"LV\"], [\"Lithuania\", \"LT\"],\n"
" [\"Japan\", \"JP\"], [\"China\", \"CN\"], [\"South Korea\", \"KR\"], [\"India\", \"IN\"], [\"Singapore\", \"SG\"],\n"
" [\"Malaysia\", \"MY\"], [\"Thailand\", \"TH\"], [\"Indonesia\", \"ID\"], [\"Philippines\", \"PH\"], [\"Vietnam\", \"VN\"],\n"
" [\"Brazil\", \"BR\"], [\"Mexico\", \"MX\"], [\"Argentina\", \"AR\"], [\"Chile\", \"CL\"], [\"Colombia\", \"CO\"],\n"
" [\"Peru\", \"PE\"], [\"Venezuela\", \"VE\"], [\"Ecuador\", \"EC\"], [\"Uruguay\", \"UY\"], [\"Paraguay\", \"PY\"],\n"
" [\"South Africa\", \"ZA\"], [\"Egypt\", \"EG\"], [\"Nigeria\", \"NG\"], [\"Kenya\", \"KE\"], [\"Morocco\", \"MA\"],\n"
" [\"Israel\", \"IL\"], [\"Turkey\", \"TR\"], [\"Saudi Arabia\", \"SA\"], [\"United Arab Emirates\", \"AE\"], [\"Russia\", \"RU\"],\n"
" [\"Ukraine\", \"UA\"], [\"New Zealand\", \"NZ\"], [\"Iceland\", \"IS\"], [\"Luxembourg\", \"LU\"], [\"Malta\", \"MT\"]\n"
" ]\n"
" }\n"
"\n"
" static streetSuffixes {\n"
" return [\n"
" \"Street\", \"Avenue\", \"Boulevard\", \"Drive\", \"Lane\", \"Road\", \"Way\", \"Place\", \"Court\", \"Circle\",\n"
" \"Trail\", \"Parkway\", \"Terrace\", \"Plaza\", \"Commons\", \"Alley\", \"Path\", \"Run\", \"View\", \"Ridge\"\n"
" ]\n"
" }\n"
"\n"
" static streetNames {\n"
" return [\n"
" \"Main\", \"Oak\", \"Maple\", \"Cedar\", \"Pine\", \"Elm\", \"Washington\", \"Lake\", \"Hill\", \"Park\",\n"
" \"River\", \"Spring\", \"Valley\", \"Forest\", \"Sunset\", \"Highland\", \"Meadow\", \"Church\", \"Mill\", \"Union\",\n"
" \"Market\", \"Water\", \"Bridge\", \"School\", \"North\", \"South\", \"East\", \"West\", \"Center\", \"High\",\n"
" \"Front\", \"Broad\", \"College\", \"Prospect\", \"Lincoln\", \"Franklin\", \"Jefferson\", \"Adams\", \"Madison\", \"Jackson\",\n"
" \"Monroe\", \"Wilson\", \"Harrison\", \"Cleveland\", \"Grant\", \"Lee\", \"King\", \"Queen\", \"Duke\", \"Prince\",\n"
" \"Cherry\", \"Walnut\", \"Chestnut\", \"Birch\", \"Willow\", \"Poplar\", \"Spruce\", \"Hickory\", \"Ash\", \"Beech\",\n"
" \"Summit\", \"Ridge\", \"Crest\", \"Mount\", \"Valley\", \"Canyon\", \"Creek\", \"Brook\", \"Falls\", \"Grove\",\n"
" \"Orchard\", \"Garden\", \"Field\", \"Woods\", \"Acres\", \"Estate\", \"Manor\", \"Villa\", \"Terrace\", \"Glen\",\n"
" \"Bay\", \"Harbor\", \"Shore\", \"Beach\", \"Ocean\", \"Sea\", \"Coral\", \"Palm\", \"Sunset\", \"Sunrise\",\n"
" \"Colonial\", \"Liberty\", \"Independence\", \"Victory\", \"Heritage\", \"Pioneer\", \"Frontier\", \"Commerce\", \"Industrial\", \"Technology\"\n"
" ]\n"
" }\n"
"\n"
" static companyPrefixes {\n"
" return [\n"
" \"Global\", \"United\", \"International\", \"National\", \"American\", \"Pacific\", \"Atlantic\", \"Northern\", \"Southern\",\n"
" \"Western\", \"Eastern\", \"Central\", \"Premier\", \"Prime\", \"Elite\", \"Apex\", \"Summit\", \"Peak\", \"Pinnacle\",\n"
" \"First\", \"Advanced\", \"Modern\", \"Future\", \"Next\", \"New\", \"Innovative\", \"Dynamic\", \"Strategic\", \"Smart\",\n"
" \"Digital\", \"Tech\", \"Cyber\", \"Data\", \"Cloud\", \"Net\", \"Web\", \"Quantum\", \"Fusion\", \"Synergy\"\n"
" ]\n"
" }\n"
"\n"
" static companySuffixes {\n"
" return [\n"
" \"Inc.\", \"LLC\", \"Corp.\", \"Ltd.\", \"Co.\", \"Group\", \"Holdings\", \"Industries\", \"Enterprises\", \"Solutions\",\n"
" \"Services\", \"Systems\", \"Technologies\", \"Partners\", \"Associates\", \"Consulting\", \"International\", \"Global\", \"Worldwide\"\n"
" ]\n"
" }\n"
"\n"
" static companyNouns {\n"
" return [\n"
" \"Tech\", \"Systems\", \"Solutions\", \"Data\", \"Software\", \"Digital\", \"Media\", \"Networks\", \"Communications\", \"Industries\",\n"
" \"Dynamics\", \"Logic\", \"Works\", \"Labs\", \"Innovations\", \"Ventures\", \"Capital\", \"Resources\", \"Management\", \"Consulting\"\n"
" ]\n"
" }\n"
"\n"
" static jobTitles {\n"
" return [\n"
" \"Software Engineer\", \"Product Manager\", \"Data Scientist\", \"Marketing Manager\", \"Sales Representative\",\n"
" \"Financial Analyst\", \"Human Resources Manager\", \"Operations Manager\", \"Project Manager\", \"Business Analyst\",\n"
" \"Accountant\", \"Graphic Designer\", \"Content Writer\", \"Customer Service Representative\", \"Administrative Assistant\",\n"
" \"Executive Assistant\", \"Office Manager\", \"Quality Assurance Engineer\", \"DevOps Engineer\", \"System Administrator\",\n"
" \"Network Engineer\", \"Database Administrator\", \"Security Analyst\", \"UX Designer\", \"UI Developer\",\n"
" \"Full Stack Developer\", \"Frontend Developer\", \"Backend Developer\", \"Mobile Developer\", \"Cloud Architect\",\n"
" \"Solutions Architect\", \"Technical Lead\", \"Engineering Manager\", \"Director of Engineering\", \"VP of Engineering\",\n"
" \"Chief Technology Officer\", \"Chief Executive Officer\", \"Chief Financial Officer\", \"Chief Operating Officer\", \"Chief Marketing Officer\"\n"
" ]\n"
" }\n"
"\n"
" static jobDescriptors {\n"
" return [\"Lead\", \"Senior\", \"Junior\", \"Associate\", \"Principal\", \"Staff\", \"Chief\", \"Head\", \"Director\", \"Manager\"]\n"
" }\n"
"\n"
" static productCategories {\n"
" return [\n"
" \"Electronics\", \"Clothing\", \"Home & Garden\", \"Sports & Outdoors\", \"Books\", \"Toys & Games\",\n"
" \"Health & Beauty\", \"Automotive\", \"Food & Grocery\", \"Office Supplies\", \"Pet Supplies\", \"Jewelry\",\n"
" \"Music\", \"Movies\", \"Software\", \"Tools & Hardware\", \"Baby Products\", \"Furniture\", \"Art & Crafts\", \"Industrial\"\n"
" ]\n"
" }\n"
"\n"
" static productAdjectives {\n"
" return [\n"
" \"Incredible\", \"Fantastic\", \"Amazing\", \"Gorgeous\", \"Practical\", \"Sleek\", \"Elegant\", \"Rustic\", \"Modern\",\n"
" \"Handmade\", \"Refined\", \"Premium\", \"Ergonomic\", \"Intelligent\", \"Smart\", \"Licensed\", \"Recycled\", \"Unbranded\", \"Generic\", \"Tasty\"\n"
" ]\n"
" }\n"
"\n"
" static productMaterials {\n"
" return [\n"
" \"Steel\", \"Wooden\", \"Concrete\", \"Plastic\", \"Cotton\", \"Granite\", \"Rubber\", \"Metal\", \"Soft\", \"Fresh\",\n"
" \"Frozen\", \"Bronze\", \"Silver\", \"Gold\", \"Copper\", \"Marble\", \"Leather\", \"Silk\", \"Wool\", \"Linen\"\n"
" ]\n"
" }\n"
"\n"
" static productNouns {\n"
" return [\n"
" \"Chair\", \"Car\", \"Computer\", \"Keyboard\", \"Mouse\", \"Bike\", \"Ball\", \"Gloves\", \"Pants\", \"Shirt\",\n"
" \"Table\", \"Shoes\", \"Hat\", \"Towels\", \"Soap\", \"Tuna\", \"Chicken\", \"Fish\", \"Cheese\", \"Bacon\",\n"
" \"Pizza\", \"Salad\", \"Sausages\", \"Chips\", \"Watch\", \"Phone\", \"Tablet\", \"Camera\", \"Lamp\", \"Clock\"\n"
" ]\n"
" }\n"
"\n"
" static creditCardTypes {\n"
" return [\n"
" [\"Visa\", \"4\"],\n"
" [\"Mastercard\", \"5\"],\n"
" [\"American Express\", \"3\"],\n"
" [\"Discover\", \"6\"]\n"
" ]\n"
" }\n"
"\n"
" static currencies {\n"
" return [\n"
" [\"USD\", \"US Dollar\", \"$\"], [\"EUR\", \"Euro\", \"€\"], [\"GBP\", \"British Pound\", \"£\"],\n"
" [\"JPY\", \"Japanese Yen\", \"¥\"], [\"AUD\", \"Australian Dollar\", \"A$\"], [\"CAD\", \"Canadian Dollar\", \"C$\"],\n"
" [\"CHF\", \"Swiss Franc\", \"CHF\"], [\"CNY\", \"Chinese Yuan\", \"¥\"], [\"INR\", \"Indian Rupee\", \"₹\"],\n"
" [\"MXN\", \"Mexican Peso\", \"$\"], [\"BRL\", \"Brazilian Real\", \"R$\"], [\"RUB\", \"Russian Ruble\", \"₽\"]\n"
" ]\n"
" }\n"
"\n"
" static colorNames {\n"
" return [\n"
" \"Red\", \"Blue\", \"Green\", \"Yellow\", \"Orange\", \"Purple\", \"Pink\", \"Brown\", \"Black\", \"White\",\n"
" \"Gray\", \"Cyan\", \"Magenta\", \"Lime\", \"Maroon\", \"Navy\", \"Olive\", \"Teal\", \"Aqua\", \"Silver\",\n"
" \"Crimson\", \"Coral\", \"Salmon\", \"Tomato\", \"Gold\", \"Khaki\", \"Indigo\", \"Violet\", \"Plum\", \"Orchid\",\n"
" \"Tan\", \"Beige\", \"Ivory\", \"Lavender\", \"Mint\", \"Turquoise\", \"Azure\", \"Slate\", \"Charcoal\", \"Pearl\"\n"
" ]\n"
" }\n"
"\n"
" static months {\n"
" return [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"]\n"
" }\n"
"\n"
" static daysOfWeek {\n"
" return [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]\n"
" }\n"
"\n"
" static topLevelDomains {\n"
" return [\"com\", \"org\", \"net\", \"edu\", \"gov\", \"io\", \"co\", \"biz\", \"info\", \"me\", \"us\", \"uk\", \"de\", \"fr\", \"jp\"]\n"
" }\n"
"\n"
" static httpMethods {\n"
" return [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"HEAD\", \"OPTIONS\"]\n"
" }\n"
"\n"
" static protocols {\n"
" return [\"http\", \"https\"]\n"
" }\n"
"\n"
" static userAgents {\n"
" return [\n"
" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\",\n"
" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\",\n"
" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0\",\n"
" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15\",\n"
" \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\",\n"
" \"Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1\"\n"
" ]\n"
" }\n"
"}\n"
"\n"
"class Faker {\n"
" static seed(value) { FakerRandom.seed(value) }\n"
" static reset() { FakerRandom.reset() }\n"
"\n"
" static randomElement(list) {\n"
" if (list.count == 0) return null\n"
" return list[FakerRandom.next(0, list.count - 1)]\n"
" }\n"
"\n"
" static randomElements(list, count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(randomElement(list))\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static randomInt(min, max) { FakerRandom.next(min, max) }\n"
"\n"
" static randomFloat(min, max) {\n"
" var f = FakerRandom.nextFloat()\n"
" return min + (f * (max - min))\n"
" }\n"
"\n"
" static randomFloat(min, max, precision) {\n"
" var f = randomFloat(min, max)\n"
" var mult = 1\n"
" for (i in 0...precision) mult = mult * 10\n"
" return (f * mult).round / mult\n"
" }\n"
"\n"
" static boolean() { FakerRandom.next(0, 1) == 1 }\n"
" static bool() { boolean() }\n"
"\n"
" static numerify(format) {\n"
" var result = \"\"\n"
" for (char in format) {\n"
" if (char == \"#\") {\n"
" result = result + FakerRandom.next(0, 9).toString\n"
" } else {\n"
" result = result + char\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static letterify(format) {\n"
" var letters = \"abcdefghijklmnopqrstuvwxyz\"\n"
" var result = \"\"\n"
" for (char in format) {\n"
" if (char == \"?\") {\n"
" result = result + letters[FakerRandom.next(0, 25)]\n"
" } else {\n"
" result = result + char\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static bothify(format) { letterify(numerify(format)) }\n"
"\n"
" static firstName() { randomElement(FakerData.firstNames) }\n"
" static firstNameMale() { randomElement(FakerData.firstNamesMale) }\n"
" static firstNameFemale() { randomElement(FakerData.firstNamesFemale) }\n"
" static lastName() { randomElement(FakerData.lastNames) }\n"
" static name() { \"%(firstName()) %(lastName())\" }\n"
" static nameMale() { \"%(firstNameMale()) %(lastName())\" }\n"
" static nameFemale() { \"%(firstNameFemale()) %(lastName())\" }\n"
"\n"
" static prefix() { randomElement([\"Mr.\", \"Mrs.\", \"Ms.\", \"Miss\", \"Dr.\"]) }\n"
" static namePrefix() { prefix() }\n"
" static suffix() { randomElement([\"Jr.\", \"Sr.\", \"I\", \"II\", \"III\", \"IV\", \"V\", \"MD\", \"PhD\"]) }\n"
" static nameSuffix() { suffix() }\n"
"\n"
" static gender() { randomElement([\"Male\", \"Female\"]) }\n"
" static sex() { gender() }\n"
"\n"
" static username() {\n"
" var first = Str.toLower(firstName())\n"
" var last = Str.toLower(lastName())\n"
" var patterns = [\n"
" \"%(first)%(last)\",\n"
" \"%(first).%(last)\",\n"
" \"%(first)_%(last)\",\n"
" \"%(first)%(FakerRandom.next(1, 99))\",\n"
" \"%(first).%(last)%(FakerRandom.next(1, 99))\",\n"
" \"%(first)_%(FakerRandom.next(100, 999))\"\n"
" ]\n"
" return randomElement(patterns)\n"
" }\n"
"\n"
" static email() {\n"
" var user = username()\n"
" var domain = randomElement(FakerData.emailDomains)\n"
" return \"%(user)@%(domain)\"\n"
" }\n"
"\n"
" static exampleEmail() {\n"
" var user = username()\n"
" var domain = randomElement([\"example.com\", \"example.org\", \"example.net\"])\n"
" return \"%(user)@%(domain)\"\n"
" }\n"
"\n"
" static city() { randomElement(FakerData.cities) }\n"
"\n"
" static state() {\n"
" var s = randomElement(FakerData.states)\n"
" return s[0]\n"
" }\n"
"\n"
" static stateAbbr() {\n"
" var s = randomElement(FakerData.states)\n"
" return s[1]\n"
" }\n"
"\n"
" static stateFull() { state() }\n"
" static stateName() { state() }\n"
"\n"
" static country() {\n"
" var c = randomElement(FakerData.countries)\n"
" return c[0]\n"
" }\n"
"\n"
" static countryCode() {\n"
" var c = randomElement(FakerData.countries)\n"
" return c[1]\n"
" }\n"
"\n"
" static countryName() { country() }\n"
"\n"
" static zipCode() { numerify(\"#####\") }\n"
" static postcode() { zipCode() }\n"
"\n"
" static buildingNumber() { FakerRandom.next(1, 9999).toString }\n"
"\n"
" static streetName() {\n"
" var name = randomElement(FakerData.streetNames)\n"
" var suffix = randomElement(FakerData.streetSuffixes)\n"
" return \"%(name) %(suffix)\"\n"
" }\n"
"\n"
" static streetAddress() { \"%(buildingNumber()) %(streetName())\" }\n"
"\n"
" static address() {\n"
" return \"%(streetAddress()), %(city()), %(stateAbbr()) %(zipCode())\"\n"
" }\n"
"\n"
" static latitude() { randomFloat(-90, 90, 6) }\n"
" static longitude() { randomFloat(-180, 180, 6) }\n"
"\n"
" static latitude(min, max) { randomFloat(min, max, 6) }\n"
" static longitude(min, max) { randomFloat(min, max, 6) }\n"
"\n"
" static ipv4() {\n"
" var a = FakerRandom.next(1, 255)\n"
" var b = FakerRandom.next(0, 255)\n"
" var c = FakerRandom.next(0, 255)\n"
" var d = FakerRandom.next(1, 254)\n"
" return \"%(a).%(b).%(c).%(d)\"\n"
" }\n"
"\n"
" static ipv6() {\n"
" var parts = []\n"
" for (i in 0...8) {\n"
" var hex = \"\"\n"
" for (j in 0...4) {\n"
" var digit = FakerRandom.next(0, 15)\n"
" hex = hex + \"0123456789abcdef\"[digit]\n"
" }\n"
" parts.add(hex)\n"
" }\n"
" return parts.join(\":\")\n"
" }\n"
"\n"
" static macAddress() {\n"
" var parts = []\n"
" for (i in 0...6) {\n"
" var hex = \"\"\n"
" for (j in 0...2) {\n"
" var digit = FakerRandom.next(0, 15)\n"
" hex = hex + \"0123456789abcdef\"[digit]\n"
" }\n"
" parts.add(hex)\n"
" }\n"
" return parts.join(\":\")\n"
" }\n"
"\n"
" static port() { FakerRandom.next(1, 65535) }\n"
"\n"
" static tld() { randomElement(FakerData.topLevelDomains) }\n"
" static topLevelDomain() { tld() }\n"
"\n"
" static domainWord() {\n"
" var words = [Str.toLower(firstName()), Str.toLower(lastName()), Str.toLower(randomElement(FakerData.companyNouns))]\n"
" return randomElement(words)\n"
" }\n"
"\n"
" static domainName() { \"%(domainWord()).%(tld())\" }\n"
"\n"
" static protocol() { randomElement(FakerData.protocols) }\n"
"\n"
" static url() { \"%(protocol())://%(domainName())\" }\n"
"\n"
" static password() { password(12) }\n"
"\n"
" static password(length) {\n"
" var chars = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#^&*-_+=\"\n"
" var result = \"\"\n"
" for (i in 0...length) {\n"
" result = result + chars[FakerRandom.next(0, chars.count - 1)]\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static httpMethod() { randomElement(FakerData.httpMethods) }\n"
" static httpStatusCode() { randomElement([200, 201, 204, 301, 302, 400, 401, 403, 404, 500, 502, 503]) }\n"
" static userAgent() { randomElement(FakerData.userAgents) }\n"
" static hostname() { \"%(domainWord())-%(FakerRandom.next(1, 99)).%(domainName())\" }\n"
"\n"
" static uuid() { Uuid.v4() }\n"
"\n"
" static md5() { Hash.toHex(Hash.md5(password(32))) }\n"
" static sha1() { Hash.toHex(Hash.sha1(password(32))) }\n"
" static sha256() { Hash.toHex(Hash.sha256(password(32))) }\n"
"\n"
" static hexColor() {\n"
" var hex = \"\"\n"
" for (i in 0...6) {\n"
" var digit = FakerRandom.next(0, 15)\n"
" hex = hex + \"0123456789abcdef\"[digit]\n"
" }\n"
" return \"#\" + hex\n"
" }\n"
"\n"
" static rgbColor() {\n"
" var r = FakerRandom.next(0, 255)\n"
" var g = FakerRandom.next(0, 255)\n"
" var b = FakerRandom.next(0, 255)\n"
" return \"rgb(%(r), %(g), %(b))\"\n"
" }\n"
"\n"
" static rgb() { rgbColor() }\n"
"\n"
" static rgbaCssColor() {\n"
" var r = FakerRandom.next(0, 255)\n"
" var g = FakerRandom.next(0, 255)\n"
" var b = FakerRandom.next(0, 255)\n"
" var a = randomFloat(0, 1, 2)\n"
" return \"rgba(%(r), %(g), %(b), %(a))\"\n"
" }\n"
"\n"
" static colorName() { randomElement(FakerData.colorNames) }\n"
"\n"
" static date() {\n"
" var now = DateTime.now()\n"
" var start = now - Duration.fromDays(365 * 10)\n"
" var end = now\n"
" return dateBetween(start, end)\n"
" }\n"
"\n"
" static dateTime() {\n"
" var now = DateTime.now()\n"
" var start = now - Duration.fromDays(365 * 10)\n"
" var range = 365 * 10 * 24 * 60 * 60\n"
" var offset = FakerRandom.next(0, range)\n"
" var dt = start + Duration.fromSeconds(offset)\n"
" return dt.toIso8601\n"
" }\n"
"\n"
" static pastDate() { pastDate(1) }\n"
"\n"
" static pastDate(years) {\n"
" var now = DateTime.now()\n"
" var start = now - Duration.fromDays(365 * years)\n"
" return dateBetween(start, now)\n"
" }\n"
"\n"
" static futureDate() { futureDate(1) }\n"
"\n"
" static futureDate(years) {\n"
" var now = DateTime.now()\n"
" var end = now + Duration.fromDays(365 * years)\n"
" return dateBetween(now, end)\n"
" }\n"
"\n"
" static dateBetween(start, end) {\n"
" var startTs = start.timestamp\n"
" var endTs = end.timestamp\n"
" if (endTs <= startTs) return start.format(\"\\%Y-\\%m-\\%d\")\n"
" var range = endTs - startTs\n"
" var offset = FakerRandom.next(0, range.floor)\n"
" var dt = DateTime.fromTimestamp(startTs + offset)\n"
" return dt.format(\"\\%Y-\\%m-\\%d\")\n"
" }\n"
"\n"
" static dateOfBirth() { dateOfBirth(18, 80) }\n"
"\n"
" static dateOfBirth(minAge, maxAge) {\n"
" var now = DateTime.now()\n"
" var end = now - Duration.fromDays(365 * minAge)\n"
" var start = now - Duration.fromDays(365 * maxAge)\n"
" return dateBetween(start, end)\n"
" }\n"
"\n"
" static year() { FakerRandom.next(1950, 2025) }\n"
" static month() { FakerRandom.next(1, 12) }\n"
" static monthName() { FakerData.months[FakerRandom.next(0, 11)] }\n"
" static dayOfWeek() { randomElement(FakerData.daysOfWeek) }\n"
" static dayOfMonth() { FakerRandom.next(1, 28) }\n"
" static hour() { FakerRandom.next(0, 23) }\n"
" static minute() { FakerRandom.next(0, 59) }\n"
" static second() { FakerRandom.next(0, 59) }\n"
" static amPm() { randomElement([\"AM\", \"PM\"]) }\n"
"\n"
" static time() {\n"
" var h = hour()\n"
" var m = minute()\n"
" var s = second()\n"
" return \"%(h < 10 ? \"0\" : \"\")%(h):%(m < 10 ? \"0\" : \"\")%(m):%(s < 10 ? \"0\" : \"\")%(s)\"\n"
" }\n"
"\n"
" static word() { randomElement(FakerData.loremWords) }\n"
"\n"
" static words(count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(word())\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static sentence() { sentence(FakerRandom.next(5, 12)) }\n"
"\n"
" static sentence(wordCount) {\n"
" var w = words(wordCount)\n"
" var first = w[0]\n"
" w[0] = first[0].bytes[0] >= 97 && first[0].bytes[0] <= 122 ? String.fromCodePoint(first[0].bytes[0] - 32) + first[1..-1] : first\n"
" return w.join(\" \") + \".\"\n"
" }\n"
"\n"
" static sentences(count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(sentence())\n"
" }\n"
" return result.join(\" \")\n"
" }\n"
"\n"
" static paragraph() { paragraph(FakerRandom.next(3, 6)) }\n"
"\n"
" static paragraph(sentenceCount) { sentences(sentenceCount) }\n"
"\n"
" static paragraphs(count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(paragraph())\n"
" }\n"
" return result.join(\"\\n\\n\")\n"
" }\n"
"\n"
" static text() { text(200) }\n"
"\n"
" static text(maxChars) {\n"
" var result = \"\"\n"
" while (result.count < maxChars) {\n"
" result = result + sentence() + \" \"\n"
" }\n"
" return result[0...maxChars]\n"
" }\n"
"\n"
" static slug() { slug(3) }\n"
"\n"
" static slug(wordCount) {\n"
" var w = words(wordCount)\n"
" var lower = []\n"
" for (word in w) {\n"
" lower.add(Str.toLower(word))\n"
" }\n"
" return lower.join(\"-\")\n"
" }\n"
"\n"
" static company() {\n"
" var patterns = [\n"
" \"%(lastName()) %(randomElement(FakerData.companySuffixes))\",\n"
" \"%(lastName())-%(lastName())\",\n"
" \"%(randomElement(FakerData.companyPrefixes)) %(randomElement(FakerData.companyNouns))\",\n"
" \"%(lastName()) %(randomElement(FakerData.companyNouns))\"\n"
" ]\n"
" return randomElement(patterns)\n"
" }\n"
"\n"
" static companyName() { company() }\n"
" static companySuffix() { randomElement(FakerData.companySuffixes) }\n"
"\n"
" static job() { randomElement(FakerData.jobTitles) }\n"
" static jobTitle() { job() }\n"
" static jobDescriptor() { randomElement(FakerData.jobDescriptors) }\n"
"\n"
" static product() {\n"
" var adj = randomElement(FakerData.productAdjectives)\n"
" var mat = randomElement(FakerData.productMaterials)\n"
" var noun = randomElement(FakerData.productNouns)\n"
" return \"%(adj) %(mat) %(noun)\"\n"
" }\n"
"\n"
" static productName() { product() }\n"
" static productCategory() { randomElement(FakerData.productCategories) }\n"
"\n"
" static price() { price(1, 1000) }\n"
" static price(min, max) { randomFloat(min, max, 2) }\n"
"\n"
" static currency() {\n"
" var c = randomElement(FakerData.currencies)\n"
" return c[0]\n"
" }\n"
"\n"
" static currencyName() {\n"
" var c = randomElement(FakerData.currencies)\n"
" return c[1]\n"
" }\n"
"\n"
" static currencySymbol() {\n"
" var c = randomElement(FakerData.currencies)\n"
" return c[2]\n"
" }\n"
"\n"
" static creditCardType() {\n"
" var c = randomElement(FakerData.creditCardTypes)\n"
" return c[0]\n"
" }\n"
"\n"
" static creditCardNumber() {\n"
" var type = randomElement(FakerData.creditCardTypes)\n"
" var prefix = type[1]\n"
" return prefix + numerify(\"###-####-####-###\")\n"
" }\n"
"\n"
" static creditCardCVV() { numerify(\"###\") }\n"
"\n"
" static creditCardExpiryDate() {\n"
" var m = FakerRandom.next(1, 12)\n"
" var y = FakerRandom.next(25, 30)\n"
" return \"%(m < 10 ? \"0\" : \"\")%(m)/%(y)\"\n"
" }\n"
"\n"
" static phoneNumber() { numerify(\"(###) ###-####\") }\n"
" static phone() { phoneNumber() }\n"
"\n"
" static iban() {\n"
" var country = randomElement([\"DE\", \"FR\", \"GB\", \"ES\", \"IT\", \"NL\"])\n"
" var check = numerify(\"##\")\n"
" var bban = numerify(\"####################\")\n"
" return country + check + bban\n"
" }\n"
"\n"
" static accountNumber() { accountNumber(10) }\n"
" static accountNumber(length) {\n"
" var result = \"\"\n"
" for (i in 0...length) {\n"
" result = result + FakerRandom.next(0, 9).toString\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static routingNumber() { numerify(\"#########\") }\n"
"\n"
" static fileName() {\n"
" var extensions = [\"txt\", \"pdf\", \"doc\", \"xls\", \"jpg\", \"png\", \"gif\", \"mp3\", \"mp4\", \"zip\"]\n"
" var name = Str.toLower(word())\n"
" var ext = randomElement(extensions)\n"
" return \"%(name).%(ext)\"\n"
" }\n"
"\n"
" static fileExtension() { randomElement([\"txt\", \"pdf\", \"doc\", \"xls\", \"jpg\", \"png\", \"gif\", \"mp3\", \"mp4\", \"zip\"]) }\n"
"\n"
" static mimeType() {\n"
" return randomElement([\n"
" \"application/json\", \"application/xml\", \"application/pdf\", \"application/zip\",\n"
" \"text/html\", \"text/plain\", \"text/css\", \"text/javascript\",\n"
" \"image/jpeg\", \"image/png\", \"image/gif\", \"image/svg+xml\",\n"
" \"audio/mpeg\", \"video/mp4\"\n"
" ])\n"
" }\n"
"\n"
" static semver() { \"%(FakerRandom.next(0, 9)).%(FakerRandom.next(0, 20)).%(FakerRandom.next(0, 50))\" }\n"
"\n"
" static digit() { FakerRandom.next(0, 9) }\n"
" static randomDigit() { digit() }\n"
" static digits(count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(digit())\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static letter() { \"abcdefghijklmnopqrstuvwxyz\"[FakerRandom.next(0, 25)] }\n"
" static letters(count) {\n"
" var result = \"\"\n"
" for (i in 0...count) {\n"
" result = result + letter()\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static shuffle(list) {\n"
" var result = list.toList\n"
" for (i in (result.count - 1)..1) {\n"
" var j = FakerRandom.next(0, i)\n"
" var temp = result[i]\n"
" result[i] = result[j]\n"
" result[j] = temp\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static profile() {\n"
" return {\n"
" \"username\": username(),\n"
" \"name\": name(),\n"
" \"email\": email(),\n"
" \"address\": address(),\n"
" \"phone\": phoneNumber(),\n"
" \"job\": jobTitle(),\n"
" \"company\": company(),\n"
" \"birthdate\": dateOfBirth()\n"
" }\n"
" }\n"
"\n"
" static simpleProfile() {\n"
" return {\n"
" \"username\": username(),\n"
" \"name\": name(),\n"
" \"email\": email(),\n"
" \"address\": address()\n"
" }\n"
" }\n"
"\n"
" static locale() { \"en_US\" }\n"
"}\n";
+450
View File
@@ -1,13 +1,30 @@
// retoor <retoor@molodetz.nl>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include "subprocess.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
#ifdef __linux__
#include <pty.h>
#include <termios.h>
#include <sys/ioctl.h>
#elif defined(__APPLE__) || defined(__FreeBSD__)
#include <util.h>
#include <termios.h>
#include <sys/ioctl.h>
#endif
#define STREAM_STDIN 0
#define STREAM_STDOUT 1
#define STREAM_STDERR 2
typedef struct {
WrenHandle* fiber;
uv_process_t process;
@@ -23,6 +40,38 @@ typedef struct {
int handlesOpen;
} ProcessData;
typedef struct StreamData {
uv_pipe_t pipe;
WrenHandle* readFiber;
char* buffer;
size_t bufLen;
size_t bufCap;
bool isOpen;
bool isReadable;
bool isWritable;
bool readPending;
struct PopenData* parent;
} StreamData;
typedef struct PopenData {
uv_process_t process;
WrenHandle* waitFiber;
StreamData streams[3];
int64_t exitCode;
bool exited;
bool usePty;
int ptyMaster;
int handlesOpen;
int pid;
} PopenData;
typedef struct {
uv_write_t req;
uv_buf_t buf;
WrenHandle* fiber;
StreamData* stream;
} WriteRequest;
static void appendBuffer(char** buf, size_t* len, size_t* cap, const char* data, size_t dataLen) {
if (*len + dataLen >= *cap) {
size_t newCap = (*cap == 0) ? 1024 : *cap * 2;
@@ -182,3 +231,404 @@ void subprocessRun(WrenVM* vm) {
uv_read_start((uv_stream_t*)&data->stdoutPipe, allocBuffer, onStdoutRead);
uv_read_start((uv_stream_t*)&data->stderrPipe, allocBuffer, onStderrRead);
}
static void onPopenProcessExit(uv_process_t* process, int64_t exitStatus, int termSignal) {
PopenData* data = (PopenData*)process->data;
data->exitCode = termSignal ? 128 + termSignal : exitStatus;
data->exited = true;
if (data->waitFiber != NULL) {
WrenVM* vm = getVM();
WrenHandle* fiber = data->waitFiber;
data->waitFiber = NULL;
wrenEnsureSlots(vm, 3);
wrenSetSlotDouble(vm, 2, (double)data->exitCode);
schedulerResume(fiber, true);
schedulerFinishResume();
}
}
static void onPopenStreamRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
StreamData* sdata = (StreamData*)stream->data;
if (nread > 0) {
appendBuffer(&sdata->buffer, &sdata->bufLen, &sdata->bufCap, buf->base, nread);
}
if (buf->base != NULL) free(buf->base);
if (nread < 0 || (sdata->readPending && sdata->bufLen > 0)) {
uv_read_stop(stream);
if (nread < 0 && nread != UV_EOF) {
sdata->isOpen = false;
}
if (sdata->readFiber != NULL) {
WrenVM* vm = getVM();
WrenHandle* fiber = sdata->readFiber;
sdata->readFiber = NULL;
sdata->readPending = false;
wrenEnsureSlots(vm, 3);
if (sdata->buffer != NULL) {
wrenSetSlotString(vm, 2, sdata->buffer);
free(sdata->buffer);
sdata->buffer = NULL;
sdata->bufLen = 0;
sdata->bufCap = 0;
} else {
wrenSetSlotString(vm, 2, "");
}
schedulerResume(fiber, true);
schedulerFinishResume();
}
}
}
static void onPopenWriteComplete(uv_write_t* req, int status) {
WriteRequest* wreq = (WriteRequest*)req;
WrenHandle* fiber = wreq->fiber;
free(wreq->buf.base);
free(wreq);
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 3);
if (status < 0) {
wrenSetSlotBool(vm, 2, false);
} else {
wrenSetSlotBool(vm, 2, true);
}
schedulerResume(fiber, true);
schedulerFinishResume();
}
void popenAllocate(WrenVM* vm) {
PopenData* data = (PopenData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(PopenData));
memset(data, 0, sizeof(PopenData));
int argCount = wrenGetListCount(vm, 1);
if (argCount == 0) {
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
char** args = (char**)malloc((argCount + 1) * sizeof(char*));
if (args == NULL) {
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
for (int i = 0; i < argCount; i++) {
wrenGetListElement(vm, 1, i, 2);
const char* arg = wrenGetSlotString(vm, 2);
args[i] = strdup(arg);
}
args[argCount] = NULL;
bool usePty = false;
if (wrenGetSlotCount(vm) > 2 && wrenGetSlotType(vm, 2) == WREN_TYPE_BOOL) {
usePty = wrenGetSlotBool(vm, 2);
}
data->usePty = usePty;
uv_loop_t* loop = getLoop();
for (int i = 0; i < 3; i++) {
data->streams[i].parent = data;
data->streams[i].isOpen = false;
}
if (usePty) {
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
int master, slave;
struct winsize ws = { .ws_row = 24, .ws_col = 80 };
if (openpty(&master, &slave, NULL, NULL, &ws) < 0) {
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
data->ptyMaster = master;
uv_pipe_init(loop, &data->streams[STREAM_STDIN].pipe, 0);
uv_pipe_open(&data->streams[STREAM_STDIN].pipe, master);
data->streams[STREAM_STDIN].pipe.data = &data->streams[STREAM_STDIN];
data->streams[STREAM_STDIN].isOpen = true;
data->streams[STREAM_STDIN].isWritable = true;
data->streams[STREAM_STDIN].isReadable = true;
data->streams[STREAM_STDOUT].isOpen = true;
data->streams[STREAM_STDOUT].isReadable = true;
data->handlesOpen = 1;
uv_stdio_container_t stdio[3];
stdio[0].flags = UV_INHERIT_FD;
stdio[0].data.fd = slave;
stdio[1].flags = UV_INHERIT_FD;
stdio[1].data.fd = slave;
stdio[2].flags = UV_INHERIT_FD;
stdio[2].data.fd = slave;
uv_process_options_t options;
memset(&options, 0, sizeof(options));
options.file = args[0];
options.args = args;
options.stdio = stdio;
options.stdio_count = 3;
options.exit_cb = onPopenProcessExit;
data->process.data = data;
int result = uv_spawn(loop, &data->process, &options);
close(slave);
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
if (result != 0) {
close(master);
data->streams[STREAM_STDIN].isOpen = false;
data->streams[STREAM_STDOUT].isOpen = false;
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
data->pid = data->process.pid;
data->handlesOpen++;
#else
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
#endif
} else {
uv_pipe_init(loop, &data->streams[STREAM_STDIN].pipe, 0);
uv_pipe_init(loop, &data->streams[STREAM_STDOUT].pipe, 0);
uv_pipe_init(loop, &data->streams[STREAM_STDERR].pipe, 0);
data->streams[STREAM_STDIN].pipe.data = &data->streams[STREAM_STDIN];
data->streams[STREAM_STDOUT].pipe.data = &data->streams[STREAM_STDOUT];
data->streams[STREAM_STDERR].pipe.data = &data->streams[STREAM_STDERR];
data->streams[STREAM_STDIN].isOpen = true;
data->streams[STREAM_STDIN].isWritable = true;
data->streams[STREAM_STDOUT].isOpen = true;
data->streams[STREAM_STDOUT].isReadable = true;
data->streams[STREAM_STDERR].isOpen = true;
data->streams[STREAM_STDERR].isReadable = true;
data->handlesOpen = 3;
uv_stdio_container_t stdio[3];
stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
stdio[0].data.stream = (uv_stream_t*)&data->streams[STREAM_STDIN].pipe;
stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
stdio[1].data.stream = (uv_stream_t*)&data->streams[STREAM_STDOUT].pipe;
stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
stdio[2].data.stream = (uv_stream_t*)&data->streams[STREAM_STDERR].pipe;
uv_process_options_t options;
memset(&options, 0, sizeof(options));
options.file = args[0];
options.args = args;
options.stdio = stdio;
options.stdio_count = 3;
options.exit_cb = onPopenProcessExit;
data->process.data = data;
int result = uv_spawn(loop, &data->process, &options);
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
if (result != 0) {
data->handlesOpen = 0;
data->streams[STREAM_STDIN].isOpen = false;
data->streams[STREAM_STDOUT].isOpen = false;
data->streams[STREAM_STDERR].isOpen = false;
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
data->pid = data->process.pid;
data->handlesOpen++;
}
}
void popenFinalize(void* userData) {
}
void popenPid(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)data->pid);
}
void popenIsRunning(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
wrenSetSlotBool(vm, 0, !data->exited);
}
void popenWait(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
if (data->exited) {
wrenSetSlotDouble(vm, 0, (double)data->exitCode);
return;
}
data->waitFiber = wrenGetSlotHandle(vm, 1);
}
void popenKill(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int signal = (int)wrenGetSlotDouble(vm, 1);
if (!data->exited) {
uv_process_kill(&data->process, signal);
}
}
void popenReadStream(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int index = (int)wrenGetSlotDouble(vm, 1);
if (index < 0 || index > 2) {
wrenSetSlotString(vm, 0, "Invalid stream index.");
wrenAbortFiber(vm, 0);
return;
}
StreamData* stream = &data->streams[index];
if (!stream->isReadable) {
wrenSetSlotString(vm, 0, "Stream is not readable.");
wrenAbortFiber(vm, 0);
return;
}
if (!stream->isOpen) {
wrenSetSlotString(vm, 0, "");
return;
}
stream->readFiber = wrenGetSlotHandle(vm, 2);
stream->readPending = true;
if (data->usePty && index == STREAM_STDOUT) {
uv_read_start((uv_stream_t*)&data->streams[STREAM_STDIN].pipe, allocBuffer, onPopenStreamRead);
} else {
uv_read_start((uv_stream_t*)&stream->pipe, allocBuffer, onPopenStreamRead);
}
}
void popenWriteStream(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int index = (int)wrenGetSlotDouble(vm, 1);
const char* str = wrenGetSlotString(vm, 2);
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
if (index != STREAM_STDIN) {
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Can only write to stdin.");
wrenAbortFiber(vm, 0);
return;
}
StreamData* stream = &data->streams[STREAM_STDIN];
if (!stream->isOpen) {
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Stream is closed.");
wrenAbortFiber(vm, 0);
return;
}
size_t len = strlen(str);
WriteRequest* req = (WriteRequest*)malloc(sizeof(WriteRequest));
if (req == NULL) {
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
req->buf.base = (char*)malloc(len);
if (req->buf.base == NULL) {
free(req);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
memcpy(req->buf.base, str, len);
req->buf.len = len;
req->fiber = fiber;
req->stream = stream;
int result = uv_write(&req->req, (uv_stream_t*)&stream->pipe, &req->buf, 1, onPopenWriteComplete);
if (result != 0) {
free(req->buf.base);
free(req);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
}
}
static void onStreamClosed(uv_handle_t* handle) {
StreamData* stream = (StreamData*)handle->data;
stream->isOpen = false;
stream->parent->handlesOpen--;
}
void popenCloseStream(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int index = (int)wrenGetSlotDouble(vm, 1);
if (index < 0 || index > 2) {
return;
}
StreamData* stream = &data->streams[index];
if (!stream->isOpen) {
return;
}
if (data->usePty) {
if (index == STREAM_STDIN) {
stream->isOpen = false;
data->streams[STREAM_STDOUT].isOpen = false;
uv_close((uv_handle_t*)&stream->pipe, onStreamClosed);
}
} else {
stream->isOpen = false;
uv_close((uv_handle_t*)&stream->pipe, onStreamClosed);
}
}
void popenIsStreamOpen(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int index = (int)wrenGetSlotDouble(vm, 1);
if (index < 0 || index > 2) {
wrenSetSlotBool(vm, 0, false);
return;
}
wrenSetSlotBool(vm, 0, data->streams[index].isOpen);
}
+11
View File
@@ -7,4 +7,15 @@
void subprocessRun(WrenVM* vm);
void popenAllocate(WrenVM* vm);
void popenFinalize(void* data);
void popenPid(WrenVM* vm);
void popenIsRunning(WrenVM* vm);
void popenWait(WrenVM* vm);
void popenKill(WrenVM* vm);
void popenReadStream(WrenVM* vm);
void popenWriteStream(WrenVM* vm);
void popenCloseStream(WrenVM* vm);
void popenIsStreamOpen(WrenVM* vm);
#endif
+46
View File
@@ -45,3 +45,49 @@ class Subprocess {
return ProcessResult.new_(result[0], result[1], result[2])
}
}
class ProcessStream {
construct new_(popen, index) {
_popen = popen
_index = index
}
read() {
return Scheduler.await_ { _popen.readStream_(_index, Fiber.current) }
}
write(data) {
return Scheduler.await_ { _popen.writeStream_(_index, data, Fiber.current) }
}
close() { _popen.closeStream_(_index) }
isOpen { _popen.isStreamOpen_(_index) }
}
foreign class Popen {
construct new(args) {}
construct new(args, usePty) {}
stdin { ProcessStream.new_(this, 0) }
stdout { ProcessStream.new_(this, 1) }
stderr { ProcessStream.new_(this, 2) }
foreign pid
foreign isRunning
foreign wait_(fiber)
foreign kill_(signal)
foreign readStream_(index, fiber)
foreign writeStream_(index, data, fiber)
foreign closeStream_(index)
foreign isStreamOpen_(index)
wait() {
return Scheduler.await_ { wait_(Fiber.current) }
}
kill() { kill_(15) }
kill(signal) { kill_(signal) }
}
+46
View File
@@ -48,4 +48,50 @@ static const char* subprocessModuleSource =
" var result = Scheduler.runNextScheduled_()\n"
" return ProcessResult.new_(result[0], result[1], result[2])\n"
" }\n"
"}\n"
"\n"
"class ProcessStream {\n"
" construct new_(popen, index) {\n"
" _popen = popen\n"
" _index = index\n"
" }\n"
"\n"
" read() {\n"
" return Scheduler.await_ { _popen.readStream_(_index, Fiber.current) }\n"
" }\n"
"\n"
" write(data) {\n"
" return Scheduler.await_ { _popen.writeStream_(_index, data, Fiber.current) }\n"
" }\n"
"\n"
" close() { _popen.closeStream_(_index) }\n"
"\n"
" isOpen { _popen.isStreamOpen_(_index) }\n"
"}\n"
"\n"
"foreign class Popen {\n"
" construct new(args) {}\n"
" construct new(args, usePty) {}\n"
"\n"
" stdin { ProcessStream.new_(this, 0) }\n"
" stdout { ProcessStream.new_(this, 1) }\n"
" stderr { ProcessStream.new_(this, 2) }\n"
"\n"
" foreign pid\n"
" foreign isRunning\n"
"\n"
" foreign wait_(fiber)\n"
" foreign kill_(signal)\n"
"\n"
" foreign readStream_(index, fiber)\n"
" foreign writeStream_(index, data, fiber)\n"
" foreign closeStream_(index)\n"
" foreign isStreamOpen_(index)\n"
"\n"
" wait() {\n"
" return Scheduler.await_ { wait_(Fiber.current) }\n"
" }\n"
"\n"
" kill() { kill_(15) }\n"
" kill(signal) { kill_(signal) }\n"
"}\n";
+332
View File
@@ -10,6 +10,9 @@ import "uuid" for Uuid
import "datetime" for DateTime
import "io" for File
import "strutil" for Str
import "crypto" for Crypto, Hash
import "base64" for Base64
import "bytes" for Bytes
class Request {
construct new_(method, path, query, headers, body, params, socket) {
@@ -213,6 +216,302 @@ class Response {
}
}
class WebSocketResponse {
construct new() {
_socket = null
_isPrepared = false
_isOpen = false
_readBuffer = ""
_fragmentBuffer = []
_fragmentOpcode = null
}
isPrepared { _isPrepared }
isOpen { _isOpen }
prepare(request) {
if (_isPrepared) Fiber.abort("WebSocket already prepared.")
var key = request.header("Sec-WebSocket-Key")
if (key == null) Fiber.abort("Missing Sec-WebSocket-Key header.")
_socket = request.socket
var acceptKey = computeAcceptKey_(key)
var response = "HTTP/1.1 101 Switching Protocols\r\n"
response = response + "Upgrade: websocket\r\n"
response = response + "Connection: Upgrade\r\n"
response = response + "Sec-WebSocket-Accept: %(acceptKey)\r\n"
response = response + "\r\n"
_socket.write(response)
_isPrepared = true
_isOpen = true
return this
}
send(text) { sendText(text) }
sendText(text) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
if (!(text is String)) Fiber.abort("Data must be a string.")
var payload = stringToBytes_(text)
sendFrame_(1, payload)
}
sendBinary(bytes) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
if (!(bytes is List)) Fiber.abort("Data must be a list of bytes.")
sendFrame_(2, bytes)
}
ping() { ping([]) }
ping(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var payload = data
if (data is String) payload = stringToBytes_(data)
if (payload.count > 125) Fiber.abort("Ping payload too large (max 125 bytes).")
sendFrame_(9, payload)
}
pong(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var payload = data
if (data is String) payload = stringToBytes_(data)
if (payload.count > 125) Fiber.abort("Pong payload too large (max 125 bytes).")
sendFrame_(10, payload)
}
close() { close(1000, "") }
close(code, reason) {
if (!_isOpen) return
var payload = []
payload.add((code >> 8) & 0xFF)
payload.add(code & 0xFF)
for (b in stringToBytes_(reason)) {
payload.add(b)
}
sendFrame_(8, payload)
_isOpen = false
_socket.close()
}
receive() {
if (!_isOpen) return null
while (true) {
var frame = readFrame_()
if (frame == null) {
_isOpen = false
return null
}
var opcode = frame.opcode
var payload = frame.payload
var fin = frame.fin
if (opcode == 8) {
_isOpen = false
return frame
}
if (opcode == 9) {
sendFrame_(10, payload)
continue
}
if (opcode == 10) {
continue
}
if (opcode == 0) {
for (b in payload) {
_fragmentBuffer.add(b)
}
if (fin) {
var completePayload = _fragmentBuffer
var completeOpcode = _fragmentOpcode
_fragmentBuffer = []
_fragmentOpcode = null
return WebSocketMessage.new_(completeOpcode, completePayload, true)
}
continue
}
if (opcode == 1 || opcode == 2) {
if (fin) {
return frame
} else {
_fragmentOpcode = opcode
_fragmentBuffer = []
for (b in payload) {
_fragmentBuffer.add(b)
}
continue
}
}
Fiber.abort("Unknown opcode: %(opcode)")
}
}
iterate(callback) {
while (_isOpen) {
var msg = receive()
if (msg == null || msg.isClose) break
callback.call(msg)
}
}
sendJson(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var text = Json.stringify(data)
sendText(text)
}
receiveJson() {
var msg = receive()
if (msg == null) return null
if (msg.isClose) return msg
if (!msg.isText) Fiber.abort("Expected text frame for JSON, got binary.")
return Json.parse(msg.text)
}
receiveText() {
var msg = receive()
if (msg == null) return null
if (msg.isClose) return null
if (!msg.isText) return null
return msg.text
}
receiveBinary() {
var msg = receive()
if (msg == null) return null
if (msg.isClose) return null
if (!msg.isBinary) return null
return msg.bytes
}
sendFrame_(opcode, payload) {
var frame = encodeFrame_(opcode, payload)
_socket.write(Bytes.fromList(frame))
}
encodeFrame_(opcode, payload) {
var frame = []
frame.add(0x80 | opcode)
var len = payload.count
if (len < 126) {
frame.add(len)
} else if (len < 65536) {
frame.add(126)
frame.add((len >> 8) & 0xFF)
frame.add(len & 0xFF)
} else {
frame.add(127)
for (i in 0...4) frame.add(0)
frame.add((len >> 24) & 0xFF)
frame.add((len >> 16) & 0xFF)
frame.add((len >> 8) & 0xFF)
frame.add(len & 0xFF)
}
for (b in payload) frame.add(b)
return frame
}
readFrame_() {
var header = readBytes_(2)
if (header == null || Bytes.length(header) < 2) return null
var headerList = Bytes.toList(header)
var fin = (headerList[0] & 0x80) != 0
var opcode = headerList[0] & 0x0F
var masked = (headerList[1] & 0x80) != 0
var len = headerList[1] & 0x7F
if (len == 126) {
var ext = readBytes_(2)
if (ext == null || Bytes.length(ext) < 2) return null
var extList = Bytes.toList(ext)
len = (extList[0] << 8) | extList[1]
} else if (len == 127) {
var ext = readBytes_(8)
if (ext == null || Bytes.length(ext) < 8) return null
var extList = Bytes.toList(ext)
len = 0
for (i in 4...8) {
len = (len << 8) | extList[i]
}
}
var mask = null
if (masked) {
mask = readBytes_(4)
if (mask == null || Bytes.length(mask) < 4) return null
}
var payload = ""
if (len > 0) {
payload = readBytes_(len)
if (payload == null) return null
}
if (masked && mask != null) {
payload = Bytes.xorMask(payload, mask)
}
return WebSocketMessage.new_(opcode, Bytes.toList(payload), fin)
}
readBytes_(count) {
while (Bytes.length(_readBuffer) < count) {
var chunk = _socket.read()
if (chunk == null || chunk.bytes.count == 0) {
if (Bytes.length(_readBuffer) == 0) return null
var result = _readBuffer
_readBuffer = ""
return result
}
_readBuffer = Bytes.concat(_readBuffer, chunk)
}
var result = Bytes.slice(_readBuffer, 0, count)
_readBuffer = Bytes.slice(_readBuffer, count, Bytes.length(_readBuffer))
return result
}
computeAcceptKey_(key) {
var magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
var combined = key + magic
var sha1 = Hash.sha1(combined)
var sha1Str = bytesToString_(sha1)
return Base64.encode(sha1Str)
}
bytesToString_(bytes) {
var parts = []
for (b in bytes) {
parts.add(String.fromByte(b))
}
return parts.join("")
}
stringToBytes_(str) {
var bytes = []
for (b in str.bytes) {
bytes.add(b)
}
return bytes
}
}
class Session {
construct new_(id, data) {
_id = id
@@ -343,6 +642,7 @@ class View {
put(request) { methodNotAllowed_() }
delete(request) { methodNotAllowed_() }
patch(request) { methodNotAllowed_() }
websocket(request) { methodNotAllowed_() }
methodNotAllowed_() {
var r = Response.new()
@@ -351,7 +651,15 @@ class View {
return r
}
isWebSocketRequest_(request) {
var upgrade = request.header("Upgrade")
var connection = request.header("Connection")
if (upgrade == null || connection == null) return false
return Str.toLower(upgrade) == "websocket" && Str.toLower(connection).contains("upgrade")
}
dispatch(request) {
if (isWebSocketRequest_(request)) return websocket(request)
if (request.method == "GET") return get(request)
if (request.method == "POST") return post(request)
if (request.method == "PUT") return put(request)
@@ -499,6 +807,14 @@ class Application {
var request = Request.new_(method, path, query, headers, body, {}, socket)
loadSession_(request)
if (isWebSocketRequest_(headers)) {
if (_wsHandlers.containsKey(path)) {
var handler = _wsHandlers[path]
var result = handler.call(request)
return
}
}
var response = null
for (sp in _staticPrefixes) {
@@ -531,11 +847,27 @@ class Application {
}
}
if (response is WebSocketResponse) {
return
}
saveSession_(request, response)
socket.write(response.build())
socket.close()
}
isWebSocketRequest_(headers) {
var upgrade = null
var connection = null
for (key in headers.keys) {
var lower = Str.toLower(key)
if (lower == "upgrade") upgrade = headers[key]
if (lower == "connection") connection = headers[key]
}
if (upgrade == null || connection == null) return false
return Str.toLower(upgrade) == "websocket" && Str.toLower(connection).contains("upgrade")
}
loadSession_(request) {
var sessionId = request.cookies[_sessionCookieName]
if (sessionId != null) {
+332
View File
@@ -14,6 +14,9 @@ static const char* webModuleSource =
"import \"datetime\" for DateTime\n"
"import \"io\" for File\n"
"import \"strutil\" for Str\n"
"import \"crypto\" for Crypto, Hash\n"
"import \"base64\" for Base64\n"
"import \"bytes\" for Bytes\n"
"\n"
"class Request {\n"
" construct new_(method, path, query, headers, body, params, socket) {\n"
@@ -217,6 +220,302 @@ static const char* webModuleSource =
" }\n"
"}\n"
"\n"
"class WebSocketResponse {\n"
" construct new() {\n"
" _socket = null\n"
" _isPrepared = false\n"
" _isOpen = false\n"
" _readBuffer = \"\"\n"
" _fragmentBuffer = []\n"
" _fragmentOpcode = null\n"
" }\n"
"\n"
" isPrepared { _isPrepared }\n"
" isOpen { _isOpen }\n"
"\n"
" prepare(request) {\n"
" if (_isPrepared) Fiber.abort(\"WebSocket already prepared.\")\n"
"\n"
" var key = request.header(\"Sec-WebSocket-Key\")\n"
" if (key == null) Fiber.abort(\"Missing Sec-WebSocket-Key header.\")\n"
"\n"
" _socket = request.socket\n"
" var acceptKey = computeAcceptKey_(key)\n"
"\n"
" var response = \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n"
" response = response + \"Upgrade: websocket\\r\\n\"\n"
" response = response + \"Connection: Upgrade\\r\\n\"\n"
" response = response + \"Sec-WebSocket-Accept: %(acceptKey)\\r\\n\"\n"
" response = response + \"\\r\\n\"\n"
"\n"
" _socket.write(response)\n"
" _isPrepared = true\n"
" _isOpen = true\n"
" return this\n"
" }\n"
"\n"
" send(text) { sendText(text) }\n"
"\n"
" sendText(text) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" if (!(text is String)) Fiber.abort(\"Data must be a string.\")\n"
" var payload = stringToBytes_(text)\n"
" sendFrame_(1, payload)\n"
" }\n"
"\n"
" sendBinary(bytes) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" if (!(bytes is List)) Fiber.abort(\"Data must be a list of bytes.\")\n"
" sendFrame_(2, bytes)\n"
" }\n"
"\n"
" ping() { ping([]) }\n"
"\n"
" ping(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var payload = data\n"
" if (data is String) payload = stringToBytes_(data)\n"
" if (payload.count > 125) Fiber.abort(\"Ping payload too large (max 125 bytes).\")\n"
" sendFrame_(9, payload)\n"
" }\n"
"\n"
" pong(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var payload = data\n"
" if (data is String) payload = stringToBytes_(data)\n"
" if (payload.count > 125) Fiber.abort(\"Pong payload too large (max 125 bytes).\")\n"
" sendFrame_(10, payload)\n"
" }\n"
"\n"
" close() { close(1000, \"\") }\n"
"\n"
" close(code, reason) {\n"
" if (!_isOpen) return\n"
"\n"
" var payload = []\n"
" payload.add((code >> 8) & 0xFF)\n"
" payload.add(code & 0xFF)\n"
" for (b in stringToBytes_(reason)) {\n"
" payload.add(b)\n"
" }\n"
"\n"
" sendFrame_(8, payload)\n"
" _isOpen = false\n"
" _socket.close()\n"
" }\n"
"\n"
" receive() {\n"
" if (!_isOpen) return null\n"
"\n"
" while (true) {\n"
" var frame = readFrame_()\n"
" if (frame == null) {\n"
" _isOpen = false\n"
" return null\n"
" }\n"
"\n"
" var opcode = frame.opcode\n"
" var payload = frame.payload\n"
" var fin = frame.fin\n"
"\n"
" if (opcode == 8) {\n"
" _isOpen = false\n"
" return frame\n"
" }\n"
"\n"
" if (opcode == 9) {\n"
" sendFrame_(10, payload)\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 10) {\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 0) {\n"
" for (b in payload) {\n"
" _fragmentBuffer.add(b)\n"
" }\n"
" if (fin) {\n"
" var completePayload = _fragmentBuffer\n"
" var completeOpcode = _fragmentOpcode\n"
" _fragmentBuffer = []\n"
" _fragmentOpcode = null\n"
" return WebSocketMessage.new_(completeOpcode, completePayload, true)\n"
" }\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 1 || opcode == 2) {\n"
" if (fin) {\n"
" return frame\n"
" } else {\n"
" _fragmentOpcode = opcode\n"
" _fragmentBuffer = []\n"
" for (b in payload) {\n"
" _fragmentBuffer.add(b)\n"
" }\n"
" continue\n"
" }\n"
" }\n"
"\n"
" Fiber.abort(\"Unknown opcode: %(opcode)\")\n"
" }\n"
" }\n"
"\n"
" iterate(callback) {\n"
" while (_isOpen) {\n"
" var msg = receive()\n"
" if (msg == null || msg.isClose) break\n"
" callback.call(msg)\n"
" }\n"
" }\n"
"\n"
" sendJson(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var text = Json.stringify(data)\n"
" sendText(text)\n"
" }\n"
"\n"
" receiveJson() {\n"
" var msg = receive()\n"
" if (msg == null) return null\n"
" if (msg.isClose) return msg\n"
" if (!msg.isText) Fiber.abort(\"Expected text frame for JSON, got binary.\")\n"
" return Json.parse(msg.text)\n"
" }\n"
"\n"
" receiveText() {\n"
" var msg = receive()\n"
" if (msg == null) return null\n"
" if (msg.isClose) return null\n"
" if (!msg.isText) return null\n"
" return msg.text\n"
" }\n"
"\n"
" receiveBinary() {\n"
" var msg = receive()\n"
" if (msg == null) return null\n"
" if (msg.isClose) return null\n"
" if (!msg.isBinary) return null\n"
" return msg.bytes\n"
" }\n"
"\n"
" sendFrame_(opcode, payload) {\n"
" var frame = encodeFrame_(opcode, payload)\n"
" _socket.write(Bytes.fromList(frame))\n"
" }\n"
"\n"
" encodeFrame_(opcode, payload) {\n"
" var frame = []\n"
" frame.add(0x80 | opcode)\n"
"\n"
" var len = payload.count\n"
"\n"
" if (len < 126) {\n"
" frame.add(len)\n"
" } else if (len < 65536) {\n"
" frame.add(126)\n"
" frame.add((len >> 8) & 0xFF)\n"
" frame.add(len & 0xFF)\n"
" } else {\n"
" frame.add(127)\n"
" for (i in 0...4) frame.add(0)\n"
" frame.add((len >> 24) & 0xFF)\n"
" frame.add((len >> 16) & 0xFF)\n"
" frame.add((len >> 8) & 0xFF)\n"
" frame.add(len & 0xFF)\n"
" }\n"
"\n"
" for (b in payload) frame.add(b)\n"
" return frame\n"
" }\n"
"\n"
" readFrame_() {\n"
" var header = readBytes_(2)\n"
" if (header == null || Bytes.length(header) < 2) return null\n"
"\n"
" var headerList = Bytes.toList(header)\n"
" var fin = (headerList[0] & 0x80) != 0\n"
" var opcode = headerList[0] & 0x0F\n"
" var masked = (headerList[1] & 0x80) != 0\n"
" var len = headerList[1] & 0x7F\n"
"\n"
" if (len == 126) {\n"
" var ext = readBytes_(2)\n"
" if (ext == null || Bytes.length(ext) < 2) return null\n"
" var extList = Bytes.toList(ext)\n"
" len = (extList[0] << 8) | extList[1]\n"
" } else if (len == 127) {\n"
" var ext = readBytes_(8)\n"
" if (ext == null || Bytes.length(ext) < 8) return null\n"
" var extList = Bytes.toList(ext)\n"
" len = 0\n"
" for (i in 4...8) {\n"
" len = (len << 8) | extList[i]\n"
" }\n"
" }\n"
"\n"
" var mask = null\n"
" if (masked) {\n"
" mask = readBytes_(4)\n"
" if (mask == null || Bytes.length(mask) < 4) return null\n"
" }\n"
"\n"
" var payload = \"\"\n"
" if (len > 0) {\n"
" payload = readBytes_(len)\n"
" if (payload == null) return null\n"
" }\n"
"\n"
" if (masked && mask != null) {\n"
" payload = Bytes.xorMask(payload, mask)\n"
" }\n"
"\n"
" return WebSocketMessage.new_(opcode, Bytes.toList(payload), fin)\n"
" }\n"
"\n"
" readBytes_(count) {\n"
" while (Bytes.length(_readBuffer) < count) {\n"
" var chunk = _socket.read()\n"
" if (chunk == null || chunk.bytes.count == 0) {\n"
" if (Bytes.length(_readBuffer) == 0) return null\n"
" var result = _readBuffer\n"
" _readBuffer = \"\"\n"
" return result\n"
" }\n"
" _readBuffer = Bytes.concat(_readBuffer, chunk)\n"
" }\n"
" var result = Bytes.slice(_readBuffer, 0, count)\n"
" _readBuffer = Bytes.slice(_readBuffer, count, Bytes.length(_readBuffer))\n"
" return result\n"
" }\n"
"\n"
" computeAcceptKey_(key) {\n"
" var magic = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n"
" var combined = key + magic\n"
" var sha1 = Hash.sha1(combined)\n"
" var sha1Str = bytesToString_(sha1)\n"
" return Base64.encode(sha1Str)\n"
" }\n"
"\n"
" bytesToString_(bytes) {\n"
" var parts = []\n"
" for (b in bytes) {\n"
" parts.add(String.fromByte(b))\n"
" }\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" stringToBytes_(str) {\n"
" var bytes = []\n"
" for (b in str.bytes) {\n"
" bytes.add(b)\n"
" }\n"
" return bytes\n"
" }\n"
"}\n"
"\n"
"class Session {\n"
" construct new_(id, data) {\n"
" _id = id\n"
@@ -347,6 +646,7 @@ static const char* webModuleSource =
" put(request) { methodNotAllowed_() }\n"
" delete(request) { methodNotAllowed_() }\n"
" patch(request) { methodNotAllowed_() }\n"
" websocket(request) { methodNotAllowed_() }\n"
"\n"
" methodNotAllowed_() {\n"
" var r = Response.new()\n"
@@ -355,7 +655,15 @@ static const char* webModuleSource =
" return r\n"
" }\n"
"\n"
" isWebSocketRequest_(request) {\n"
" var upgrade = request.header(\"Upgrade\")\n"
" var connection = request.header(\"Connection\")\n"
" if (upgrade == null || connection == null) return false\n"
" return Str.toLower(upgrade) == \"websocket\" && Str.toLower(connection).contains(\"upgrade\")\n"
" }\n"
"\n"
" dispatch(request) {\n"
" if (isWebSocketRequest_(request)) return websocket(request)\n"
" if (request.method == \"GET\") return get(request)\n"
" if (request.method == \"POST\") return post(request)\n"
" if (request.method == \"PUT\") return put(request)\n"
@@ -503,6 +811,14 @@ static const char* webModuleSource =
" var request = Request.new_(method, path, query, headers, body, {}, socket)\n"
" loadSession_(request)\n"
"\n"
" if (isWebSocketRequest_(headers)) {\n"
" if (_wsHandlers.containsKey(path)) {\n"
" var handler = _wsHandlers[path]\n"
" var result = handler.call(request)\n"
" return\n"
" }\n"
" }\n"
"\n"
" var response = null\n"
"\n"
" for (sp in _staticPrefixes) {\n"
@@ -535,11 +851,27 @@ static const char* webModuleSource =
" }\n"
" }\n"
"\n"
" if (response is WebSocketResponse) {\n"
" return\n"
" }\n"
"\n"
" saveSession_(request, response)\n"
" socket.write(response.build())\n"
" socket.close()\n"
" }\n"
"\n"
" isWebSocketRequest_(headers) {\n"
" var upgrade = null\n"
" var connection = null\n"
" for (key in headers.keys) {\n"
" var lower = Str.toLower(key)\n"
" if (lower == \"upgrade\") upgrade = headers[key]\n"
" if (lower == \"connection\") connection = headers[key]\n"
" }\n"
" if (upgrade == null || connection == null) return false\n"
" return Str.toLower(upgrade) == \"websocket\" && Str.toLower(connection).contains(\"upgrade\")\n"
" }\n"
"\n"
" loadSession_(request) {\n"
" var sessionId = request.cookies[_sessionCookieName]\n"
" if (sessionId != null) {\n"