history.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. /*
  2. * history.c
  3. *
  4. * Simple non-readline hooks for the cli library
  5. */
  6. #include <stdlib.h>
  7. #include <string.h>
  8. #include <stdio.h>
  9. #include <assert.h>
  10. #include <errno.h>
  11. #include "private.h"
  12. #include "lub/string.h"
  13. #include "tinyrl/history.h"
  14. struct _tinyrl_history {
  15. tinyrl_history_entry_t **entries; /* pointer entries */
  16. unsigned length; /* Number of elements within this array */
  17. unsigned size; /* Number of slots allocated in this array */
  18. unsigned current_index;
  19. unsigned stifle;
  20. };
  21. /*------------------------------------- */
  22. void tinyrl_history_init(tinyrl_history_t * this, unsigned stifle)
  23. {
  24. this->entries = NULL;
  25. this->stifle = stifle;
  26. this->current_index = 1;
  27. this->length = 0;
  28. this->size = 0;
  29. }
  30. /*------------------------------------- */
  31. void tinyrl_history_fini(tinyrl_history_t * this)
  32. {
  33. tinyrl_history_entry_t *entry;
  34. tinyrl_history_iterator_t iter;
  35. /* release the resource associated with each entry */
  36. for (entry = tinyrl_history_getfirst(this, &iter);
  37. entry; entry = tinyrl_history_getnext(&iter)) {
  38. tinyrl_history_entry_delete(entry);
  39. }
  40. /* release the list */
  41. free(this->entries);
  42. this->entries = NULL;
  43. }
  44. /*------------------------------------- */
  45. tinyrl_history_t *tinyrl_history_new(unsigned stifle)
  46. {
  47. tinyrl_history_t *this = malloc(sizeof(tinyrl_history_t));
  48. if (NULL != this) {
  49. tinyrl_history_init(this, stifle);
  50. }
  51. return this;
  52. }
  53. /*------------------------------------- */
  54. void tinyrl_history_delete(tinyrl_history_t * this)
  55. {
  56. tinyrl_history_fini(this);
  57. free(this);
  58. }
  59. /*
  60. HISTORY LIST MANAGEMENT
  61. */
  62. /*------------------------------------- */
  63. /* insert a new entry at the current offset */
  64. static void insert_entry(tinyrl_history_t * this, const char *line)
  65. {
  66. tinyrl_history_entry_t *new_entry =
  67. tinyrl_history_entry_new(line, this->current_index++);
  68. assert(this->length);
  69. assert(this->entries);
  70. if (new_entry) {
  71. this->entries[this->length - 1] = new_entry;
  72. }
  73. }
  74. /*------------------------------------- */
  75. /*
  76. * This frees the specified entries from the
  77. * entries vector. NB it doesn't perform any shuffling.
  78. * This function is inclusive of start and end
  79. */
  80. static void
  81. free_entries(const tinyrl_history_t * this, unsigned start, unsigned end)
  82. {
  83. unsigned i;
  84. assert(start <= end);
  85. assert(end < this->length);
  86. for (i = start; i <= end; i++) {
  87. tinyrl_history_entry_t *entry = this->entries[i];
  88. tinyrl_history_entry_delete(entry);
  89. entry = NULL;
  90. }
  91. }
  92. /*------------------------------------- */
  93. /*
  94. * This removes the specified entries from the
  95. * entries vector. Shuffling up the array as necessary
  96. * This function is inclusive of start and end
  97. */
  98. static void
  99. remove_entries(tinyrl_history_t * this, unsigned start, unsigned end)
  100. {
  101. unsigned delta = (end - start) + 1; /* number of entries being deleted */
  102. /* number of entries to shuffle */
  103. unsigned num_entries = (this->length - end) - 1;
  104. assert(start <= end);
  105. assert(end < this->length);
  106. if (num_entries) {
  107. /* move the remaining entries down to close the array */
  108. memmove(&this->entries[start],
  109. &this->entries[end + 1],
  110. sizeof(tinyrl_history_entry_t *) * num_entries);
  111. }
  112. /* now fix up the length variables */
  113. this->length -= delta;
  114. }
  115. /*------------------------------------- */
  116. /*
  117. Search the current history buffer for the specified
  118. line and if found remove it.
  119. */
  120. static bool_t remove_duplicate(tinyrl_history_t * this, const char *line)
  121. {
  122. bool_t result = BOOL_FALSE;
  123. unsigned i;
  124. for (i = 0; i < this->length; i++) {
  125. tinyrl_history_entry_t *entry = this->entries[i];
  126. if (0 == strcmp(line, tinyrl_history_entry__get_line(entry))) {
  127. free_entries(this, i, i);
  128. remove_entries(this, i, i);
  129. result = BOOL_TRUE;
  130. break;
  131. }
  132. }
  133. return result;
  134. }
  135. /*------------------------------------- */
  136. /*
  137. add an entry to the end of the current array
  138. if there is no space returns -1 else 0
  139. */
  140. static void append_entry(tinyrl_history_t * this, const char *line)
  141. {
  142. if (this->length < this->size) {
  143. this->length++;
  144. insert_entry(this, line);
  145. }
  146. }
  147. /*------------------------------------- */
  148. /*
  149. add a new history entry replacing the oldest one
  150. */
  151. static void add_n_replace(tinyrl_history_t * this, const char *line)
  152. {
  153. if (BOOL_FALSE == remove_duplicate(this, line)) {
  154. /* free the oldest entry */
  155. free_entries(this, 0, 0);
  156. /* shuffle the array */
  157. remove_entries(this, 0, 0);
  158. }
  159. /* add the new entry */
  160. append_entry(this, line);
  161. }
  162. /*------------------------------------- */
  163. /* add a new history entry growing the array if necessary */
  164. static void add_n_grow(tinyrl_history_t * this, const char *line)
  165. {
  166. if (this->size == this->length) {
  167. /* increment the history memory by 10 entries each time we grow */
  168. unsigned new_size = this->size + 10;
  169. size_t nbytes;
  170. tinyrl_history_entry_t **new_entries;
  171. nbytes = sizeof(tinyrl_history_entry_t *) * new_size;
  172. new_entries = realloc(this->entries, nbytes);
  173. if (NULL != new_entries) {
  174. this->size = new_size;
  175. this->entries = new_entries;
  176. }
  177. }
  178. (void)remove_duplicate(this, line);
  179. append_entry(this, line);
  180. }
  181. /*------------------------------------- */
  182. void tinyrl_history_add(tinyrl_history_t * this, const char *line)
  183. {
  184. if (this->length && (this->length == this->stifle)) {
  185. add_n_replace(this, line);
  186. } else {
  187. add_n_grow(this, line);
  188. }
  189. }
  190. /*------------------------------------- */
  191. tinyrl_history_entry_t *tinyrl_history_remove(tinyrl_history_t * this,
  192. unsigned offset)
  193. {
  194. tinyrl_history_entry_t *result = NULL;
  195. if (offset < this->length) {
  196. result = this->entries[offset];
  197. /* do the biz */
  198. remove_entries(this, offset, offset);
  199. }
  200. return result;
  201. }
  202. /*------------------------------------- */
  203. void tinyrl_history_clear(tinyrl_history_t * this)
  204. {
  205. /* free all the entries */
  206. free_entries(this, 0, this->length - 1);
  207. /* and shuffle the array */
  208. remove_entries(this, 0, this->length - 1);
  209. }
  210. /*------------------------------------- */
  211. void tinyrl_history_stifle(tinyrl_history_t * this, unsigned stifle)
  212. {
  213. /*
  214. * if we are stifling (i.e. non zero value) then
  215. * delete the obsolete entries
  216. */
  217. if (stifle) {
  218. if (stifle < this->length) {
  219. unsigned num_deletes = this->length - stifle;
  220. /* free the entries */
  221. free_entries(this, 0, num_deletes - 1);
  222. /* shuffle the array shut */
  223. remove_entries(this, 0, num_deletes - 1);
  224. }
  225. this->stifle = stifle;
  226. }
  227. }
  228. /*------------------------------------- */
  229. unsigned tinyrl_history_unstifle(tinyrl_history_t * this)
  230. {
  231. unsigned result = this->stifle;
  232. this->stifle = 0;
  233. return result;
  234. }
  235. /*------------------------------------- */
  236. bool_t tinyrl_history_is_stifled(const tinyrl_history_t * this)
  237. {
  238. return this->stifle ? BOOL_TRUE : BOOL_FALSE;
  239. }
  240. /*
  241. INFORMATION ABOUT THE HISTORY LIST
  242. */
  243. tinyrl_history_entry_t *tinyrl_history_get(const tinyrl_history_t * this,
  244. unsigned position)
  245. {
  246. unsigned i;
  247. tinyrl_history_entry_t *entry = NULL;
  248. for (i = 0; i < this->length; i++) {
  249. entry = this->entries[i];
  250. if (position == tinyrl_history_entry__get_index(entry)) {
  251. /* found it */
  252. break;
  253. }
  254. entry = NULL;
  255. }
  256. return entry;
  257. }
  258. /*------------------------------------- */
  259. tinyrl_history_expand_t
  260. tinyrl_history_expand(const tinyrl_history_t * this,
  261. const char *string, char **output)
  262. {
  263. tinyrl_history_expand_t result = tinyrl_history_NO_EXPANSION; /* no expansion */
  264. const char *p, *start;
  265. char *buffer = NULL;
  266. unsigned len;
  267. for (p = string, start = string, len = 0; *p; p++, len++) {
  268. /* perform pling substitution */
  269. if (*p == '!') {
  270. /* assume the last command to start with... */
  271. unsigned offset = this->current_index - 1;
  272. unsigned skip;
  273. tinyrl_history_entry_t *entry;
  274. /* this could be an escape sequence */
  275. if (p[1] != '!') {
  276. int tmp;
  277. int res;
  278. /* read the numeric identifier */
  279. res = sscanf(p, "!%d", &tmp);
  280. if ((0 == res) || (EOF == res)) {
  281. /* error so ignore it */
  282. break;
  283. }
  284. if (tmp < 0) {
  285. /* this is a relative reference */
  286. /*lint -e737 Loss of sign in promotion from int to unsigend int */
  287. offset += tmp; /* adding a negative substracts... */
  288. /*lint +e737 */
  289. } else {
  290. /* this is an absolute reference */
  291. offset = (unsigned)tmp;
  292. }
  293. }
  294. if (len > 0) {
  295. /* we need to add in some previous plain text */
  296. lub_string_catn(&buffer, start, len);
  297. }
  298. /* skip the escaped chars */
  299. p += skip = strspn(p, "!-0123456789");
  300. /* try and find the history entry */
  301. entry = tinyrl_history_get(this, offset);
  302. if (NULL != entry) {
  303. /* reset the non-escaped references */
  304. start = p;
  305. len = 0;
  306. /* add the expanded text to the buffer */
  307. result = tinyrl_history_EXPANDED;
  308. lub_string_cat(&buffer,
  309. tinyrl_history_entry__get_line
  310. (entry));
  311. } else {
  312. /* we simply leave the unexpanded sequence */
  313. len += skip;
  314. }
  315. }
  316. }
  317. /* add any left over plain text */
  318. lub_string_catn(&buffer, start, len);
  319. *output = buffer;
  320. return result;
  321. }
  322. /*-------------------------------------*/
  323. tinyrl_history_entry_t *tinyrl_history_getfirst(const tinyrl_history_t * this,
  324. tinyrl_history_iterator_t *
  325. iter)
  326. {
  327. tinyrl_history_entry_t *result = NULL;
  328. iter->history = this;
  329. iter->offset = 0;
  330. if (this->length) {
  331. result = this->entries[iter->offset];
  332. }
  333. return result;
  334. }
  335. /*-------------------------------------*/
  336. tinyrl_history_entry_t *tinyrl_history_getnext(tinyrl_history_iterator_t * iter)
  337. {
  338. tinyrl_history_entry_t *result = NULL;
  339. if (iter->offset < iter->history->length - 1) {
  340. iter->offset++;
  341. result = iter->history->entries[iter->offset];
  342. }
  343. return result;
  344. }
  345. /*-------------------------------------*/
  346. tinyrl_history_entry_t *tinyrl_history_getlast(const tinyrl_history_t * this,
  347. tinyrl_history_iterator_t * iter)
  348. {
  349. iter->history = this;
  350. iter->offset = this->length;
  351. return tinyrl_history_getprevious(iter);
  352. }
  353. /*-------------------------------------*/
  354. tinyrl_history_entry_t *tinyrl_history_getprevious(tinyrl_history_iterator_t *
  355. iter)
  356. {
  357. tinyrl_history_entry_t *result = NULL;
  358. if (iter->offset) {
  359. iter->offset--;
  360. result = iter->history->entries[iter->offset];
  361. }
  362. return result;
  363. }
  364. /*-------------------------------------*/
  365. /* Save command history to specified file */
  366. int tinyrl_history_save(const tinyrl_history_t *this, const char *fname)
  367. {
  368. tinyrl_history_entry_t *entry;
  369. tinyrl_history_iterator_t iter;
  370. FILE *f;
  371. if (!fname) {
  372. errno = EINVAL;
  373. return -1;
  374. }
  375. if (!(f = fopen(fname, "w")))
  376. return -1;
  377. for (entry = tinyrl_history_getfirst(this, &iter);
  378. entry; entry = tinyrl_history_getnext(&iter)) {
  379. if (fprintf(f, "%s\n", tinyrl_history_entry__get_line(entry)) < 0)
  380. return -1;
  381. }
  382. fclose(f);
  383. return 0;
  384. }
  385. /*-------------------------------------*/
  386. /* Restore command history from specified file */
  387. int tinyrl_history_restore(tinyrl_history_t *this, const char *fname)
  388. {
  389. FILE *f;
  390. char *p;
  391. int part_len = 300;
  392. char *buf;
  393. int buf_len = part_len;
  394. int res = 0;
  395. if (!fname) {
  396. errno = EINVAL;
  397. return -1;
  398. }
  399. if (!(f = fopen(fname, "r")))
  400. return 0; /* Can't find history file */
  401. buf = malloc(buf_len);
  402. p = buf;
  403. while (fgets(p, buf_len - (p - buf), f)) {
  404. char *ptmp = NULL;
  405. char *el = strchr(buf, '\n');
  406. if (el) { /* The whole line was readed */
  407. *el = '\0';
  408. tinyrl_history_add(this, buf);
  409. p = buf;
  410. continue;
  411. }
  412. buf_len += part_len;
  413. ptmp = realloc(buf, buf_len);
  414. if (!ptmp) {
  415. res = -1;
  416. goto end;
  417. }
  418. buf = ptmp;
  419. p = buf + buf_len - part_len - 1;
  420. }
  421. end:
  422. free(buf);
  423. fclose(f);
  424. return res;
  425. }
  426. /*-------------------------------------*/