source: tol/tol/stdlib/TolPackage/def_catalog.tol @ be3dec5

1941-docker-image-to-testdevel
Last change on this file since be3dec5 was be3dec5, checked in by Pedro Gea <pgea@…>, 12 years ago

Refs #1821
Se mejora TolPackage a la version 4 que incorpora la gestión de paquetes según la plataforma.
Esta versión aúna TolPackage.2 (para tol.3.1) y TolPackage.3 (para tol.3.2) que fue separada por una incompatibilidad en OIS que luego fue revertida.

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@6526 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 22.0 KB
Line 
1
2//////////////////////////////////////////////////////////////////////////////
3// Record: [[
4//   Text identifier
5//   Text name
6//   Text variant (new)
7//   Real high
8//   Real low
9//   Text repository
10//   Date release
11//   Text platform (new)
12//   Text minTolVersion
13//   Text maxTolVersion
14//   Set freeDependencies
15//   Real ozaSize (extra)
16//   Real ozaTime (extra)
17// ]]
18
19//////////////////////////////////////////////////////////////////////////////
20Class @Catalog
21//////////////////////////////////////////////////////////////////////////////
22{
23  Text _.type;
24  Set _.records;
25  Set _rebuild;
26
27  ////////////////////////////////////////////////////////////////////////////
28  Real FindRecord(Text identifier)
29  ////////////////////////////////////////////////////////////////////////////
30  {
31    Set parts = Tokenizer(identifier, ".");
32    Text name = Tokenizer(parts[1], "#")[1];
33    Real index = FindIndexByName(_.records, name);
34    If(index, FindIndexByName(_.records[index], identifier), 0)
35  };
36
37  ////////////////////////////////////////////////////////////////////////////
38  Set GetRecord(Text identifier)
39  ////////////////////////////////////////////////////////////////////////////
40  {
41    Set parts = Tokenizer(identifier, ".");
42    Text name = Tokenizer(parts[1], "#")[1];
43    Real index = FindIndexByName(_.records, name);
44    If(index, {
45      Real index2 = FindIndexByName(_.records[index], identifier);
46      If(index2, _.records[index][index2], Copy(Empty))
47    }, Copy(Empty))
48  };
49
50  ////////////////////////////////////////////////////////////////////////////
51  Set GetRecord_Compatible(Text identifier)
52  ////////////////////////////////////////////////////////////////////////////
53  {
54    Set record = GetRecord(identifier);
55    If(Card(record),
56      If(_Record.IsCompatible(record, Platform(?), Version, UnknownDate),
57        record, Copy(Empty)),
58      Copy(Empty))
59  };
60
61  ////////////////////////////////////////////////////////////////////////////
62  Real _Record.IsCompatible(Set record, Text tolPlatform,
63    Text tolVersion, Date date)
64  ////////////////////////////////////////////////////////////////////////////
65  {
66    // Se comprueba la plataforma de TOL
67    Real ok = Case(tolPlatform=="", True,
68      record::platform=="", True,
69      // no hay limitaciones, fallarían los paquetes que se crearon válidos
70      // sólo para win32_msvc y unix32_gnu
71      True, TolPlatform::IsCompatibleWith(tolPlatform, record::platform));
72    Real If(ok, {
73      // Se comprueba la versión de TOL
74      Real ok := If(tolVersion=="any", True, {
75        TolVersion::CompareVersions(record::minTolVersion, tolVersion)<=0
76        & TolVersion::CompareVersions(tolVersion, record::maxTolVersion)<=0
77      })
78    });
79    If(ok, {
80      // Se comprueba que la fecha no es posterior a la indicada
81      If(""<<date=="Unknown", True, Not(record::release>date))
82    }, False)
83  };
84
85  ////////////////////////////////////////////////////////////////////////////
86  Text ObtainIdentifier(Text pattern, Text tolPlatform,
87    Text tolVersion, Date date)
88  ////////////////////////////////////////////////////////////////////////////
89  {
90    ObtainIdentifier_Show(pattern, tolPlatform, tolVersion, date, 1)
91  };
92
93  ////////////////////////////////////////////////////////////////////////////
94  Text ObtainLastCompatible(Text pattern)
95  ////////////////////////////////////////////////////////////////////////////
96  {
97    ObtainIdentifier(pattern, Platform(?), Version, UnknownDate)
98  };
99
100  ////////////////////////////////////////////////////////////////////////////
101  Text ObtainIdentifier_Show(Text pattern, Text tolPlatform, Text tolVersion,
102    Date date, Real show)
103  ////////////////////////////////////////////////////////////////////////////
104  {
105    // show: 0 none, 1 info, 2 warning, 3 error
106    Text identifier = _ObtainIdentifier(pattern, tolPlatform,
107      tolVersion, date);
108    If(Text Sub(identifier, 1, 1)=="#", {
109      Text wm = Case(show==0, "", show==1, "U", show==2, "W", show==3, "E");
110      Case(show==0, {
111        ""
112      }, identifier=="#NotCompatible", {
113        Text msg = Case(tolPlatform=="" & tolVersion=="any", "",
114          tolPlatform=="", " para la versión de TOL '"<<tolVersion<<"'",
115          tolVersion=="any", " para TOL con '"<<tolPlatform<<"'",
116          True, " para la versión de TOL '"<<tolVersion<<"' con '"
117            <<tolPlatform<<"'");
118        Text msg2 = If(""<<date=="Unknown", If(TextLength(msg), msg, ""), {
119          If(TextLength(msg), msg<<" y", " para")<<" la fecha "<<date
120        });
121        WriteLn("[TolPackage::@Catalog "<<_.type
122          <<"] No se encuentra el paquete '"<<pattern<<"'"<<msg2<<".", wm);
123          ""
124      }, identifier=="#NotFound", {
125        WriteLn("[TolPackage::@Catalog "<<_.type
126          <<"] No se encuentra el paquete '"<<Tokenizer(pattern, ".")[1]
127          <<"'.", wm);
128        ""
129      })
130    }, identifier)
131  };
132
133  ////////////////////////////////////////////////////////////////////////////
134  Text _ObtainIdentifier(Text pattern, Text tolPlatform,
135    Text tolVersion, Date date)
136  ////////////////////////////////////////////////////////////////////////////
137  {
138    Set parts = Tokenizer(pattern, ".");
139    Text name = Tokenizer(parts[1], "#")[1];
140    Real index = FindIndexByName(_.records, name);
141    If(index, {
142      Set records = _.records[index];
143      Set parts := parts << For(1, 3-Card(parts), Text (Real i) { "*" });
144      Text txtPattern1 = parts[1]<<"."<<parts[2]<<"."<<parts[3];
145      Text txtPattern2 = parts[1]<<"#*."<<parts[2]<<"."<<parts[3];
146      Set options = Select(records, Real (Set record) {
147        // Se comprueba si encaja con el patrón indicado
148        Real ok = TextMatch(record::identifier, txtPattern1)
149          | TextMatch(record::identifier, txtPattern2);
150        // Se comprueba la plataforma de TOL
151        Real If(ok, ok := _Record.IsCompatible(record, tolPlatform,
152          tolVersion, date));
153        // Se comprueba la versión de TOL
154        Real If(ok, ok := Case(tolVersion=="any", True,
155          TolVersion::CompareVersions(record::minTolVersion, tolVersion)>0,
156            False,
157          TolVersion::CompareVersions(tolVersion, record::maxTolVersion)>0,
158            False,
159          True, True));
160        // Se comprueba que la fecha no es posterior a la indicada
161        Real If(ok, ok := If(""<<date=="Unknown", True,
162          record::release<=date));
163        ok
164      });
165      If(Card(options), options[1]["identifier"], "#NotCompatible")
166    }, "#NotFound")
167  };
168
169  ////////////////////////////////////////////////////////////////////////////
170  Set GetFreeDependencies(Text pattern, Text tolPlatform, Text tolVersion,
171    Date date)
172  ////////////////////////////////////////////////////////////////////////////
173  {
174    Text identifier = ObtainIdentifier(pattern, tolPlatform, tolVersion, date);
175    If(TextLength(identifier), {
176      GetRecord(identifier)["freeDependencies"]
177    }, {
178      WriteLn("No se encuentra el paquete "<<pattern, "W");
179      Copy(Empty)
180    })
181  };
182
183  ////////////////////////////////////////////////////////////////////////////
184  Set GetMainDependencies(Text pattern, Text tolPlatform, Text tolVersion,
185    Date date)
186  ////////////////////////////////////////////////////////////////////////////
187  {
188    Set freeD = GetFreeDependencies(pattern, tolPlatform, tolVersion, date);
189    Set extraD = Unique(SetConcat(EvalSet(freeD, Set (Text p) {
190      GetFreeDependencies(p, tolPlatform, tolVersion, date)
191    })));
192    freeD-extraD
193  };
194
195  ////////////////////////////////////////////////////////////////////////////
196  Anything IdentifyPackages(Anything patterns, Text tolPlatform,
197    Text tolVersion, Date date)
198  ////////////////////////////////////////////////////////////////////////////
199  {
200    Case(Grammar(patterns)=="Set", {
201      EvalSet(patterns, Anything (Anything a) {
202        IdentifyPackages(a, tolPlatform, tolVersion, date)
203      })
204    }, Grammar(patterns)=="Text", {
205      If(TextLength(patterns),
206        ObtainIdentifier(patterns, tolPlatform, tolVersion, date), "")
207    })
208  };
209
210  ////////////////////////////////////////////////////////////////////////////
211  Set GetDeepDependencies(Text pattern, Text tolPlatform,
212    Text tolVersion, Date date)
213  ////////////////////////////////////////////////////////////////////////////
214  {
215    Set mainDs = GetMainDependencies(pattern, tolPlatform, tolVersion, date);
216    If(Card(mainDs), {
217      Unique(SetConcat(EvalSet(mainDs, Set (Text mainD) {
218        Set deepDs = GetDeepDependencies(mainD, tolPlatform, tolVersion, date);
219        If(Card(deepDs), {
220          Set [[ mainD ]] << deepDs
221        }, [[ mainD ]])
222      })))
223    }, Copy(Empty))
224  };
225
226  ////////////////////////////////////////////////////////////////////////////
227  Real Update(Real void)
228  ////////////////////////////////////////////////////////////////////////////
229  {
230    Case(_.type=="Local", {
231      Set _.records := @Catalog::BuildRecords_Local(_rebuild[1]);
232    1}, _.type=="Remote", {
233      Set _.records := @Catalog::BuildRecords_Remote(_rebuild[1]);
234    1}, _.type=="Stored", {
235      Set _.records := Include(_rebuild[1]);
236    1})
237  };
238
239  ////////////////////////////////////////////////////////////////////////////
240  Set GetRecords(Real void)
241  ////////////////////////////////////////////////////////////////////////////
242  {
243    Copy(_.records)
244  };
245
246  ////////////////////////////////////////////////////////////////////////////
247  Set GetCompatibleRecords(Text tolPlatform, Text tolVersion, Date date)
248  ////////////////////////////////////////////////////////////////////////////
249  {
250    If(tolPlatform=="" & tolVersion=="any" & ""<<date=="Unknown",
251      Copy(_.records), {
252      Set records = Select(EvalSet(_.records, Set (Set subrecords) {
253        Set selection = Select(subrecords, Real (Set record) {
254          _Record.IsCompatible(record, tolPlatform, tolVersion, date)
255        });
256        Real If(Card(selection), SetIndexByName(selection));
257        PutName(Name(subrecords), selection)
258      }), Card);
259      Real SetIndexByName(records);
260      records
261    })
262  };
263
264  ////////////////////////////////////////////////////////////////////////////
265  Set GetCompatibleList(Text tolPlatform, Text tolVersion, Date date)
266  ////////////////////////////////////////////////////////////////////////////
267  {
268    Set compRecords = GetCompatibleRecords(tolPlatform, tolVersion, date);
269    EvalSet(compRecords, Set (Set subrecords) { subrecords[1] })
270  };
271
272  ////////////////////////////////////////////////////////////////////////////
273  Set ObtainUpdateList(@Catalog reference, Text tolPlatform,
274    Text tolVersion, Date date)
275  ////////////////////////////////////////////////////////////////////////////
276  {
277    Set records = GetCompatibleRecords(tolPlatform, tolVersion, date);
278    Set refRecords = reference::GetCompatibleRecords(tolPlatform,
279      tolVersion, date);
280    SetConcat(EvalSet(refRecords, Set (Set refSubrecords) {
281      Real index = FindIndexByName(records, Name(refSubrecords));
282      If(index, {
283        Set subrecords = records[index];
284        // Se buscan todos los paquetes que necesiten ser actualizados
285        // (manteniendo los números de versión) por otra versión más reciente.
286        Set Select(EvalSet(refSubrecords, Set (Set refRecord) {
287          Real index2 = FindIndexByName(subrecords, Name(refRecord));
288          If(index2, {
289            Set record = subrecords[index2];
290            If(DateToIndex(refRecord::release)<DateToIndex(record::release), {
291              subrecords[index2] // se mantiene así el nombre del set
292            }, Empty)
293          }, Empty)
294        }), Card)
295      }, Empty)
296    }))
297  };
298
299  ////////////////////////////////////////////////////////////////////////////
300  Set ObtainUpgradeList(@Catalog reference, Text tolPlatform,
301    Text tolVersion, Date date)
302  ////////////////////////////////////////////////////////////////////////////
303  {
304    Set records = GetCompatibleRecords(tolPlatform, tolVersion, date);
305    Set refRecords = reference::GetCompatibleRecords(tolPlatform,
306      tolVersion, date);
307    SetConcat(EvalSet(refRecords, Set (Set refSubrecords) {
308      Real index = FindIndexByName(records, Name(refSubrecords));
309      If(index, {
310        Set subrecords = records[index];
311        // Se buscan todos los archivos que necesiten ser actualizados
312        // (o mejorados) por otra versión (con distintos números de versión).
313        Set lastRecord = subrecords[1];
314        Set refLastRecord = refSubrecords[1];
315        If(lastRecord::high > refLastRecord::high, {
316          [[ PutName(lastRecord::identifier, lastRecord) ]]
317        }, {
318          If(lastRecord::low > refLastRecord::low, {
319            [[ PutName(lastRecord::identifier, lastRecord) ]]
320          }, Empty)
321        })
322      }, Empty)
323    }))
324  };
325
326  ////////////////////////////////////////////////////////////////////////////
327  Set ObtainLowUpgradeList(@Catalog reference, Text tolPlatform,
328    Text tolVersion, Date date)
329  ////////////////////////////////////////////////////////////////////////////
330  {
331    Set records = RegroupRecords(GetCompatibleRecords(tolPlatform,
332      tolVersion, date));
333    Set refRecords = RegroupRecords(reference::GetCompatibleRecords(
334      tolPlatform, tolVersion, date));
335    SetConcat(EvalSet(refRecords, Set (Set refSubrecords) {
336      Real index = FindIndexByName(records, Name(refSubrecords));
337      If(index, {
338        Set subrecords = records[index];
339        // Se buscan todos los archivos que necesiten ser actualizados
340        // (o mejorados) por otra versión (con distintos números de versión).
341        Set lastRecord = subrecords[1];
342        Set refLastRecord = refSubrecords[1];
343        If(lastRecord::high > refLastRecord::high, {
344          [[ PutName(lastRecord::identifier, lastRecord) ]]
345        }, {
346          If(lastRecord::low > refLastRecord::low, {
347            [[ PutName(lastRecord::identifier, lastRecord) ]]
348          }, Empty)
349        })
350      }, Empty)
351    }))
352  };
353
354  ////////////////////////////////////////////////////////////////////////////
355  Set ObtainFullUpgradeList(@Catalog reference, Text tolPlatform,
356    Text tolVersion, Date date)
357  ////////////////////////////////////////////////////////////////////////////
358  {
359    Set fullList = ObtainUpgradeList(reference, tolPlatform, tolVersion, date)
360      << ObtainLowUpgradeList(reference, tolPlatform, tolVersion, date);
361    EvalSet(Classify(fullList, Real (Set r1, Set r2) {
362      Compare(Name(r1), Name(r2))
363    }), Set (Set group) { group[1] })
364  };
365
366  ////////////////////////////////////////////////////////////////////////////
367  Static @Catalog Local(Text path)
368  ////////////////////////////////////////////////////////////////////////////
369  {
370    @Catalog catalog = [[
371      Text _.type = "Local";
372      Set _rebuild = [[ path ]];
373      Set _.records = @Catalog::BuildRecords_Local(path)
374    ]]
375  };
376
377  ////////////////////////////////////////////////////////////////////////////
378  Static @Catalog Remote(Set repositories)
379  ////////////////////////////////////////////////////////////////////////////
380  {
381    @Catalog catalog = [[
382      Text _.type = "Remote";
383      Set _rebuild = [[ repositories ]];
384      Set _.records = @Catalog::BuildRecords_Remote(repositories)
385    ]]
386  };
387
388  ////////////////////////////////////////////////////////////////////////////
389  Static @Catalog Stored(Text file)
390  ////////////////////////////////////////////////////////////////////////////
391  {
392    @Catalog catalog = [[
393      Text _.type = "Stored";
394      Set _rebuild = [[ file ]];
395      Set _.records = Include(file)
396    ]]
397  };
398
399  ////////////////////////////////////////////////////////////////////////////
400  Static Set BuildRecords_Local(Text path)
401  ////////////////////////////////////////////////////////////////////////////
402  {
403    Set autodocs = Select(EvalSet(GetDir(path)[2], Set (Text package) {
404      Text autodocFile = path<<package<<"/info.oza";
405      Text autodocFileV = path<<package<<"/infov.oza";
406      Case(FileExist(autodocFileV), Ois.Load(autodocFileV)[1],
407        FileExist(autodocFile), Ois.Load(autodocFile)[1],
408        True, {
409        // Si no tiene info se obtiene de la lista A
410        Text ozaFile = path<<package<<"/"<<package<<".oza";
411        If(FileExist(ozaFile), {
412          Real ozaSize = FileBytes(ozaFile);
413          Text ozaId = package<<"."<<ozaSize;
414          TolPackage::AtListA(ozaId)
415        }, {
416          WriteLn("La carpeta local '"<<package
417            <<"' no contiene el archivo OZA de un paquete.", "W");
418          Copy(Empty)
419        })
420      })
421    }), Card);
422    Set records = EvalSet(autodocs, Set (Set autodoc) {
423      @Catalog::ObtainRecord_Autodoc(autodoc)
424    });
425    @Catalog::ClassifyRecords(records)
426  };
427
428  ////////////////////////////////////////////////////////////////////////////
429  Static Set BuildRecords_Remote(Set repositories)
430  ////////////////////////////////////////////////////////////////////////////
431  {
432    If(TolConfigManager::Config::Upgrading::TolPackage::LocalOnly, {
433      // Si la configuración está a local-only, no se accede al catálogo
434      // Podría plantearse una caché para trabajar sin conexión
435      Copy(Empty)
436    }, {
437      Set repositories_ = If(Card(repositories), repositories,
438        TolConfigManager::Config::Upgrading::TolPackage::Repositories);
439      Set rows = SetConcat(EvalSet(repositories_, Set (Text repository) {
440        Text urlAnswer = GetUrlContents(repository
441          <<"?tol_package_version="<<_.autodoc.version.high
442          <<"&action=versions");
443        If(Text Sub(urlAnswer, 1, 3)=="Set", IncludeText(urlAnswer), {
444          Error("El repositorio '"<<repository
445            <<"' no está respondiendo adecuadamente.");
446          Empty
447        })
448      }));
449      Set records = EvalSet(rows, Set (Set row) {
450        Set pkgInfo = Tokenizer(row[3], ".");
451        Set freeDeps = Unique(Select(EvalSet(Tokenizer(row[7], ","),
452          Text (Text t) { Tokenizer(t, ".")[1] }), TextLength));
453        Set name_variant = Tokenizer(pkgInfo[1], "#");
454        Set extra_info = If(TextLength(row[4]), {
455          SetConcat(EvalSet(Tokenizer(row[4], "\n"), Set (Text line) {
456            Real pos = TextFind(line, "=");
457            If(pos, {
458              Text name = TextSub(line, 1, pos-1);
459              Text value = TextSub(line, pos+1, -1);
460              [[PutName(name, value)]]
461            }, Copy(Empty))
462          }))
463        }, Copy(Empty));
464        Set pkgRecord =  [[
465          Text identifier = row[3];
466          Text name = name_variant[1];
467          Text variant = If(Card(name_variant)>1, name_variant[1], "");
468          Real high = Eval(pkgInfo[2]);
469          Real low = Eval(pkgInfo[3]);
470          Text repository = row[1];
471          Date release = row[8];
472          Text platform = getOptArg(extra_info, "platform", "");
473          Text minTolVersion = row[5];
474          Text maxTolVersion = row[6];
475          Set freeDependencies = freeDeps
476        ]];
477        PutName(row[3], pkgRecord)
478      });
479      @Catalog::ClassifyRecords(records)
480    })
481  };
482
483  ////////////////////////////////////////////////////////////////////////////
484  Static Set ObtainRecord_Autodoc(Set autodoc)
485  ////////////////////////////////////////////////////////////////////////////
486  {
487    Text variant = getOptArg(autodoc, "_.autodoc.variant", "");
488    Set pkgRecord = [[
489      Text identifier = autodoc::_.autodoc.name
490        <<If(TextLength(variant), "#"<<variant, "")
491        <<"."<<autodoc::_.autodoc.version.high
492        <<"."<<autodoc::_.autodoc.version.low;
493      Text name = autodoc::_.autodoc.name;
494      Text variant;
495      Real high = autodoc::_.autodoc.version.high;
496      Real low = autodoc::_.autodoc.version.low;
497      Text repository = autodoc::_.autodoc.url;
498      Date release = autodoc::_.autodoc.releaseDate;
499      Text platform = getOptArg(autodoc, "_.autodoc.platform", "");
500      Text minTolVersion = autodoc::_.autodoc.minTolVersion;
501      Text maxTolVersion = If(
502        ObjectExist("Text", "autodoc::_.autodoc.maxTolVersion"),
503        autodoc::_.autodoc.maxTolVersion, "v999");
504      Set freeDependencies = Unique(EvalSet(autodoc::_.autodoc.dependencies,
505        Text (Text dep) { Tokenizer(dep, ".")[1] }))
506    ]];
507    PutName(pkgRecord::identifier, pkgRecord)
508  };
509
510  ////////////////////////////////////////////////////////////////////////////
511  Static Set FormatRecord_Autodoc(Set record)
512  ////////////////////////////////////////////////////////////////////////////
513  {
514    Set autodoc = [[
515      Text _.autodoc.name = record::name;
516      Text _.autodoc.variant = record::variant;
517      Real _.autodoc.version.high = record::high;
518      Real _.autodoc.version.low = record::low;
519      Text _.autodoc.url = record::repository;
520      Text _.autodoc.platform = record::platform;
521      Date _.autodoc.releaseDate = record::release;
522      Text _.autodoc.minTolVersion = record::minTolVersion;
523      Text _.autodoc.maxTolVersion = record::maxTolVersion;
524      Set _.autodoc.dependencies = record::freeDependencies
525    ]];
526    PutName(record::identifier, autodoc)
527  };
528
529  ////////////////////////////////////////////////////////////////////////////
530  Static Set ClassifyRecords(Set flatCatalog)
531  ////////////////////////////////////////////////////////////////////////////
532  {
533    Set catalog0 = Classify(flatCatalog,
534      Real (Set rec1, Set rec2) { Compare(rec1::name, rec2::name) });
535    Set hierCatalog = EvalSet(catalog0, Set (Set class) {
536      Set sortedClass = Sort(class, Real (Set rec1, Set rec2) {
537        Real compareHigh = Compare(rec2::high, rec1::high);
538        If(compareHigh, compareHigh, Compare(rec2::low, rec1::low))
539      });
540      Real SetIndexByName(sortedClass);
541      PutName(class[1]["name"], sortedClass)
542    });
543    Real SetIndexByName(hierCatalog);
544    hierCatalog
545  };
546
547  ////////////////////////////////////////////////////////////////////////////
548  Static Set RegroupRecords(Set records)
549  ////////////////////////////////////////////////////////////////////////////
550  {
551    SetConcat(EvalSet(records, Set (Set subrecords) { 
552      EvalSet(Classify(subrecords, Real (Set rec1, Set rec2) {
553        Compare(rec1::high, rec2::high)
554      }), Set (Set class) {
555        Set class2 = Sort(class, Real (Set rec1, Set rec2) {
556          Compare(rec2::low, rec1::low)
557        });
558        PutName(class[1]["name"]<<"."<<class[1]["high"], class2)
559      })
560    }))
561  }
562};
563//////////////////////////////////////////////////////////////////////////////
Note: See TracBrowser for help on using the repository browser.