Como trabalhar com subdiretorios

Fórum sobre a linguagem CA-Clipper.

Moderador: Moderadores

ROMARIO
Usuário Nível 1
Usuário Nível 1
Mensagens: 35
Registrado em: 06 Jul 2004 11:25
Localização: AGUDO - RS

Como trabalhar com subdiretorios

Mensagem por ROMARIO »

Senhores clipeiros (experientes).

Num sistema "FISCAL" gostaria de trabalhar assim:

c:\FISCAL\EMPR01-DADOS
\EMPR02-DADOS ETC., ONDE:

Dados comuns a várias empresas estivessem no diretório principal e os dados de cada empresa estivessem no respectivo subdiretório.

Se todos os dados estivessem no respectivo subdiretorio poderiamos usar o set default to...mas como tem dados no diretorio principal devemos usar o "&" para acessar a pasta, certo ??.

Tem alguma maneira mais profissional ?. Alguem que já fez programas comerciais já passou por isso. Gostaria de uma opinião.

Obrigado.

Romario.
Domenico
Usuário Nível 1
Usuário Nível 1
Mensagens: 41
Registrado em: 02 Out 2004 16:03
Localização: São Paulo
Contato:

Diretórios

Mensagem por Domenico »

Prezado Romario,

Não sei qual versão do clipper vc está utilizando.

Se vc estiver utilizando a versão 5.3 tem a mais as seguintes funções para manipulação de diretórios:

Em ordem alfabetica

DIRCHANGE()
Sintaxi

DIRCHANGE(<cDir>) --> nExito

Argumentos

<cDir> é o nome, incluída a unidada, do novo diretório actual.

Devolve

DIRCHANGE() devolve 0 se houve exito e -1 se possuir um argunto não válido. Do contrário, DIRCHANGE() devolve um código de erro
do DOS.


Descrção

DIRCHANGE() altera o diretório atual do DOS. Também pode se usado
para comprovar a existência de um diretório.


DIRMAKE()

DIRMAKE(<cNovoDir>) --> nExito

Argumentos

<cNovoDir> é o nome do diretório que se pretende criar e
inclue, opcionalmente, a unidade. Se não se especifica a unidade, se
utiliza a atual.

Devolve

DIRMAKE() devolve 0 se houve exito e -1 se tiver um argumento incorreto.
Do contrário, devoelve um código de erro do DOS.


Descriç~~ao

DIRMAKE() cria o direcório especificado. Lembre-se que para criar um
diretório deve-se ter as permissões adequadas. Para criar
diretórios aninhados, deve-se criar cada sub-diretório de forma
independente, começando pelo diretório de nvel superior.


DIRREMOVE()
Sintaxi

DIRREMOVE(<cNomeDir>) --> nExito

Argumentos

<cNomeDir> é o nome do diretório que se pretende eliminar e
inclue, opcionalmente, a unidade. Se não se especifica uma unidade, s
se utiliza a atual.

Devolve

DIRREMOVE() devolve 0 se houve exito e -1 se tiver um argumento
incorreto. Deo contrário, devolve um código de erro do DOS.


Descrição

DIRREMOVE() apaga um diretório. Lembre-se que para apagar um
diretório deve-se ter as permissões adequadas e que deve estar
vázío. Portanto, para apagar um diretório com sub-diretórios, é
preciso apagar primeiro os sub-diretrios.



A função DIRECTORY() continua no 5.3 e funciona da mesma maneira que no 5.2

Espero ter sido bastante ilustrativo para vc e para quem precisar.
Avatar do usuário
Maligno
Membro Master
Membro Master
Mensagens: 6398
Registrado em: 06 Jul 2004 01:40
Localização: Londrina/PR

Mensagem por Maligno »

Sempre trabalhei com sistemas multi-empresas e sempre preferi o endereçamento direto dos arquivos de dados (nunca aceitei usar o conceito de diretório default). Assim, eu monto o caminho completo, anexando-o ao nome do arquivo. Procedendo desta forma, me livrei da obrigação da troca de diretório.
Imagino (só imagino) que a tendência seja usar esse tipo de endereçamento direto, dadas as suas vantagens. É mais fácil (evita o troca-troca de diretórios - é um trabalho chato) e, principalmente, mais seguro (não requer a lembrança de retornar ao diretório, se houver troca).

[]'s
Maligno
http://www.buzinello.com/prg
Avatar do usuário
Toledo
Administrador
Administrador
Mensagens: 3133
Registrado em: 22 Jul 2003 18:39
Localização: Araçatuba - SP
Contato:

Mensagem por Toledo »

Romario,

No seu programa deve ter um cadastro das empresas, coloque neste cadastro um campo com o caminho da pasta onde vão ficar os arquivos exclusivos desta empresa, pode ser um campo oculto para o usuário, tipo: C:\FISCAL\EMPR01\ (não esquecer da barra no final).
Depois quando você selecionar esta empresa, passe o conteúdo deste campo para uma variável.

cPasta:=ALLTRIM(nomedocampo) // tira todos os espaços em branco

Agora, ao abrir um arquivo correspondente a empresa, coloque o caminho antes do nome do arquivo DBF ou NTX que será aberto.

db_f:=cPasta+"ARQDBF"
SELE 0
USE (db_f) SHARED

Abraços,
Toledo - Clipper On Line
toledo@pctoledo.com.br
Harbour 3.2/MiniGui/HwGui
Faça uma doação para o fórum, clique neste link: http://www.pctoledo.com.br/doacao
Avatar do usuário
Clipper
Colaborador
Colaborador
Mensagens: 1334
Registrado em: 23 Ago 2004 00:04
Localização: Recife/PE

Mensagem por Clipper »

Se você não usa a versão 5.3 pode utilizar uma LIB que tenha funçõe de manipilação de diretórios. Você pode usar uma destas abaixo :

CLIPPER TOOLS
FAST LIB
CLIPON

Até logo.

Marcelo
Programador que é programador, quando tá de folga vai inventar função nova, fazer testes, ou seja... se divertir
Cobra 210 - Drive de 8" 1.024 KB - 64 KB RAM - Impressora de Linha Cobra - Visicalc - Fortran - Dialog - Sistema Operacional SP/M (é sp/m mesmo - era o cp/m da cobra)
Avatar do usuário
Maligno
Membro Master
Membro Master
Mensagens: 6398
Registrado em: 06 Jul 2004 01:40
Localização: Londrina/PR

Mensagem por Maligno »

Toledo escreveu:Depois quando você selecionar esta empresa, passe o conteúdo deste campo para uma variável.
O único ponto que eu discordo é o de gravar partes do nome do caminho, incluindo barras, num campo de DBF. Não é uma boa idéia. Primeiro por causa do desperdício. Segundo que, se houver necessidade do projeto ter uma parte do nome de caminho alterado, você precisará fazer uma gambiarra pra ajustar os nomes já gravados.

Eu gravo no DBF apenas a identificação exclusiva da empresa. Assim, meu USE (não uso USE para nada - uso aqui só para exemplo) ficaria:

Código: Selecionar todos

use (DiskDef()+":"+EmpDatDir()+"\+CADFUN."+SIGLAEMP) alias EMPRES new
As funções DiskDef(), que retorna a letra do disco usado, e EmpDatDir(), que retorna o nome completo do caminho do diretório onde são armazenados os dados das empresas, servem apenas para abstrair essas strings constantes. Posso usar isso em várias partes de um projeto que poderá ultrapassar facilmente as 100.000 linhas. No caso de uma alteração, bastará alterar um único ponto e recompilar. Se não fosse assim, eu teria que achar todos os pontos em que essas strings foram usadas. Trabalho mais que dobrado.

Neste exemplo, para a abertura do cadastro de funcionários, SIGLAEMP é o campo do arquivo de dados que armazena a sigla de identificação da empresa. Para minha conveniência, é uma string de apenas 3 letras, usada como extensão do arquivo. Ou seja, todas as empresas, nos seus respectivos diretórios têm os mesmos nomes de arquivos. A diferença, portanto, fica apenas na extensão destes.

[]'s
Maligno
http://www.buzinello.com/prg
Avatar do usuário
Toledo
Administrador
Administrador
Mensagens: 3133
Registrado em: 22 Jul 2003 18:39
Localização: Araçatuba - SP
Contato:

Mensagem por Toledo »

Romário,

Seguindo o exemplo do nosso amigo Maligno, você pode utilizar a LIB abaixo para retornar o diretório corrente.

https://pctoledo.org/forum/dload. ... ile_id=130

Exemplo:

use (QUALDIR()+"\ARQDBF."+SIGLAEMP)

A função QUALDIR() é diferente da CURDIR() do Clipper, nesta última a letra do drive não é retornado.

Abraços,
Toledo - Clipper On Line
toledo@pctoledo.com.br
Harbour 3.2/MiniGui/HwGui
Faça uma doação para o fórum, clique neste link: http://www.pctoledo.com.br/doacao
ROMARIO
Usuário Nível 1
Usuário Nível 1
Mensagens: 35
Registrado em: 06 Jul 2004 11:25
Localização: AGUDO - RS

Mensagem por ROMARIO »

Obrigado a todos que responderam a minha dúvida (Toledo, Domenico e Maligno).

Foi muito esclarecedor. Mas, fiquei tentado, com a resposta do Maligno. (não uso o "USE"). Como não sou "expert" em programação, fiquei curioso em saber como você faz os teus sistemas (na parte de subdiretórios). Por isso se não for abuso, gostaria que postasse aqui ou me enviasse em private um trecho de como você faz esta parte do programa. Reafirmo que achei muito interessante o modo como descreveste o problema e como quero justamente resolver esse aspecto de selecionar o subsdiretorio, voltar para o principal, selecionar subsdiretório de outra empresa, etc. (que muito chato) gostaria de mais detalhes sobre isso (se possível).

Antecipadamente agradeço.

Abraços a todos.

Romario.

rkilian@terra.com.br
Avatar do usuário
Maligno
Membro Master
Membro Master
Mensagens: 6398
Registrado em: 06 Jul 2004 01:40
Localização: Londrina/PR

Mensagem por Maligno »

ROMARIO escreveu:fiquei curioso em saber como você faz os teus sistemas
Com relação à indicação dos diretórios, é como eu disse: não uso e pronto. Você pode fazer assim também. Apenas esqueça a existência de coisas como SET DEFAULT e explicite os nomes dos diretórios, junto aos nomes dos arquivos que deverão ser abertos. Só isso.

Agora, com relação ao fato de eu não usar o comando USE, a explicação é mais longa. Já há muitos anos, percebendo as dificuldades de utilização do sistema tradicional de abertura, criei o conceito de grupos de arquivos de dados. Primeiro, veja as dificuldades. Imagine que você precisa abrir 10 arquivos de dados e seus índices. Dentro deste grupo de 10 arquivos, 5 são essenciais, 4 são opcionais (se não for possível abrir, não haverá problema) e 1 é temporário e deverá estar "zerado". Pelo método tradicional, imagine se a abertura do oitavo arquivo falhar. Todos os sete arquivos anteriores terão de ser fechados. Se você der um dbCloseAll(), resolve, mas isso fechará todos, inclusive os arquivos abertos em funções anterior à esta, que precisam continuar aberto. Assim, será melhor fechar um a um. Serão muitos e muitos IFs. Tudo bem que o programador é um profissional que deve se acostumar ao trabalho pesado, mas assim é um desperdício enorme de produtividade.

Então, resolvi montar um sub-sistema de manutenção de arquivos de dados, que deveria me livrar desse tipo de pesadelo.
Ele funciona de maneira bem simples. Cada arquivo de dados tem sua geometria (dados dos arquivos (DBF e índices) e campos) definida em uma função nomeada de forma padronizada. Algo do tipo:

Código: Selecionar todos

DatCadfun()  -->  Cadastro de funcionários
DatDocEnt()  -->  Documentos fiscais de entrada
DatDocSai()  -->  Documentos fiscais de saída
DatProFis()  -->  Processamento dos dados fiscais
Veja como é uma das minhas funções de geometria mais simples:

Código: Selecionar todos

function DatCtaRec(nOut)
local cAlias := "CtaRec"
local cID    :=  SigEmpSel(cAlias)
dbServReq(nOut)
defDBF as "CTAREC."+cID at EmpDatDir(cID) alias cAlias comment "Contas a Receber"
defIDX at EmpIdxDir(cID)
   defKey "CODIGO"                                  alias "iCTR_CODIGO"
   defKey "CONJUNTO+NUMERO"                         alias "iCTR_CNJNRO"
   defKey "NUMERO"                                  alias "iCTR_NRO"
   defKey "DTOS(VENCIMENTO)+NUMERO"                 alias "iCTR_VNCNRO"
   defKey "DTOS(PAGAMENTO)+NUMERO"                  alias "iCTR_PAGNRO"
   defKey "DTOS(PAGAMENTO)+DTOS(VENCIMENTO)+NUMERO" alias "iCTR_PAGVNCNRO"
   defKey "CLIENTE+NUMERO"                          alias "iCTR_CLINRO"
   defKey "CLIENTE+DTOS(VENCIMENTO)+NUMERO"         alias "iCTR_CLIVNCNRO"
   *
defStruct
   defFld "CODIGO"     cha  6   // código de identificação no sistema
   defFld "CONJUNTO"   cha  6   // conjunto do qual esta conta faz parte
   defFld "CLIENTE"    cha  6   // código do cliente
                                // 
   defFld "NUMERO"     cha  9   // número do documento
   defFld "DATAEMISS"  dat      // data de emissão da duplicata
   defFld "VENCIMENTO" dat      // data do vencimento
   defFld "PAGAMENTO"  dat      // data do pagamento
   defFld "VALOR"      num  9,2 // valor total
   defFld "DESCONTO"   num  9,2 // valor do desconto concedido
   defFld "ACRESCIMO"  num  9,2 // valor do acréscimo cobrado
                                // 
   defFld "DPLSPEND"   log      // se verdadeiro, vínculo vazio
   defFld "DOCORIGEM"  num  1   // documento de origem: 1=DocSai 2=DocSer
   defFld "CODORIGEM"  cha  6   // código deste documento de origem
                                // 
   defFld "LSTUPDINFO" cha 31   // última alteração
   defFld "VER001_CRC" cha  8   // versão da estrutura e CRC32 do registro
   *
return dbCloseReq()
Qualquer manipulação em arquivos de dados representa a invocação de um código de serviço. A abertura chamará esta função (por macro-substituição) com um código próprio. A função dbServReq() na quarta linha inicia o serviço e a função dbCloseReq() informa que o serviço foi completado. Com o código apropriado, uma função do sub-sistema vai recebendo o conteúdo de cada comando listado acima e, de acordo com o código de serviço, "filtrará" o que for necessário, descartando o resto. Se, por exemplo, na abertura o arquivo não for encontrado e houver um comando de criação, esta função será novamente chamada, com o código de requisição de estrutura. Aquela função do sistema, percebendo o código diferente, "filtrará" as informações acima, acumulando em matriz apenas as informações a respeito da estrutura do arquivo.

Aliás, note que na quinta linha eu defino o diretório onde reside (ou será criado) o arquivo. É a parte at EmpDatDir().

Essa é a descrição sucinta de como é a base dos dados dos arquivos, que me fornecem apenas informações.

Fiz esse sub-sistema para que eu não tivesse mais que ficar repetindo o trabalho enfadonho de ter que ficar testando a abertura de cada arquivo de dados e índices. Assim, com uma série de comandos criados especialmente, o exemplo que dei sobre os 10 arquivos DBF ficariam, no meu sub-sistema, assim:

Código: Selecionar todos

dbDefGroup Arq_01 create      vital exclusive reindexIF .T.
dbDefGroup Arq_02                             reindexIF  TstReindex()
dbDefGroup Arq_03 create      vital           reindexIF .T.
dbDefGroup Arq_04                   exclusive reindexIF  TstReindex()
dbDefGroup Arq_05 create      vital exclusive
dbDefGroup Arq_06 create      vital
dbDefGroup Arq_07 create      vital exclusive reindexIF .T.
dbDefGroup Arq_08                   exclusive
dbDefGroup Arq_09 create      vital           reindexIF .T.
dbDefGroup TmpExp create kill vital
*
if !dbOpenGrp("MyGroup")
   // Se o retorno foi FALSE, significa que nem todos os arquivos com
   // a cláusula vital puderam ser abertos. Assim sendo, fim!
   //
   return nil
end
Muito mais fácil. Sem os comentários, são apenas 13 linhas de código bem legível e fácil de manter, que abrirão os arquivos que puderem ser abertos, criarão esses DBFs se não existirem, criarão todas as chaves de índices (eu uso a LIB SIX) e reindexarão, se for necessário ou possível (neste exemplo, em alguns arquivos a reindexação dependerá do retorno da função TstReindex()). E ainda por cima, apagará fisicamente o arquivo temporário (cláusula KILL) no fechamento do grupo. Eu não exemplifiquei todos os recursos. Há outras cláusulas e funções de apoio.

Agora, tente fazer isso tudo usando o método tradicional. A quantidade de código será muito maior. Isso num único ponto do programa. Imagine num sistema com dezenas de aberturas como essa.

Mas, além da facilidade demonstrada acima, há algumas características interessantes. Através desse sistema de grupos, posso, por exemplo, abrir o mesmo DBF 2 ou mais vezes, ao mesmo tempo. Explico: depois que o grupo é "cadastrado" pelos comandos acima, a função dbOpenGrp() tentará abrir os arquivos um a um. Antes de tentar abrir um arquivo, ela verificará se em seu repositório de controle já existe, num grupo aberto anteriormente, um DBF com a mesma identificação (alias). Se não existir, o processo de abertura prossegue normalmente. Se existir, uma nova instância de controle é criada, e todas as características de posicionamento e filtragem são armazenadas. Assim, quando esse grupo for fechado, tais características (SCOPE, FILTER, RecNo()) serão restauradas para que fiquem exatamente como estavam antes da abertura do grupo. No limite da possibilidade, claro.

O sistema é mais extenso que isso, mas acho que você já deve ter tido uma boa noção do porquê eu não uso mais o velho USE. Com um sistema desses, não preciso mais dele, felizmente.

[]'s
Maligno
http://www.buzinello.com/prg
Jorge Adourian
Usuário Nível 2
Usuário Nível 2
Mensagens: 95
Registrado em: 05 Jul 2004 23:38
Localização: São Paulo-SP-Brasil
Contato:

Mensagem por Jorge Adourian »

Maligno, primeiramente parabens pela sua criatividade em criar este sub-sistema de abertura, como você chama.

Agora, vamos colocar os pingos nos Is.

Não diga: "Não uso o comando USE"
Diga: "Não uso o comando USE de forma direta e sim por meio do sub-sistema de abertura que criei". Afinal no dbOpenGrp("MyGroup") óbviamente você tem que usar o comando USE.

Da forma como você colocou, a grande maioria dos colegas, fica achando que você é mágico!

Quando falo do USE estou falando do DBUSEAREA() é claro!
Até...
Jorge Adourian
Clipper5.2e, Blinker7.0, SIX2(NSX), ADS7.1, FW2.3c, PrintFile2.1.5 e PDFCreator0.8.0(2)
Avatar do usuário
Clipper
Colaborador
Colaborador
Mensagens: 1334
Registrado em: 23 Ago 2004 00:04
Localização: Recife/PE

Mensagem por Clipper »

Prezado Maligno

Eu concordo com o Jorge, também fiquei muito curioso com o tal "não uso o USE", passei umas 2 horas pensando como poderia ser, acabei desistindo e esperei a resposta...

Até logo.

Marcelo
Programador que é programador, quando tá de folga vai inventar função nova, fazer testes, ou seja... se divertir
Cobra 210 - Drive de 8" 1.024 KB - 64 KB RAM - Impressora de Linha Cobra - Visicalc - Fortran - Dialog - Sistema Operacional SP/M (é sp/m mesmo - era o cp/m da cobra)
ROMARIO
Usuário Nível 1
Usuário Nível 1
Mensagens: 35
Registrado em: 06 Jul 2004 11:25
Localização: AGUDO - RS

Mensagem por ROMARIO »

Caros colegas clippeiros !!

Agradeço novamente a todos que responderam a minha dúvida.

Na verdade, pelas respostas dadas, todos tem uma maneira particular de resolver o problema colocado.

Resumindo ou se usa o "&" (macro substituição) para acessar o diretório, ou se referencia diretamenta o subsdiretório (explicitando todo o caminho ex.: c:\fiscal\empr01\cadfun.dbf) ou se faz com funções como demonstrou o Maligno. Todos válidos. Era essa a minha dúvida. Pensava que poderia haver algum outro comando ou função do clipper que eu não sabia.
Sei que tem funções feitas em outras linguagens (C,Assembly,etc)que fazem o requerido. Mas eu particularmente gosto de programas em Clipper puro (Não sei nada de c, assembly etc.), não gosto de usar Lib de espécie nenhuma fornecida por terceiros (não sei exatamente o que contem), gosto de 'ENTENDER" o que estou fazendo (senão compraria programas prontos) e se não for possível realizar com o clipper puro, prefiro mudar de linguagem (alguma mais completa, se existir.)

Obrigado.

Romario
Avatar do usuário
Maligno
Membro Master
Membro Master
Mensagens: 6398
Registrado em: 06 Jul 2004 01:40
Localização: Londrina/PR

Mensagem por Maligno »

Clipper escreveu:fiquei muito curioso com o tal "não uso o USE", passei umas 2 horas pensando como poderia ser, acabei desistindo e esperei a resposta...
Me desculpe, Marcelo. Acho que não fui claro na minha exposição. Numa mensagem longa como aquela, é compreensível que alguma coisa escape, ou que não fique bem explicada.

Quando eu disse que não uso o comando USE, quis dizer que não uso este comando na abertura dos arquivos em cada momento, em cada fonte. Uso, naturalmente, a função que criei para o sistema de abertura de grupos de arquivos.
Neste sistema uso (ainda assim, só uma vez) a função dbUseArea(), que equivale ao comando USE. Até porquê sou obrigado a usá-la, já que o arquivo aberto precisará ser manipulado pelo sistema de gerenciamento de arquivos do Clipper.

É quase como o comando GET. Não uso também. Tenho outro sistema, bem melhor. O comando equivalente ao GET é outro, claro. Pertence ao meu sub-sistema. Mas dentro deste utilizo, para todo o programa, um único pseudo-objeto GET, criado no início do programa por uma chamada à função _GET_(), que equivale ao comando GET. A diferença com relação ao sistema de arquivos é que no caso dos GETs não sou obrigado a utilizar essa função. A qualquer momento posso descartá-la e criar meu próprio pseudo-objeto. Aliás, até ficaria melhor.

[]'s
Maligno
http://www.buzinello.com/prg
Avatar do usuário
Maligno
Membro Master
Membro Master
Mensagens: 6398
Registrado em: 06 Jul 2004 01:40
Localização: Londrina/PR

Mensagem por Maligno »

ROMARIO escreveu:não gosto de usar Lib de espécie nenhuma fornecida por terceiros (não sei exatamente o que contem), gosto de 'ENTENDER" o que estou fazendo (senão compraria programas prontos) e se não for possível realizar com o clipper puro, prefiro mudar de linguagem (alguma mais completa, se existir.)
Note que em qualquer linguagem que for programar você não terá como escapar de utilizar códigos de terceiros, aos quais você não teve nem terá acesso. É preciso confiar, pura e simplesmente. O próprio Clipper tem montes de códigos que você nunca verá. Mas você usa e confia. Se não por opção, por falta de alternativas.

Usar LIBs de terceiros não sugere de imediato enfrentar problemas desconhecidos, produzidos por códigos mal escritos. Você dá um voto de confiança, usa e, se algum problema aparecer, discute com o desenvolvedor. Se isso não for possível, abandona a LIB e arranja uma alternativa.

Por fim, e voltando à questão dos nomes de diretórios, acho que você já tem informações suficientes para poder escolher a opção que melhor se encaixará na sua filosofia de trabalho.

[]'s
Maligno
http://www.buzinello.com/prg
Avatar do usuário
Clipper
Colaborador
Colaborador
Mensagens: 1334
Registrado em: 23 Ago 2004 00:04
Localização: Recife/PE

Mensagem por Clipper »

Prezado Romario

Não vejo problema algum em usar LIBs externas, creio que em qualquer linguagem você precisará usar códigos, libs, apps, dlls de terceiros, porque nenhuma linguagem consegue abranger tudo, é impossível visto que a cada dia surgem novos equipamentos, sistemas operacionais e nenhuma liguagagem tem o poder de prever o futuro (pelo menoas ainda), além do mais a maioria das libs do clipper foram criadas com o aval da CA, e a maioria delas tem manuais completos, da maneira que você coloca então não deveria usar também o Clipper e sim o C que é a lingaugem de desenvolvimento do Clipper, eu uso libs externas a muito tempo e nunca tive problemas a maioria são estaveis e confiáveis.

Até logo.

Marcelo
Programador que é programador, quando tá de folga vai inventar função nova, fazer testes, ou seja... se divertir
Cobra 210 - Drive de 8" 1.024 KB - 64 KB RAM - Impressora de Linha Cobra - Visicalc - Fortran - Dialog - Sistema Operacional SP/M (é sp/m mesmo - era o cp/m da cobra)
Responder