fat_table 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9cce93aafbc687a599cb04bb11ead038af02ecd433b64168f904ea09707a298
4
- data.tar.gz: d61de9b052e89b47c13afa517f1e229d05e42903ba96bb272ddb14ce81c1c563
3
+ metadata.gz: 9f247f30e6f34d7eb429f43a9375fdd9c61cc2be02fecf35916c896c831966a1
4
+ data.tar.gz: 97932270075de281d3f34414b991795d553ca6fd28f32766bf718942001c8d68
5
5
  SHA512:
6
- metadata.gz: 4f5418bad20549df9794fe7c3c69226d77e017433926b220282d094b66dc5b130de88f93e62d3045763aad065a5e1fb9ffbf626ee7b20e745774ee4653bad649
7
- data.tar.gz: 31f4eee0b70bbcef9cba988dc4a2b7b00f1372fe23dd38732b34f520e6a1e2244d73f05abc9963f8b97cb5bd294e4c2ec406dbc0709d59a88355ce4f3676acd7
6
+ metadata.gz: 951fd4ef3540fc7dacce89f73fa9fad6185e50cff664511b8038633608357dbd70277a464c0cb27a25fbcb801f4ef738a065c66addfa0a11887cec9a0013b9bf
7
+ data.tar.gz: 88d4a6479af8e384ed33d631bfad5085971f53c9a40413f751ae421524dba65552c305a5566ce7a19a19f9f5b35406bdf5ae961ddb4edccd6e7a0784deef60ce
data/README.org CHANGED
@@ -1,6 +1,7 @@
1
1
  #+TITLE: FatTable User Guide
2
2
  #+OPTIONS: toc:4
3
3
  #+LATEX_HEADER: \usepackage[margin=0.75in]{geometry}
4
+ #+LATEX_HEADER: \usepackage[utf8]{inputenc}
4
5
  #+PROPERTY: header-args:ruby :colnames no :session readme :hlines yes :exports both
5
6
  #+PROPERTY: header-args:sh :exports code
6
7
  #+STARTUP: inlineimages
@@ -31,8 +32,9 @@ The following is for org.
31
32
  "Current version is: #{FatTable::VERSION}"
32
33
  #+end_src
33
34
 
35
+ #+RESULTS:
34
36
  #+begin_EXAMPLE
35
- Current version is: 0.6.0
37
+ Current version is: 0.6.3
36
38
  #+end_EXAMPLE
37
39
 
38
40
  * Introduction
@@ -104,8 +106,9 @@ org-mode buffer as an org-table, ready for processing by other code blocks.
104
106
  - [[#example-input-tables][Example Input Tables]]
105
107
  - [[#select][Select]]
106
108
  - [[#selecting-existing-columns-also-of-omni][Selecting Existing Columns (Also of :omni)]]
107
- - [[#copying-and-renaming-existing-columns][Copying and Renaming Existing Columns]]
109
+ - [[#copying-and-renaming-existing-columns][Copying and Renaming Existing Columns.]]
108
110
  - [[#adding-new-columns][Adding New Columns]]
111
+ - [[#adding-constant-strings-and-other-types-in-select][Adding Constant Strings and Other Types in select]]
109
112
  - [[#custom-instance-variables-and-hooks][Custom Instance Variables and Hooks]]
110
113
  - [[#argument-order-and-boundaries][Argument Order and Boundaries]]
111
114
  - [[#where][Where]]
@@ -530,6 +533,7 @@ each group.
530
533
  ** Constructing Tables
531
534
  *** Empty Tables
532
535
  **** Without Headers
536
+
533
537
  You can create an empty table with ~FatTable::Table.new~ or, the shorter form,
534
538
  ~FatTable.new~, and then add rows with the ~<<~ operator and a Hash. The keys
535
539
  in the added rows determine the names of the headers:
@@ -585,6 +589,7 @@ for which no key was give are assigned ~nil~ as well:
585
589
  #+end_EXAMPLE
586
590
 
587
591
  **** With Headers
592
+
588
593
  Alternatively, you can specify the headers at the outset, in which case,
589
594
  headers in added rows that do not match any of the initial headers cause new
590
595
  columns to be created:
@@ -618,6 +623,7 @@ columns to be created:
618
623
  #+end_EXAMPLE
619
624
 
620
625
  **** Forcing String Type
626
+
621
627
  Occasionally, ~FatTable~'s automatic type detection can get in the way and you
622
628
  just want it to treat one or more columns as Strings regardless of their
623
629
  appearance. Think, for example, of zip codes. If headers are given when a
@@ -657,6 +663,7 @@ exisiting items in the column are converted to strings with the #to_s method.
657
663
  #+end_EXAMPLE
658
664
 
659
665
  **** Designating "Tolerant" Columns
666
+
660
667
  Related to the problem just discussed is the problem of reading files in from
661
668
  the wild where a column may get typed as, say Numeric, but then contain
662
669
  something that can't be parsed as a Numeric. ~FatTable~ raises an exception
@@ -789,12 +796,13 @@ header row, and the headers are converted to symbols as described above.
789
796
 
790
797
  *** From Arrays of Arrays
791
798
  **** In Ruby Code
799
+
792
800
  You can also initialize a table directly from ruby data structures. You can,
793
801
  for example, build a table from an array of arrays. Remember that you can
794
802
  make any column tolerant with a ~tolerant_columns:~ keyword argument or make
795
803
  them all tolerant by designating the pseudo-column ~:*~ as tolerant.
796
804
 
797
- #+BEGIN_SRC ruby
805
+ #+BEGIN_SRC ruby :eval never
798
806
  aoa = [
799
807
  ['Ref', 'Date', 'Code', 'Raw', 'Shares', 'Price', 'Info', 'Bool'],
800
808
  [1, '2013-05-02', 'P', 795_546.20, 795_546.2, 1.1850, 'ENTITY1', 'T'],
@@ -817,11 +825,14 @@ Notice that the values can either be ruby objects, such as the Integer ~85_000~,
817
825
  or strings that can be parsed into one of the permissible column types.
818
826
 
819
827
  **** In Emacs Org Files
828
+
820
829
  This method of building a table, ~.from_aoa~, is particularly useful in dealing
821
830
  with Emacs org-mode code blocks. Tables in org-mode are passed to code blocks as
822
831
  arrays of arrays. Likewise, a result of a code block in the form of an array of
823
832
  arrays is displayed as an org-mode table:
824
833
 
834
+ #+ATTR_LATEX: :environment footnotesize
835
+ #+ATTR_LATEX: :environment verbatim
825
836
  #+BEGIN_EXAMPLE
826
837
  #+NAME: trades1
827
838
  | Ref | Date | Code | Price | G10 | QP10 | Shares | LP | QP | IPLP | IPQP |
@@ -984,6 +995,7 @@ keyword argument or make them all tolerant by designating the pseudo-column
984
995
 
985
996
  *** Marking Groups in Input
986
997
  **** Manually
998
+
987
999
  At any point, you can add a boundary to a table by invokong the
988
1000
  ~mark_boundary~ method. Without an argument, it adds the boundary to the end
989
1001
  of the table; with a numeric argument, ~n~, it adds the boundary after row
@@ -1179,6 +1191,7 @@ table, rearrange their order, and create new columns that are a function of
1179
1191
  other columns.
1180
1192
 
1181
1193
  **** Selecting Existing Columns (Also of :omni)
1194
+
1182
1195
  Here we select three existing columns by simply passing header symbols in the
1183
1196
  order we want them to appear in the output. Thus, one use of =select= is to
1184
1197
  filter and permute the order of existing columns. The =select= method preserves
@@ -1255,7 +1268,8 @@ you cannot add additional column names:
1255
1268
  | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 | 825.0 |
1256
1269
  #+end_EXAMPLE
1257
1270
 
1258
- **** Copying and Renaming Existing Columns
1271
+ **** Copying and Renaming Existing Columns.
1272
+
1259
1273
  After the list of selected column names in the call to ~select~, you can add
1260
1274
  any number of hash-like arguments. You can use these to add a copy of an
1261
1275
  existing column. By calling select again, you can include only the copied
@@ -1295,6 +1309,7 @@ changed to ~:id~, just add an argument to define the new ~:id~ column:
1295
1309
  #+end_EXAMPLE
1296
1310
 
1297
1311
  **** Adding New Columns
1312
+
1298
1313
  More interesting is that ~select~ can take hash-like keyword arguments after
1299
1314
  the symbol arguments to create new columns in the output as functions of other
1300
1315
  columns. For each hash-like parameter, the keyword given must be a symbol,
@@ -1386,7 +1401,32 @@ second, chained call to ~select~:
1386
1401
  | T016 | 2016-11-02 | 8.25 | 100 | 825.0 |
1387
1402
  #+end_EXAMPLE
1388
1403
 
1404
+ **** Adding Constant Strings and Other Types in select
1405
+
1406
+ Because ~select~'s hash-like parameters evaluate a string as a ruby
1407
+ expression, as just described, it must provide a way to set a new column to a
1408
+ string literal. To indicate that a string should be inserted literally, add a
1409
+ ~:~ as the first non-blank character in the string. This will supress
1410
+ evaluation and insert the remainder of the string in the named column.
1411
+
1412
+ #+BEGIN_SRC ruby :wrap EXAMPLE
1413
+ tab1.select(:ref, :price, :shares, traded_on: :date, cost: ':the price of freedom').
1414
+ select(:ref, :traded_on, :price, :shares, :cost).to_aoa
1415
+ #+END_SRC
1416
+
1417
+ This sets the ~:cost~ column to the string constant 'the price of freedom' for
1418
+ the whole table.
1419
+
1420
+ You can set a column to a constant of any of the acceptable types, ~Numeric~,
1421
+ ~Date~, ~DateTime~, true, false, or nil.
1422
+
1423
+ #+BEGIN_SRC ruby :wrap EXAMPLE
1424
+ tab1.select(:ref, :price, :shares, traded_on: :date, cost: Math::PI, today: Date.today).
1425
+ select(:ref, :traded_on, :price, :shares, :cost, :today).to_aoa
1426
+ #+END_SRC
1427
+
1389
1428
  **** Custom Instance Variables and Hooks
1429
+
1390
1430
  As the above examples demonstrate, the instance variables ~@row~ and ~@group~
1391
1431
  are available when evaluating expressions that add new columns. You can also set
1392
1432
  up your own instance variables as well for keeping track of things that cross
@@ -1445,6 +1485,7 @@ of the running cost of shares, then formats the table.
1445
1485
  #+END_EXAMPLE
1446
1486
 
1447
1487
  **** Argument Order and Boundaries
1488
+
1448
1489
  Notice that ~select~ can take any number of arguments but all the symbol
1449
1490
  arguments must come first followed by all the hash-like keyword arguments,
1450
1491
  including the special arguments for instance variables and hooks.
@@ -1580,7 +1621,6 @@ row, and the table is sorted as with ~order_by~ on that column.
1580
1621
  | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 | 390285.0 |
1581
1622
  #+end_EXAMPLE
1582
1623
 
1583
-
1584
1624
  *** Group_by
1585
1625
  Like ~order_by~, ~group_by~ takes a set of parameters of column header symbols,
1586
1626
  the "grouping parameters", by which to sort the table into a set of groups that
@@ -1668,6 +1708,7 @@ implicit ~order_by~ on the grouping columns is collapsed into a single row.
1668
1708
 
1669
1709
  *** Join
1670
1710
  **** Join Types
1711
+
1671
1712
  So far, all the operations have operated on a single table. ~FatTable~ provides
1672
1713
  several ~join~ methods for combining two tables, each of which takes as
1673
1714
  parameters (1) a second table and (2) except in the case of ~cross_join~, zero
@@ -1704,6 +1745,7 @@ for inclusion in the joined output table.
1704
1745
  M~ rows.
1705
1746
 
1706
1747
  **** Join Expressions
1748
+
1707
1749
  For each of the join types, if no join expressions are given, the tables will be
1708
1750
  joined on columns having the same column header in both tables, and the join
1709
1751
  condition is satisfied when all the values in those columns are equal. If the
@@ -1731,6 +1773,7 @@ local variables within the expression, but the instance variables ~@row~ and
1731
1773
  ~@group~ are not.
1732
1774
 
1733
1775
  **** Join Examples
1776
+
1734
1777
  The following examples are taken from the [[https://www.tutorialspoint.com/postgresql/postgresql_using_joins.htm][Postgresql tutorial]], with some slight
1735
1778
  modifications. The examples will use the following two tables, which are also
1736
1779
  available in ~ft_console~ as ~@tab_a~ and ~@tab_b~:
@@ -2007,6 +2050,7 @@ set operations on duplicates and groups.
2007
2050
  #+END_EXAMPLE
2008
2051
 
2009
2052
  **** Unions
2053
+
2010
2054
  Two tables that are set-compatible can be combined with the ~union~ or
2011
2055
  ~union_all~ methods so that the rows of both tables appear in the output. In the
2012
2056
  output table, the headers of the receiver table are used. You can use ~select~
@@ -2098,6 +2142,7 @@ group boundary between the rows of the two input tables.
2098
2142
  #+END_EXAMPLE
2099
2143
 
2100
2144
  **** Intersections
2145
+
2101
2146
  The ~intersect~ method returns a table having only rows common to both tables,
2102
2147
  eliminating any duplicate rows in the result.
2103
2148
 
@@ -2159,6 +2204,7 @@ operation matters.
2159
2204
  #+END_EXAMPLE
2160
2205
 
2161
2206
  **** Set Differences with Except
2207
+
2162
2208
  You can use the ~except~ method to delete from a table any rows that occur in
2163
2209
  another table, that is, compute the set difference between the tables.
2164
2210
 
@@ -2321,6 +2367,7 @@ but ruby data structures, and for them, things such as alignment are irrelevant.
2321
2367
 
2322
2368
  *** Available Formatter Output Targets
2323
2369
  **** Output Media
2370
+
2324
2371
  ~FatTable~ supports the following output targets for its tables:
2325
2372
 
2326
2373
  - Text :: form the table with ACSII characters,
@@ -2341,6 +2388,7 @@ formats can be defined by adding additional classes.
2341
2388
 
2342
2389
  **** Examples
2343
2390
  ***** To Text
2391
+
2344
2392
  This formatter uses nothing by ASCII characters to draw the table. Notice
2345
2393
  that, unlike to ~to_org~ formatter shown below, the intersections of lines are
2346
2394
  represented by a ~+~ character. Embelishments such as color, bold, and so
@@ -2366,6 +2414,7 @@ forth are ignored.
2366
2414
  #+END_EXAMPLE
2367
2415
 
2368
2416
  ***** To Org
2417
+
2369
2418
  This formatter is designed to format tables in a manner consistent with the
2370
2419
  way tables are drawn within Emacs Org Mode. It also uses nothing by ASCII
2371
2420
  characters to draw the table, but, the intersections of lines are represented
@@ -2394,6 +2443,7 @@ it may be better to use that formatter as shown below.
2394
2443
  #+end_EXAMPLE
2395
2444
 
2396
2445
  ***** To Term
2446
+
2397
2447
  When outputting to a terminal or other device that can interpret ANSI
2398
2448
  characters and escape codes, you can use this formatter to get a prettier
2399
2449
  table. It also allows embelishments such as color and text styles to the
@@ -2419,6 +2469,7 @@ extent the device supports it.
2419
2469
  #+end_EXAMPLE
2420
2470
 
2421
2471
  ***** To LaTeX
2472
+
2422
2473
  This formatter outputs a table in the form suitable for inclusion in a LaTeX
2423
2474
  document using the ~logtable~ package. Natualy it allows embelishments such
2424
2475
  as color and text styles to the full extent of LaTeX's formatting prowess.
@@ -2446,6 +2497,7 @@ Finance&
2446
2497
  #+end_EXAMPLE
2447
2498
 
2448
2499
  ***** To AoA (Array of Arrays)
2500
+
2449
2501
  #+begin_SRC ruby :wrap EXAMPLE
2450
2502
  tab_b.to_aoa
2451
2503
  #+end_SRC
@@ -2456,6 +2508,7 @@ Finance&
2456
2508
  #+end_EXAMPLE
2457
2509
 
2458
2510
  ***** To AoH (Array of Hashes)
2511
+
2459
2512
  #+begin_SRC ruby :wrap EXAMPLE
2460
2513
  tab_b.to_aoh
2461
2514
  #+end_SRC
@@ -2476,6 +2529,7 @@ order, so '$R,' and ',$R' are equivalent.
2476
2529
  Here is a list of all the formatting directives that apply to each cell type:
2477
2530
 
2478
2531
  **** String
2532
+
2479
2533
  For a string element, the following instructions are valid. Note that these can
2480
2534
  also be applied to all the other cell types as well since they are all converted
2481
2535
  to a string in forming the output.
@@ -2504,6 +2558,7 @@ columns of a given type, it can be countermanded in formatting directives for
2504
2558
  particular columns.
2505
2559
 
2506
2560
  **** Numeric
2561
+
2507
2562
  For a numeric element, all the instructions valid for string are available, in
2508
2563
  addition to the following:
2509
2564
 
@@ -2522,6 +2577,7 @@ For example, the directive 'R5.0c[blue]' would right-align the numeric
2522
2577
  element, pad it on the left with zeros, and color it blue.
2523
2578
 
2524
2579
  **** DateTime
2580
+
2525
2581
  For a ~DateTime~, all the instructions valid for string are available, in
2526
2582
  addition to the following:
2527
2583
 
@@ -2537,6 +2593,7 @@ For example, 'c[pink]d[%b %-d, %Y]C', would format a date element like 'Sep
2537
2593
  22, 1957', center it, and color it pink.
2538
2594
 
2539
2595
  **** Boolean
2596
+
2540
2597
  For a boolean cell, all the instructions valid for string are available, in
2541
2598
  addition to the following:
2542
2599
 
@@ -2554,6 +2611,7 @@ render a true boolean as ~Yeppers~ colored green on pink and render a false
2554
2611
  boolean as ~Nope~ colored red on pink. See [[https://www.youtube.com/watch?v=oLdFFD8II8U][Yeppers]] for additional information.
2555
2612
 
2556
2613
  **** NilClass
2614
+
2557
2615
  By default, ~nil~ elements are rendered as blank cells, but you can make them
2558
2616
  visible with the following, and in that case, all the formatting instructions
2559
2617
  valid for strings are also available:
@@ -2564,6 +2622,7 @@ For example, you might want to use 'n[-]Cc[purple]' to make nils visible as a
2564
2622
  centered purple hyphen.
2565
2623
 
2566
2624
  *** The ~format~ and ~format_for~ methods
2625
+
2567
2626
  Formatters take only two kinds of methods, those that attach footers to a
2568
2627
  table, which are discussed in the next section, and those that specify
2569
2628
  formatting for table cells, which are the subject of this section.
@@ -2705,6 +2764,7 @@ but not for other nils, such as in the last row of the ~:join_date~ column.
2705
2764
 
2706
2765
  *** Footers
2707
2766
  **** Adding Footers
2767
+
2708
2768
  You can call the ~foot~, ~gfoot~, ~footer,~ or ~gfooter~, methods on
2709
2769
  ~Formatter~ objects to add footers and group footers. Note that all of these
2710
2770
  methods return a ~Footer~ object that can be accessed to extract the computed
@@ -2762,6 +2822,7 @@ There are also a number of convenience methods for adding common footers:
2762
2822
  columns with the label 'Group Maximum'.
2763
2823
 
2764
2824
  **** Dynamic Labels
2825
+
2765
2826
  Most of the time, you will want a fixed string as the label. However,
2766
2827
  especially in the case of a group footer, you might want a dynamically
2767
2828
  contructed label. You can use a proc or lambda for a label, and it will be
@@ -2795,6 +2856,7 @@ This would add the group's year to label, assuming the :date column of the
2795
2856
  footer's table had the same year for each item in the group.
2796
2857
 
2797
2858
  **** Aggregators
2859
+
2798
2860
  When adding a footer with the above methods, you can specify an aggregator for
2799
2861
  each column named in the ~agg_cols~ parameter. There are several candidates
2800
2862
  for what you can use for an aggregator:
@@ -2839,6 +2901,7 @@ for what you can use for an aggregator:
2839
2901
  itself.
2840
2902
 
2841
2903
  **** Footer objects
2904
+
2842
2905
  Each of the methods for adding a footer to a ~Formatter~ returns a ~Footer~ object
2843
2906
  that you can query for attributes of the generated footer, including accessing
2844
2907
  their computed values. Here are the accessors available on a
@@ -2861,6 +2924,7 @@ their computed values. Here are the accessors available on a
2861
2924
  ~k~ to specify which group to access in the case of a group footer.
2862
2925
 
2863
2926
  **** Footer Examples
2927
+
2864
2928
  As a reminder, here is the table, ~tab_a~ defined earlier:
2865
2929
 
2866
2930
  #+BEGIN_SRC ruby :wrap EXAMPLE
@@ -3115,6 +3179,7 @@ call one of the ~to_xxx~ methods directly on a table, which will yield a
3115
3179
  convenient way to do it. But there are a few other ways.
3116
3180
 
3117
3181
  **** By Instantiating a Formatter
3182
+
3118
3183
  You can instantiate a ~XXXFormatter~ object and feed it a table as a
3119
3184
  parameter. There is a Formatter subclass for each target output medium, for
3120
3185
  example, ~AoaFormatter~ will produce a ruby array of arrays. You can then call
@@ -3160,6 +3225,7 @@ you can call methods on it to affect the formatting of the output:
3160
3225
  #+END_EXAMPLE
3161
3226
 
3162
3227
  **** By Using ~FatTable~ module-level method calls
3228
+
3163
3229
  The ~FatTable~ module provides a set of methods of the form ~to_aoa~, ~to_text~,
3164
3230
  etc., to access a ~Formatter~ without having to create an instance yourself.
3165
3231
  Without a block, they apply the default formatting to the table and call the
@@ -3206,6 +3272,7 @@ automatically after the block:
3206
3272
  #+END_EXAMPLE
3207
3273
 
3208
3274
  **** By Calling Methods on Table Objects
3275
+
3209
3276
  Finally, as in many of the examples, you can call methods such as ~to_aoa~,
3210
3277
  ~to_text~, etc., directly on a Table:
3211
3278
 
@@ -952,20 +952,25 @@ module FatTable
952
952
  vars = old_row.merge(new_row)
953
953
  case expr
954
954
  when Symbol
955
- msg = "Column '#{expr}' in select does not exist"
955
+ msg = "select column '#{expr}' does not exist"
956
956
  raise UserError, msg unless vars.key?(expr)
957
957
 
958
958
  new_row[key] = vars[expr]
959
959
  when String
960
- new_row[key] = ev.evaluate(expr, locals: vars)
960
+ if expr.match?(/\A\s*:/)
961
+ # Leading colon signal a literal string
962
+ new_row[key] = expr.sub(/\A\s*:/, '')
963
+ else
964
+ # Otherwise, evaluate the string.
965
+ new_row[key] = ev.evaluate(expr, locals: vars)
966
+ end
967
+ when Numeric, DateTime, Date, TrueClass, FalseClass
968
+ new_row[key] = expr
961
969
  else
962
- msg = "Hash parameter '#{key}' to select must be a symbol or string"
970
+ msg = "select can't set column at '#{key}' to '#{expr}' of class #{expr.class}"
963
971
  raise UserError, msg
964
972
  end
965
973
  end
966
- # Set the group number and run the hook with the local variables set to
967
- # the row after the new row is evaluated.
968
- # vars = new_row.merge(__group: grp)
969
974
  ev.eval_after_hook(locals: new_row)
970
975
  result << new_row
971
976
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FatTable
4
4
  # The current version of FatTable
5
- VERSION = '0.6.2'
5
+ VERSION = '0.6.3'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fat_table
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-24 00:00:00.000000000 Z
11
+ date: 2022-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler