diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-17 19:08:13 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-17 19:08:13 +0200 |
| commit | 9b18e17eee451a9a900f792add2c4c2eeb2af533 (patch) | |
| tree | 7466a0953c5a4c9d7b3200723baf4d21a9bf225d | |
| parent | f1be1bef9c3563fd78f811f54e5a64b1d993e4da (diff) | |
| download | conky-theme-udt-9b18e17eee451a9a900f792add2c4c2eeb2af533.tar.gz conky-theme-udt-9b18e17eee451a9a900f792add2c4c2eeb2af533.zip | |
feat: parse the CPU and motherboard model strings
Stripping by rule rather than by a table of known parts: a leading vendor
word, trademark noise, a trailing core count or clock. The Intel fixture
exists to keep it a rule.
DMI placeholders read as no board at all. 'To Be Filled By O.E.M.' on the
dashboard is worse than a blank row.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| -rw-r--r-- | lib/data.lua | 65 | ||||
| -rw-r--r-- | test/fixtures/proc_cpuinfo | 12 | ||||
| -rw-r--r-- | test/fixtures/proc_cpuinfo_intel | 6 | ||||
| -rw-r--r-- | test/test_data.lua | 35 |
4 files changed, 118 insertions, 0 deletions
diff --git a/lib/data.lua b/lib/data.lua index 13e5ebd..1afc9c9 100644 --- a/lib/data.lua +++ b/lib/data.lua @@ -297,4 +297,69 @@ function M.kv_parse(text) return out end +-- The CPU's marketing name, stripped to what identifies it. +-- +-- '/proc/cpuinfo' repeats the model name once per thread; the first is enough. +-- The stripping is rules, not a table of known CPUs: a leading vendor word, +-- the registered-trademark noise, and a trailing core count or clock, all of +-- which are constant boilerplate that costs a third of the row on a card +-- measured in pixels. +-- +-- AMD Ryzen 7 9700X 8-Core Processor -> Ryzen 7 9700X +-- Intel(R) Core(TM) i7-8700K CPU @ 3.7GHz -> Core i7-8700K +function M.cpu_model(cpuinfo) + if type(cpuinfo) ~= 'string' then return nil end + local s = cpuinfo:match('model name%s*:%s*([^\n]+)') + if not s then return nil end + s = s:gsub('%(R%)', ''):gsub('%(TM%)', ''):gsub('%(tm%)', '') + s = s:gsub('^%s*AMD%s+', ''):gsub('^%s*Intel%s+', '') + s = s:gsub('%s+%d+%-Core Processor.*$', '') + s = s:gsub('%s+CPU%s*@.*$', '') + s = s:gsub('%s+Processor%s*$', '') + s = s:gsub('%s+', ' '):gsub('^%s+', ''):gsub('%s+$', '') + if s == '' then return nil end + return s +end + +-- The motherboard, from the two world-readable DMI files. +-- +-- board_vendor and board_name are readable without privilege, unlike the +-- serial fields in the same directory. The vendor's corporate suffix is +-- dropped because every vendor has one and none of it identifies the board. +-- +-- A board reporting the DMI placeholder is treated as no board at all: +-- 'To Be Filled By O.E.M.' on the dashboard is worse than a blank row. +local DMI_PLACEHOLDER = { + ['to be filled by o.e.m.'] = true, + ['system manufacturer'] = true, + ['default string'] = true, + ['unknown'] = true, + ['n/a'] = true, +} + +local function dmi_clean(s) + if type(s) ~= 'string' then return nil end + s = s:gsub('%s+', ' '):gsub('^%s+', ''):gsub('%s+$', '') + if s == '' or DMI_PLACEHOLDER[s:lower()] then return nil end + return s +end + +function M.board_name(vendor, name) + vendor = dmi_clean(vendor) + name = dmi_clean(name) + if vendor then + -- Corporate boilerplate, longest first so 'Co., Ltd.' does not leave 'Co.' + vendor = vendor:gsub('%s+Technology Co%.,? Ltd%.?$', '') + vendor = vendor:gsub('%s+COMPUTER INC%.?$', '') + vendor = vendor:gsub('%s+Corporation$', ''):gsub('%s+Corp%.?$', '') + vendor = vendor:gsub('%s+Inc%.?$', ''):gsub('%s+INC%.?$', '') + vendor = vendor:gsub('%s+Co%.,?%s*Ltd%.?$', ''):gsub('%s+CO%.,?%s*LTD%.?$', '') + vendor = vendor:gsub('%s+GmbH$', ''):gsub('%s+LLC$', '') + vendor = vendor:gsub('%s+$', '') + if vendor == '' then vendor = nil end + end + if vendor and name then return vendor .. ' ' .. name end + return name or vendor +end + return M diff --git a/test/fixtures/proc_cpuinfo b/test/fixtures/proc_cpuinfo new file mode 100644 index 0000000..e9cd523 --- /dev/null +++ b/test/fixtures/proc_cpuinfo @@ -0,0 +1,12 @@ +processor : 0 +vendor_id : AuthenticAMD +cpu family : 26 +model : 68 +model name : AMD Ryzen 7 9700X 8-Core Processor +stepping : 0 +cpu MHz : 4491.436 +cache size : 1024 KB + +processor : 1 +vendor_id : AuthenticAMD +model name : AMD Ryzen 7 9700X 8-Core Processor diff --git a/test/fixtures/proc_cpuinfo_intel b/test/fixtures/proc_cpuinfo_intel new file mode 100644 index 0000000..886b589 --- /dev/null +++ b/test/fixtures/proc_cpuinfo_intel @@ -0,0 +1,6 @@ +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 158 +model name : Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz +stepping : 10 diff --git a/test/test_data.lua b/test/test_data.lua index fdf0e2f..3e741e6 100644 --- a/test/test_data.lua +++ b/test/test_data.lua @@ -239,4 +239,39 @@ assert(type(data.kv_parse('')) == 'table', 'empty input gives a table') assert(type(data.kv_parse(nil)) == 'table', 'nil gives a table') assert(data.kv_parse('keyonly\n').keyonly == nil, 'a line with no value is skipped') +-- === Hardware identification ============================================== +-- Both strings are constant for the machine's life, so the widget memoizes +-- them. What is tested here is the stripping, which must be a rule rather +-- than a special case for one host: the Intel fixture exists to prove it. +assert(data.cpu_model(read('test/fixtures/proc_cpuinfo')) == 'Ryzen 7 9700X', + 'amd cpu, got ' .. tostring(data.cpu_model(read('test/fixtures/proc_cpuinfo')))) +assert(data.cpu_model(read('test/fixtures/proc_cpuinfo_intel')) == 'Core i7-8700K', + 'intel cpu, got ' .. tostring(data.cpu_model(read('test/fixtures/proc_cpuinfo_intel')))) + +-- Unparseable input yields nil, so the card shows nothing rather than a +-- half-stripped string. +assert(data.cpu_model('') == nil, 'empty cpuinfo gives nil') +assert(data.cpu_model(nil) == nil, 'nil cpuinfo gives nil') +assert(data.cpu_model('processor\t: 0\n') == nil, 'cpuinfo with no model name gives nil') + +-- The board is two sysfs files joined, with the vendor's corporate suffix +-- dropped: it is boilerplate on every board and costs a third of the row. +assert(data.board_name('Gigabyte Technology Co., Ltd.\n', 'X870 EAGLE WIFI7\n') + == 'Gigabyte X870 EAGLE WIFI7', + 'board, got ' .. tostring(data.board_name('Gigabyte Technology Co., Ltd.\n', 'X870 EAGLE WIFI7\n'))) +assert(data.board_name('ASUSTeK COMPUTER INC.\n', 'PRIME B650-PLUS\n') + == 'ASUSTeK PRIME B650-PLUS', + 'asus board, got ' .. tostring(data.board_name('ASUSTeK COMPUTER INC.\n', 'PRIME B650-PLUS\n'))) + +-- A machine that reports one and not the other shows what it has. +assert(data.board_name(nil, 'X870 EAGLE WIFI7\n') == 'X870 EAGLE WIFI7', + 'name alone, got ' .. tostring(data.board_name(nil, 'X870 EAGLE WIFI7\n'))) +assert(data.board_name('Gigabyte\n', nil) == 'Gigabyte', 'vendor alone') +assert(data.board_name(nil, nil) == nil, 'neither gives nil') + +-- A virtual machine reports placeholder DMI strings. Showing 'To be filled by +-- O.E.M.' as the motherboard is worse than showing nothing. +assert(data.board_name('To Be Filled By O.E.M.\n', 'To Be Filled By O.E.M.\n') == nil, + 'placeholder DMI gives nil') + print('test_data: all assertions passed') |
