1"""jc - JSON CLI output utility `acpi` command output parser
2
3Usage (cli):
4
5    $ acpi -V | jc --acpi
6
7    or
8
9    $ jc acpi -V
10
11Usage (module):
12
13    import jc.parsers.acpi
14    result = jc.parsers.acpi.parse(acpi_command_output)
15
16Schema:
17
18    [
19      {
20        "type":                             string,
21        "id":                               integer,
22        "state":                            string,
23        "charge_percent":                   integer,
24        "until_charged":                    string,
25        "until_charged_hours":              integer,
26        "until_charged_minuts":             integer,
27        "until_charged_seconds":            integer,
28        "until_charged_total_seconds":      integer,
29        "charge_remaining":                 string,
30        "charge_remaining_hours":           integer,
31        "charge_remaining_minutes":         integer,
32        "charge_remaining_seconds":         integer,
33        "charge_remaining_total_seconds":   integer,
34        "design_capacity_mah":              integer,
35        "last_full_capacity":               integer,
36        "last_full_capacity_percent":       integer,
37        "on-line":                          boolean,
38        "mode":                             string,
39        "temperature":                      float,
40        "temperature_unit":                 string,
41        "trip_points": [
42          {
43            "id":                           integer,
44            "switches_to_mode":             string,
45            "temperature":                  float,
46            "temperature_unit":             string
47          }
48        ],
49        "messages": [
50                                            string
51        ]
52      }
53    ]
54
55Examples:
56
57    $ acpi -V | jc --acpi -p
58    [
59      {
60        "type": "Battery",
61        "id": 0,
62        "state": "Charging",
63        "charge_percent": 71,
64        "until_charged": "00:29:20",
65        "design_capacity_mah": 2110,
66        "last_full_capacity": 2271,
67        "last_full_capacity_percent": 100,
68        "until_charged_hours": 0,
69        "until_charged_minutes": 29,
70        "until_charged_seconds": 20,
71        "until_charged_total_seconds": 1760
72      },
73      {
74        "type": "Adapter",
75        "id": 0,
76        "on-line": true
77      },
78      {
79        "type": "Thermal",
80        "id": 0,
81        "mode": "ok",
82        "temperature": 46.0,
83        "temperature_unit": "C",
84        "trip_points": [
85          {
86            "id": 0,
87            "switches_to_mode": "critical",
88            "temperature": 127.0,
89            "temperature_unit": "C"
90          },
91          {
92            "id": 1,
93            "switches_to_mode": "hot",
94            "temperature": 127.0,
95            "temperature_unit": "C"
96          }
97        ]
98      },
99      {
100        "type": "Cooling",
101        "id": 0,
102        "messages": [
103          "Processor 0 of 10"
104        ]
105      },
106      {
107        "type": "Cooling",
108        "id": 1,
109        "messages": [
110          "Processor 0 of 10"
111        ]
112      },
113      {
114        "type": "Cooling",
115        "id": 2,
116        "messages": [
117          "x86_pkg_temp no state information available"
118        ]
119      },
120      {
121        "type": "Cooling",
122        "id": 3,
123        "messages": [
124          "Processor 0 of 10"
125        ]
126      },
127      {
128        "type": "Cooling",
129        "id": 4,
130        "messages": [
131          "intel_powerclamp no state information available"
132        ]
133      },
134      {
135        "type": "Cooling",
136        "id": 5,
137        "messages": [
138          "Processor 0 of 10"
139        ]
140      }
141    ]
142
143    $ acpi -V | jc --acpi -p -r
144    [
145      {
146        "type": "Battery",
147        "id": "0",
148        "state": "Charging",
149        "charge_percent": "71",
150        "until_charged": "00:29:20",
151        "design_capacity_mah": "2110",
152        "last_full_capacity": "2271",
153        "last_full_capacity_percent": "100"
154      },
155      {
156        "type": "Adapter",
157        "id": "0",
158        "on-line": true
159      },
160      {
161        "type": "Thermal",
162        "id": "0",
163        "mode": "ok",
164        "temperature": "46.0",
165        "temperature_unit": "C",
166        "trip_points": [
167          {
168            "id": "0",
169            "switches_to_mode": "critical",
170            "temperature": "127.0",
171            "temperature_unit": "C"
172          },
173          {
174            "id": "1",
175            "switches_to_mode": "hot",
176            "temperature": "127.0",
177            "temperature_unit": "C"
178          }
179        ]
180      },
181      {
182        "type": "Cooling",
183        "id": "0",
184        "messages": [
185          "Processor 0 of 10"
186        ]
187      },
188      {
189        "type": "Cooling",
190        "id": "1",
191        "messages": [
192          "Processor 0 of 10"
193        ]
194      },
195      {
196        "type": "Cooling",
197        "id": "2",
198        "messages": [
199          "x86_pkg_temp no state information available"
200        ]
201      },
202      {
203        "type": "Cooling",
204        "id": "3",
205        "messages": [
206          "Processor 0 of 10"
207        ]
208      },
209      {
210        "type": "Cooling",
211        "id": "4",
212        "messages": [
213          "intel_powerclamp no state information available"
214        ]
215      },
216      {
217        "type": "Cooling",
218        "id": "5",
219        "messages": [
220          "Processor 0 of 10"
221        ]
222      }
223    ]
224"""
225import jc.utils
226
227
228class info():
229    """Provides parser metadata (version, author, etc.)"""
230    version = '1.3'
231    description = '`acpi` command parser'
232    author = 'Kelly Brazil'
233    author_email = 'kellyjonbrazil@gmail.com'
234
235    # compatible options: linux, darwin, cygwin, win32, aix, freebsd
236    compatible = ['linux']
237    magic_commands = ['acpi']
238
239
240__version__ = info.version
241
242
243def _process(proc_data):
244    """
245    Final processing to conform to the schema.
246
247    Parameters:
248
249        proc_data:   (List of Dictionaries) raw structured data to process
250
251    Returns:
252
253        List of Dictionaries. Structured data to conform to the schema.
254    """
255    int_list = ['id', 'charge_percent', 'design_capacity_mah', 'last_full_capacity', 'last_full_capacity_percent']
256    float_list = ['temperature']
257
258    for entry in proc_data:
259        for key in entry:
260            if key in int_list:
261                entry[key] = jc.utils.convert_to_int(entry[key])
262            if key in float_list:
263                entry[key] = jc.utils.convert_to_float(entry[key])
264
265        if 'trip_points' in entry:
266            for tp in entry['trip_points']:
267                for key in tp:
268                    if key in int_list:
269                        tp[key] = jc.utils.convert_to_int(tp[key])
270                    if key in float_list:
271                        tp[key] = jc.utils.convert_to_float(tp[key])
272
273    for entry in proc_data:
274        if 'until_charged' in entry:
275            entry['until_charged_hours'] = int(entry['until_charged'].split(':')[0])
276            entry['until_charged_minutes'] = int(entry['until_charged'].split(':')[1])
277            entry['until_charged_seconds'] = int(entry['until_charged'].split(':')[2])
278            entry['until_charged_total_seconds'] = (entry['until_charged_hours'] * 3600) + \
279                (entry['until_charged_minutes'] * 60) + entry['until_charged_seconds']
280
281        if 'charge_remaining' in entry:
282            entry['charge_remaining_hours'] = int(entry['charge_remaining'].split(':')[0])
283            entry['charge_remaining_minutes'] = int(entry['charge_remaining'].split(':')[1])
284            entry['charge_remaining_seconds'] = int(entry['charge_remaining'].split(':')[2])
285            entry['charge_remaining_total_seconds'] = (entry['charge_remaining_hours'] * 3600) + \
286                (entry['charge_remaining_minutes'] * 60) + entry['charge_remaining_seconds']
287
288    return proc_data
289
290
291def parse(data, raw=False, quiet=False):
292    """
293    Main text parsing function
294
295    Parameters:
296
297        data:        (string)  text data to parse
298        raw:         (boolean) output preprocessed JSON if True
299        quiet:       (boolean) suppress warning messages if True
300
301    Returns:
302
303        List of Dictionaries. Raw or processed structured data.
304    """
305    jc.utils.compatibility(__name__, info.compatible, quiet)
306    jc.utils.input_type_check(data)
307
308    raw_output = []
309    output_line = {}
310    line_state = ''
311    last_line_state = ''
312    obj_type = ''
313    obj_id = ''
314    trip_points_list = []
315    trip_points_dict = {}
316    messages_list = []
317
318    if jc.utils.has_data(data):
319
320        for line in filter(None, data.splitlines()):
321            obj_type = line.split()[0]
322            obj_id = line.split()[1][:-1]
323            line_state = obj_type + obj_id
324
325            if line_state != last_line_state:
326                if output_line:
327                    raw_output.append(output_line)
328
329                output_line = {}
330                trip_points_list = []
331                messages_list = []
332
333            if obj_type == 'Battery':
334                output_line['type'] = obj_type
335                output_line['id'] = obj_id
336                if 'Charging' in line or 'Discharging' in line or 'Full' in line:
337                    output_line['state'] = line.split()[2][:-1]
338                    output_line['charge_percent'] = line.split()[3].rstrip('%,')
339                    if 'rate information unavailable' not in line:
340                        if 'Charging' in line:
341                            output_line['until_charged'] = line.split()[4]
342                        if 'Discharging' in line:
343                            output_line['charge_remaining'] = line.split()[4]
344
345                if 'design capacity' in line:
346                    output_line['design_capacity_mah'] = line.split()[4]
347                    output_line['last_full_capacity'] = line.split()[9]
348                    output_line['last_full_capacity_percent'] = line.split()[-1][:-1]
349
350            if obj_type == 'Adapter':
351                output_line['type'] = obj_type
352                output_line['id'] = obj_id
353                if 'on-line' in line:
354                    output_line['on-line'] = True
355                else:
356                    output_line['on-line'] = False
357
358            if obj_type == 'Thermal':
359                output_line['type'] = obj_type
360                output_line['id'] = obj_id
361                if 'trip point' not in line:
362                    output_line['mode'] = line.split()[2][:-1]
363                    output_line['temperature'] = line.split()[3]
364                    output_line['temperature_unit'] = line.split()[-1]
365                else:
366                    trip_points_dict = {
367                        "id": line.split()[4],
368                        "switches_to_mode": line.split()[8],
369                        "temperature": line.split()[11],
370                        "temperature_unit": line.split()[-1]
371                    }
372                    trip_points_list.append(trip_points_dict)
373                    output_line['trip_points'] = trip_points_list
374
375            if obj_type == 'Cooling':
376                output_line['type'] = obj_type
377                output_line['id'] = obj_id
378                messages_list.append(line.split(maxsplit=2)[2])
379                output_line['messages'] = messages_list
380
381            last_line_state = line_state
382
383    if output_line:
384        raw_output.append(output_line)
385
386    if raw:
387        return raw_output
388    else:
389        return _process(raw_output)
390