├── README ├── app ├── controllers │ ├── admin.io │ └── blog.io └── models │ ├── post.io │ └── user.io ├── config └── routes.io ├── run.io └── vendor └── iota ├── brio.io ├── extensions └── map.io ├── iota.io └── markio.io /README: -------------------------------------------------------------------------------- 1 | Iota is a small web application framework written in Io. (Get it? Iota? Hah.) 2 | It is insanely far from production-ready. It has not even been extracted from 3 | the sample application which I've been developing it around. Things will 4 | change... and break... and then get fixed... only to break again. But I'm 5 | pleased with how it's going, so maybe you'll like it as well. 6 | 7 | Iota is distributed under the MIT License. 8 | -------------------------------------------------------------------------------- /app/controllers/admin.io: -------------------------------------------------------------------------------- 1 | AdminController := Iota Controller clone do ( 2 | showLogin := method( render(showLogin) ) 3 | login := method( 4 | if(User login(params), 5 | session atPut("admin", true) 6 | redirect("/admin"), 7 | redirect("/login") ) ) 8 | 9 | control := method( 10 | if(session at("admin") != true, 11 | redirect("/login"), 12 | setVar(posts, Post all) 13 | render(control) ) ) 14 | 15 | create := method( 16 | if(session at ("admin") != true, 17 | redirect("/login"), 18 | p := Post new 19 | p fields atPut("title", params at("title") ) 20 | p fields atPut("body", params at("body") ) 21 | p save 22 | redirect("/admin") ) ) ) 23 | 24 | AdminController View do ( 25 | showLogin := method( 26 | html( 27 | head( title("Login") ), 28 | body( 29 | h1( "Login" ), 30 | form({method;"post",action;"/login"}, 31 | div( 32 | label( "Name" ), 33 | input({type;"text", name;"name"}) ), 34 | div( 35 | label( "Password" ), 36 | input({type;"text", name;"password"}) ), 37 | input({type;"submit"}) ) ) ) ) 38 | 39 | control := method( 40 | html( 41 | head( title("Admin") ), 42 | body( 43 | h1( "Post" ), 44 | form({method;"post",action;"/admin/post"}, 45 | div( 46 | label( "Title" ), 47 | input({type;"text", name;"title"}) ), 48 | div( 49 | label( "Body" ), 50 | textarea({name;"body"}) ), 51 | input({type;"submit"}) ) ) ) ) 52 | 53 | ) 54 | 55 | -------------------------------------------------------------------------------- /app/controllers/blog.io: -------------------------------------------------------------------------------- 1 | BlogController := Iota Controller clone do ( 2 | index := method( 3 | setVar(posts, Post top(7)) 4 | render(index) ) 5 | 6 | show := method( 7 | setVar(post, Post at( params at("id") )) 8 | render(show) ) 9 | ) 10 | 11 | BlogController View do ( 12 | index := method( 13 | html( 14 | head( title("Tyler's Io Blog") ), 15 | body({style;"font-family:sans-serif"}, 16 | h1.title("CodeHallow"), 17 | p( "this might be problematic..." ), 18 | posts map(post, 19 | ( div( h2( a({href;("/post/#{post id}" interpolate)}, post title)), 20 | div(post body) ) ) ) join, 21 | b:id( "Thanks for stopping by." )))) 22 | 23 | create := method( "blah" ) 24 | 25 | show := method( 26 | html( 27 | head( title( post title ) ), 28 | body( 29 | h1( a({href;("/post/#{post id}" interpolate)}, post title ) ), 30 | p( post body ) ) ) ) ) 31 | 32 | -------------------------------------------------------------------------------- /app/models/post.io: -------------------------------------------------------------------------------- 1 | Post := Iota Model setup("posts") 2 | Post top := method(i, 3 | find("1=1 ORDER BY ID DESC LIMIT #{i}" interpolate) ) 4 | -------------------------------------------------------------------------------- /app/models/user.io: -------------------------------------------------------------------------------- 1 | SHA1 2 | 3 | User := Iota Model setup("users") 4 | User login := method(params, 5 | name := params at ("name") 6 | writeln("name") 7 | s := SHA1 clone 8 | s appendSeq( params at ("password") .. "pandas" ) 9 | pw := s sha1String 10 | if( find("name='#{name}' AND password='#{pw}'" interpolate) size > 0, true, false ) ) 11 | -------------------------------------------------------------------------------- /config/routes.io: -------------------------------------------------------------------------------- 1 | Iota Router do( 2 | route("/login", "GET", AdminController, showLogin) 3 | route("/login", "POST", AdminController, login) 4 | route("/admin", "GET", AdminController, control) 5 | route("/admin/post", "POST", AdminController, create) 6 | 7 | route("^/$", "GET", BlogController, index) 8 | route("^/post/(?\\d+)$", "GET", BlogController, show) 9 | ) 10 | -------------------------------------------------------------------------------- /run.io: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env io 2 | 3 | Regex 4 | 5 | doFile("vendor/iota/iota.io") 6 | 7 | doFile("app/controllers/blog.io") 8 | doFile("app/controllers/admin.io") 9 | 10 | doFile("app/models/user.io") 11 | doFile("app/models/post.io") 12 | 13 | doFile("config/routes.io") 14 | 15 | doFile("vendor/iota/brio.io") 16 | 17 | Brio run( 2013, block(request, response, Iota Router dispatch (request,response) ) ) 18 | 19 | -------------------------------------------------------------------------------- /vendor/iota/brio.io: -------------------------------------------------------------------------------- 1 | CGI 2 | 3 | Request := Object clone do( 4 | raw := nil 5 | resource := nil 6 | httpVersion := nil 7 | httpMethod := nil 8 | headers := nil 9 | body := nil 10 | params := nil 11 | body_params := nil 12 | url_params := nil) 13 | 14 | Request parse := method(data, 15 | self raw = data 16 | 17 | requestLines := data split("\n") 18 | statusParts := requestLines at(0) split 19 | 20 | self httpMethod = statusParts at(0) 21 | self resource = statusParts at(1) 22 | self httpVersion = statusParts at(2) 23 | 24 | headerLines := requestLines slice(1, requestLines indexOf("") - 1) 25 | self headers = headerLines map(v, v split(": ")) 26 | 27 | ch := headers detect(v, v first == "Cookie") 28 | self cookies := if(ch, 29 | cs := ch last split("; ") 30 | cmap := Map clone 31 | cs foreach(v, 32 | p := v split("=") 33 | cmap atPut(p first strip, p last strip) ) 34 | cmap, 35 | Map clone) 36 | 37 | 38 | self body = requestLines slice( headerLines size + 1 ) join("\n") 39 | self body_params := Map clone 40 | self body split("&") foreach(r, 41 | r = r split("=") 42 | self body_params atPut(CGI decodeUrlParam(r first), CGI decodeUrlParam(r last))) 43 | 44 | self params := self body_params clone 45 | 46 | self url_params := Map clone 47 | s := self resource split("?") last 48 | if(s != self resource, 49 | s split("&") foreach(r, 50 | r = r split("=") 51 | self url_params atPut(CGI decodeUrlParam(r first), CGI decodeUrlParam(r last)) 52 | self params atPut(r first, r last))) ) 53 | 54 | 55 | Response := Object clone do( 56 | status := 200 57 | reason := "OK" 58 | headers := nil 59 | body := nil 60 | init := method( headers = Map clone atPut("Content-Type", "text/html") ) ) 61 | 62 | Response assemble := method( 63 | lines := List clone 64 | 65 | lines append("HTTP/1.1 #{status} #{reason}" interpolate) 66 | ah := block(k,v, lines append("#{k}: #{v}" interpolate)) 67 | headers foreach(k, v, 68 | if(v isKindOf(List), 69 | v foreach(t, ah call(k,t)), 70 | ah call(k,v) 71 | ) 72 | ) 73 | lines append("") 74 | lines append(body) 75 | 76 | lines join("\n") ) 77 | 78 | 79 | Brio := Server clone 80 | 81 | Brio handleSocket := method(socket, 82 | socket streamReadNextChunk 83 | data := socket readBuffer 84 | 85 | response := Response clone 86 | request := Request clone 87 | request parse(data) 88 | 89 | writeln("Request:") 90 | writeln(data) 91 | 92 | writeln(request params asList) 93 | writeln(request body_params asList) 94 | writeln(request url_params asList) 95 | 96 | 97 | response = self handler call(request, response) 98 | 99 | writeln(response assemble) 100 | 101 | socket streamWrite(response assemble) 102 | socket close) 103 | 104 | Brio run := method(port, handler, 105 | writeln("Starting Brio on port #{port}." interpolate) 106 | self setPort(port) 107 | self handler := handler 108 | self start) 109 | -------------------------------------------------------------------------------- /vendor/iota/extensions/map.io: -------------------------------------------------------------------------------- 1 | Object curlyBrackets := method( 2 | nm := Map clone 3 | call message arguments foreach(arg, 4 | p := arg asString split(":") 5 | nm atPut(p first asMessage doInContext(call sender), p last asMessage doInContext(call sender))) 6 | nm 7 | ) 8 | -------------------------------------------------------------------------------- /vendor/iota/iota.io: -------------------------------------------------------------------------------- 1 | Regex 2 | Random 3 | MD5 4 | 5 | Object squareBrackets := method(call message argsEvaluatedIn(call sender)) 6 | Object curlyBrackets := method( 7 | map := Map clone 8 | call message arguments foreach( 9 | pair, map atPut(pair name, call sender doMessage(pair last))) ) 10 | 11 | Map merge := method(new, new foreach(k, v, self atPut(k,v)) ; self) 12 | 13 | 14 | 15 | Iota := Object clone 16 | 17 | Iota Session := Object clone 18 | Iota Session key := "pandas mak3 Delightful compani0ns" 19 | Iota Session sessions := Map clone 20 | Iota Session createSession := method( 21 | newKey := generateSessionId 22 | writeln("Creating a session, dammit!") 23 | sessions atPut(newKey, Map clone) 24 | newKey 25 | ) 26 | Iota Session handle_cookies := method(cookies, 27 | if(sessions at(cookies at("session") asString) isNil, 28 | cookies atPut("session", createSession)) 29 | cookies 30 | ) 31 | Iota Session get := method(cookies, sessions at( cookies at("session") ) ) 32 | Iota Session generateSessionId := method( 33 | m := MD5 clone 34 | m appendSeq(Date asNumber asString .. Random value asString .. key) 35 | m md5String 36 | ) 37 | 38 | Iota Router := Object clone do( routes := List clone ) 39 | Iota Router route := method(path, httpMethod, controller, 40 | routes append({path;path, httpMethod;httpMethod, controller;controller, action;(call argAt(3))}) 41 | ) 42 | 43 | Iota Router scan := method(url, meth, 44 | cr := block(obj, obj at("path") asRegex matchesIn(url) next) 45 | route := routes detect(v, cr call(v) and v at("httpMethod") == meth ) 46 | if(route isNil, return(nil)) 47 | route params := Map clone 48 | md := cr call(route) 49 | md names map(i, name, 50 | if(name, route params atPut(name, md captures at(i)))) 51 | route 52 | ) 53 | 54 | Iota Router dispatch := method(request, response, 55 | route := scan(request resource, request httpMethod) 56 | if(route isNil, return(fail(request, response))) 57 | 58 | cookies := Iota Session handle_cookies(request cookies) 59 | session := Iota Session get(request cookies) 60 | 61 | controller := route at("controller") clone 62 | controller request := request 63 | controller response := response 64 | controller params := request params merge(route params) 65 | controller cookies := cookies 66 | controller session := session 67 | 68 | controller perform(route at("action")) 69 | handleResponse( controller ) 70 | ) 71 | 72 | Iota Router handleResponse := method(controller, 73 | if(controller cookies size > 0, 74 | cks := controller response headers at("Set-Cookie") 75 | cks = if(cks isNil, list, cks) 76 | controller cookies foreach(k, v, cks append( "#{k}=#{v}; path=/" interpolate )) 77 | controller response headers atPut("Set-Cookie", cks) 78 | ) 79 | Iota Session sessions atPut(controller cookies at("session"), controller session) 80 | controller response 81 | ) 82 | 83 | Iota Router fail := method(request, response, 84 | response body = "The page you requested (#{request resource}) was not found." interpolate 85 | response status = 404 86 | response reason = "Not Found" 87 | response 88 | ) 89 | 90 | 91 | 92 | Iota Controller := Object clone 93 | Iota Controller init := method( self viewVars := Map clone ) 94 | Iota Controller setVar := method( self viewVars atPut(call argAt(0) name, call evalArgAt(1)) ) 95 | Iota Controller redirect := method(url, 96 | self response status = 302 97 | self response reason = "Found" 98 | self response headers atPut("Location", url) ) 99 | Iota Controller render := method( 100 | view := self View clone 101 | viewVars foreach(k,v, view setSlot(k,v)) 102 | self response body = view perform(call argAt(0)) ) 103 | 104 | Iota Controller View := Object clone 105 | Iota Controller View forward := method( 106 | inner := "" 107 | 108 | np := parseTag(call message name) 109 | tagName := np at("tag") 110 | 111 | if(call hasArgs, 112 | res := call message argsEvaluatedIn(self) 113 | attrs := res detect(v, v hasProto(Map)) 114 | inner = inner .. res remove(attrs) join) 115 | 116 | attrs := if(attrs isNil, Map clone, attrs) 117 | if(np at("class"), attrs atPut("class", np at("class"))) 118 | if(np at("id"), attrs atPut("id", np at("id"))) 119 | 120 | attrStr := "" 121 | if(attrs, attrs foreach(k, v, attrStr = attrStr .. " #{k}=\"#{v}\"" interpolate)) 122 | 123 | "<#{tagName}#{attrStr}>#{inner}" interpolate ) 124 | 125 | Iota Controller View parseTag := method(name, 126 | rx := "[^:.]+" asRegex 127 | out := Map clone 128 | mchs := rx matchesIn(name) 129 | mchs all foreach(i, m, 130 | ss := mchs splitString at(i) 131 | if(ss == "", 132 | out atPut("tag", m), 133 | if(ss == ".", 134 | l := if(out hasKey("class"), out at("class"), "") 135 | if(l == "", l = m asString, l append(" " .. m asString)) 136 | out atPut("class", l), 137 | out atPut("id",m) 138 | ) 139 | ) 140 | ) 141 | out 142 | ) 143 | Iota Controller View html := method( call message argsEvaluatedIn(self) join ) 144 | 145 | 146 | Iota Model := Object clone do( 147 | table := nil 148 | primaryKey := "id" 149 | connection := MySQL connect("localhost","root","","ioblog",nil,"/var/run/mysqld/mysqld.sock") ) 150 | 151 | Iota Model setup := method(table, 152 | model := self clone 153 | model table := table 154 | model columns := List clone 155 | connection query("describe #{table}" interpolate) foreach(i, col, 156 | model columns append(col first) 157 | if(col at(3) == "PRI", model primaryKey := col first) 158 | model setSlot(col first, method( self fields at(call message asString) ) ) ) 159 | model ) 160 | 161 | Iota Model query_one := method(sql, query_many(sql) first) 162 | Iota Model query_many := method(sql, 163 | query(sql) map (row, 164 | new := self clone 165 | new fields := row 166 | new ) ) 167 | Iota Model query := method(sql, writeln(sql); self connection queryThenMap(sql)) 168 | 169 | Iota Model escape := method(sql, sql matchesOfRegex("([\'\"])") replaceAllWith("\\$1") ) 170 | 171 | Iota Model new := method( self clone do( fields := Map clone ) ) 172 | 173 | Iota Model insert := method( 174 | res := query("INSERT INTO `#{escape(self table)}` 175 | (" interpolate .. fields map(k,v,k) join(",") .. ") VALUES 176 | (" .. fields map(k,v, "'#{escape(v)}'" interpolate) join(",") .. ")") 177 | if(res != 1, return(false)) 178 | self fields atPut("id", connection lastInsertRowId); 179 | true ) 180 | Iota Model update := method( 181 | query("UPDATE `#{escape(self table)}` 182 | SET " interpolate .. fields map(k, v, 183 | "`#{escape(k)}`='#{escape(v asString)}'" interpolate 184 | ) join(",") .. " 185 | WHERE `#{primaryKey}`='#{escape(fields at(primaryKey))}'" interpolate) == 1 ) 186 | Iota Model save := method( if( self fields at( self primaryKey ), update, insert) ) 187 | 188 | Iota Model at := method(id, 189 | query_one("SELECT * 190 | FROM `#{escape(self table)}` 191 | WHERE 192 | `#{escape(self primaryKey)}`='#{escape(id asString)}'" interpolate) ) 193 | Iota Model find := method( where, query_many("SELECT * FROM `#{escape(table)}` WHERE #{where}" interpolate) ) 194 | Iota Model all := method( query_many("SELECT * FROM `#{escape(table)}`" interpolate) ) 195 | 196 | -------------------------------------------------------------------------------- /vendor/iota/markio.io: -------------------------------------------------------------------------------- 1 | Markio := Object clone 2 | Markio Env := Object clone 3 | 4 | 5 | Markio makeEnv := method(params, 6 | env := Env clone 7 | params foreach(k, v, env setSlot(k, v)) 8 | env 9 | ) 10 | 11 | Markio Env init := method( self _out := "" asMutable ) 12 | 13 | Markio Env build := method( 14 | clear 15 | call message argsEvaluatedIn(self) join("\n") 16 | ) 17 | 18 | Markio Env cat := method(text, _out appendSeq(text)) 19 | Markio Env clear := method(_out = "" asMutable) 20 | 21 | Markio Env forward := method( 22 | parts := parseTagLine(call message name) 23 | 24 | attrs := if(call message argCount > 1, 25 | call message argAt(0) doInContext(self), 26 | Map clone) 27 | 28 | 29 | if(parts at(1), attrs atPut("id", parts at(1))) 30 | if(parts at(2), attrs atPut("class", parts at(2))) 31 | 32 | tag(parts at(0), attrs, call message arguments last) 33 | ) 34 | 35 | Markio Env tag := method(name, props, children, 36 | attrStr := props map(k, v, "#{k}=\"#{v}\"" interpolate) join(" ") 37 | if(attrStr size != 0, attrStr = " " .. attrStr) 38 | 39 | cat("<#{name}#{attrStr}>" interpolate) 40 | if(children, children doInContext(self)) 41 | cat("" interpolate) 42 | ) 43 | 44 | Markio Env ctag := method(name, props 45 | # TODO 46 | # Appends a self-closing tag to _out 47 | ) 48 | 49 | Markio Env > := method(text, cat(text)) 50 | 51 | Markio Env doctype := method( 52 | # TODO 53 | # Outputs an XHTML Transitional doctype by default, eventually 54 | # accepts an argument to choose between multiple 55 | ) 56 | 57 | # Internal methods 58 | 59 | Markio Env parseTagLine := method(line, 60 | rx := "[^._]+" asRegex 61 | mchs := rx matchesFor(line) 62 | mchs all foreach(i, m, 63 | s := m asString 64 | if( i == 0, 65 | out := list(s,nil,list()), 66 | if(mchs splitString at(i) == "_", 67 | out atPut(1,s), 68 | out atPut(2, out at(2) append(s)) 69 | ) 70 | ) 71 | ) 72 | 73 | if(out at(2) size == 0, 74 | out atPut(2, nil), 75 | out atPut(2, out at(2) join(" ")) 76 | ) 77 | out 78 | ) 79 | 80 | --------------------------------------------------------------------------------