Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 01fea4d9ed
Fetching contributors…

Cannot retrieve contributors at this time

file 253 lines (198 sloc) 7.228 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
@@license("New BSD License, part of efene, see LICENSE for details")
@@moddoc("module to manipulate struct data structures")

not_found = fn (_Struct, Name)
    throw(attribute_error => "struct has no attribute '" ++ atom_to_list(Name) ++ "'")

@doc("function called from getx when the attribute wasn't found on the struct,
look if there is a proto_ attribute and look inside it to search for the
value")
not_found_proto = fn (Struct, Name) when is_atom(Name)
    if struct.has(Struct, proto_)
        Proto = Struct.proto_
        if struct.has(Proto, Name)
            struct.get(Proto, Name)
        else
            not_found(Struct, Name)
    else
        not_found(Struct, Name)

@doc("throw an exception stating that the value is not a valid struct")
not_struct = fn (Val)
    throw(value_error => io_lib.format("value ~p doesn't seem to be a struct", [Val]))

get_found = fn (_Struct, _Name, Value)
    Value

set_found = fn (Value)
    fn ((struct, Body), Name, _OldValue)
        (struct, lists.keyreplace(Name, 1, Body, Name => Value))

@public
@doc("return true if the parameter is a valid struct, false otherwise")
is_struct = fn ((struct, Values)) when is_list(Values)
    true

fn (_)
    false

@public
@doc("access the value of an attribute in a struct")
get = fn (Struct, [])
    Struct

fn (Struct, [Name:Names])
    get(do(Struct, Name, fn get_found:3), Names)

@public
@doc("access the value of an attribute in a struct, if not found return default")
getdef = fn (Struct, [], _Default)
    Struct

fn (Struct, [Name:Names], Default)
    NotFound = fn (_Struct, _Name)
        Default

    NotStruct = fn (_Val)
        Default

    getdef(do(Struct, Name, fn get_found:3, NotFound, NotStruct), Names, Default)

@public
@doc("access the value of an attribute in a struct, if not found in the struct
look inside the proto_ attribute")
getx = fn (Struct, Name)
    do(Struct, Name, fn get_found:3, fn not_found_proto:2)

@public
@doc("replace the value of an attribute in a struct")
set = fn (Struct, Name, Value)
    do(Struct, Name, set_found(Value))

@public
@doc("return a pretty representation of a struct")
format = fn (Struct)
    format(Struct, false)

format = fn ((struct, Body), ShowFuns)
    Items = for Key => Val in Body if not is_function(Val) or ShowFuns
        if Key == proto_
            ""
        else if schema.is_object(Val)
            io_lib.format("~p: ~s, ", [Key, format(Val, ShowFuns)])
        else
            io_lib.format("~p: ~s, ", [Key, fio.format(Val)])

    Items1 = lists.flatten(Items)
    Items1Len = length(Items1)

    if Items1Len == 0
        io_lib.format("{}")
    else
        # TODO: seems to enter here with Items1Len == 0 (?)
        io_lib.format("{~s}", [lists.sublist(Items1, Items1Len - 2)])

@public
@doc("pretty print a struct")
print = fn (Struct)
    print(Struct, false)

@public
@doc("pretty print a struct")
print = fn (Struct, ShowFuns)
    io.format("~s~n", [format(Struct, ShowFuns)])

@public
@doc("return true if the struct has the given attribute as atom or string,
false otherwise.

the second parameter must be an atom or a list of atoms/strings

to check if a struct has an attribute with a string use hasstr

throw an exception if the given parameter is not a valid struct")
has = fn (_Struct, [])
    true

fn (Struct, [Name:Names])

    CheckFun = if is_atom(Name)
        has
    else
        hasstr

    if struct.CheckFun(Struct, Name)

        if length(Names) == 0
            true
        else
            NewStruct = get(Struct, [Name])

            if is_struct(NewStruct)
                has(NewStruct, Names)
            else
                false
    else
        false

fn (Struct, Name)
    Found = fn (_Struct, _Name, _Value)
        true

    NotFound = fn (Struct1, Name1) when is_atom(Name1)
        hasstr(Struct1, atom_to_list(Name1))
    fn (_Struct, _Name)
        false

    do(Struct, Name, Found, NotFound)

@public
@doc("return true if the struct has the given attribute as string,
false otherwise.

throw an exception if the given parameter is not a valid struct")
hasstr = fn (Struct, StrName)
    Found = fn (_Struct, _Name, _Value)
        true

    NotFound = fn (_Struct, _Name)
        false

    do(Struct, StrName, Found, NotFound)

@public
@doc("return all the field names of a struct.

the types are preserved, so if a struct has some string keys (posible if
decoding json) then those keys will be returned as strings.

it's your job to check that. This is done this way to avoid creating atoms
")
fields = fn ((struct, Body))
    [Key for (Key, _Val) in Body if Key != proto_]

fn (Val)
    not_struct(Val)

@public
@doc("extend the struct by adding an attribute named *Key* with value *Value*")
extend = fn ((struct, Body), Key, Value)
    if is_atom(Key) or is_list(Key)
        (struct, [Key => Value:Body])
    else
        throw(value_error => "key isn't a string or an atom")

fn (_, _Key, _Value)
    throw(value_error => "value doesn't seem to be a struct")

@public
@doc("return the prototype for a basic struct that can do the operations
defined in this module")
prototype = fn ()
    Get = fn get:2
    Has = fn has:2
    Set = fn set:3
    GetX = fn getx:2
    GetDef = fn getdef:3
    Fields = fn fields:1
    Extend = fn extend:3
    Format = fn format:1
    Print = fn print:1

    {get: Get, has: Has, set: Set, getx: GetX, fields: Fields,
        extend: Extend, getdef: GetDef, format: Format, print: Print}

@public
@doc("look for an attribute and call a function when found")
do = fn (Struct, Name, FoundFn)
    do(Struct, Name, FoundFn, fn not_found:2)

@public
@doc("look for an attribute and call a function when found, and other if not
found.

if *Name* is an atom and it's not found it will convert the atom to a string
and check again using the string as key.

if *Name* is a string it will only check for the key a string since atoms
aren't garbage collected we don't want to be creating atoms for every attribute
lookup.

the FoundFn function will be called with the following parameters:
 * Struct: the struct passed as parameter
 * Name: the name of the attribute as passed to this function (atom or string)
 * Value: the found value of the attribute

the NotFoundFn function will be called with the following parameters:
 * Struct: the struct passed as parameter
 * Name: the name of the attribute as passed to this function (atom or string)
")
do = fn (Struct, Name, FoundFn, NotFoundFn)
    do(Struct, Name, FoundFn, NotFoundFn, fn not_struct:1)

@public
@doc("like do/4 but with a callback to be called when the parameter is not a struct")
do = fn ((struct, Body)=Struct, Name, FoundFn, NotFoundFn, _NotStruct)
    switch lists.keyfind(Name, 1, Body)
        case false
            if is_atom(Name)
                StrName = atom_to_list(Name)

                switch lists.keyfind(StrName, 1, Body)
                    case false
                        NotFoundFn(Struct, Name)
                    case _ => Value
                        FoundFn(Struct, Name, Value)

            else
                NotFoundFn(Struct, Name)
        case _ => Value
            FoundFn(Struct, Name, Value)

fn (Val, _Name, _FoundFn, _NotFoundFn, NotStruct)
    NotStruct(Val)
Something went wrong with that request. Please try again.