Make an array of Hash

Hello, I have a problem today. I try to initialize an empty Array,and this array will have inside hash. But how I can initialize that properly, because when I try that, always I have an error.

See. This code is an example of what I want:

SoftwaresDatabase = Array(Hash).new

SoftwaresDatabase << { 
"Name" => "Binutils",
"Architectures" => ["x86_64"],
"Description" => "The GNU collection of binary tools",
"Website" => "https://www.gnu.org/software/binutils/",
"DownloadLinks" => ["https://ftp.gnu.org/gnu/binutils/binutils-2.37.tar.xz"],
"SignatureLinks" => ["https://ftp.gnu.org/gnu/binutils/binutils-2.37.tar.xz.sig"],
"Options" => {  "Pass1" => {"Description" => "Build the first pass to make a system from scratch",
                            "Enabled" => false},
                "Pass2" => {"Description" => "Build the second pass to make a system from scratch",
                            "Enabled" => false},
            },
"Download" => download,
"Check" => check,
"Extract" => extract,
"Prepare" => prepare,
"Configure" => configure,
"Build" => build,
"Install" => install,
"Uninstall" => uninstall,
"EnableOption" => enableOption,
"DisableOption" => disableOption
                        }

def download
    `wget https://ftp.gnu.org/gnu/binutils/binutils-2.37.tar.xz`
    `wget https://ftp.gnu.org/gnu/binutils/binutils-2.37.tar.sig`
end

def check
    `gpg binutils-2.37.tar.xz.sig`
end

def extract
    `tar -xf binutils-2.37.tar.xz`
end

def prepare
    Dir.mkdir("build")
    Dir.cd("build")
end

def configure
    if SoftwareInformation["Options"]["Pass1"]["Enabled"] == true
        `../configure   --prefix=#{ism.settings.toolsPath}
                        --with-sysroot=#{ism.settings.rootPath}
                        --target=#{ism.settings.target}
                        --disable-nls
                        --disable-werror`
    elsif SoftwareInformation["Options"]["Pass2"]["Enabled"] == true
        `../configure   --prefix=/usr
                        --build=$(../config.guess)
                        --host=#{ism.settings.target}
                        --disable-nls
                        --enable-shared
                        --disable-werror
                        --enable-64-bit-bfd`
    else
        `../configure   --prefix=/usr
                        --enable-gold
                        --enable-ld=default
                        --enable-plugins
                        --enable-shared
                        --disable-werror
                        --enable-64-bit-bfd
                        --with-system-zlib`
    end
end

def build
    if SoftwareInformation["Options"]["Pass1"]["Enabled"] == true || SoftwareInformation["Options"]["Pass2"]["Enabled"] == true
        `make`
    else
        `make tooldir=/usr`
    end
end

def install
    if SoftwareInformation["Options"]["Pass1"]["Enabled"] == true
        `make -j1 install`
    elsif SoftwareInformation["Options"]["Pass2"]["Enabled"] == true
        `make DESTDIR=#{ism.settings.rootPath} install -j1`
        `install -vm755 libctf/.libs/libctf.so.0.0.0 #{ism.settings.rootPath}/usr/lib`
    else
        `make tooldir=/usr install -j1`
        `rm -fv /usr/lib/lib{bfd,ctf,ctf-nobfd,opcodes}.a`
    end
end

def uninstall

end

def enableOption(option)
    if SoftwareInformation["Options"][option] == "Pass1"
        SoftwareInformation["Options"]["Pass1"]["Enabled"] = true
        SoftwareInformation["Options"]["Pass2"]["Enabled"] = false
    elsif SoftwareInformation["Options"][option] == "Pass2"
        SoftwareInformation["Options"]["Pass2"]["Enabled"] = true
        SoftwareInformation["Options"]["Pass1"]["Enabled"] = false
    else
        SoftwareInformation["Options"][option]["Enabled"] = true
    end
end

def disableOption(option)
    SoftwareInformation["Options"][option]["Enabled"] = false
end

The error:


~/Documents/Programmation/ISM/Softwares/Binutils ❯❯❯ crystal 2.37.cr                                                                                                                                                          ✘ 1 master ✱ â—Œ
Showing last frame. Use --error-trace for full trace.

In 2.37.cr:1:27

 1 | SoftwaresDatabase = Array(Hash).new
                               ^---
Error: can't use Hash(K, V) as generic type argument yet, use a more specific type

This isn’t something you can do, you’d need to more specifically type the hash, like the error is suggesting. E.g. Array(Hash(String, String | Array(String) | ...)). However this is going to be a nightmare and I would highly suggest NOT using hashes like this. You are only going to have a hard time.

Instead I would suggest creating objects to manage this data:

class SoftwareProgram
  getter name : String
  getter architectures : Array(String)
  getter website : String

  def initialize(@name : String, @architectures : Array(String), @website : String); end
end

databases = Array(SoftwareProgram).new

databases << SoftwareProgram.new("Binutils", ["x86_64"], "https://www.gnu.org/software/binutils/")
# ...
1 Like

Okay. I think I need your advices, because I’m learning by myself from a long time how to programming, and I think my dark point is how I manage my data.

To explain, the file I linked to you is a file to describe how to download, build and install Binutils from the source to my software manager.

I don’t really know how to manage properly the data, but I guess if I register the data as class, it’s can be a problem later if I update this data no ? (I don’t want to use SQL or something like that )

Or I had an idea, maybe when I need to install a software, I can rebuild first the crystal file to avoid the problem with a new version of my class ?

Maybe i’m missing something but I don’t see how using a type would be any different than using a Hash. You’re going to need to recompile it if the data changes regardless of if you’re storing it in a Hash or another type unless you implement a way to read in the data at runtime, such as SQL or yaml etc.

If the data is immutable I’d suggest using a struct, or Top Level Namespace - Crystal 1.10.1. Otherwise a class would be the way to go.

Your logic itself doesn’t need to change that much as the types are just more explicitly representing the data already in the hash.

I.e. instead of if SoftwareInformation["Options"]["Pass1"]["Enabled"] == true it would be like SoftwareInformation.options.pass1.enabled?. Granted this is just an example. If the options are not actually hard coded you could still do something like SoftwareInformation.option("pass1").enabled? where the options are internally stored as a Hash(String, Option).

EDIT: To clarify, it’s not that using hashes is bad per say, but more so how you are using them. I.e. deeply nested with a lot of different types.