├── .gitattributes
├── .gitignore
├── PowerQueryExtensions.sln
├── README.md
├── build
├── Date.mez
├── List.mez
├── Number.mez
├── Splitter.mez
├── Table.mez
├── Text.mez
└── Value.mez
├── media
└── Modules.png
└── src
├── Date
├── Date.mproj
├── Date.pq
└── README.md
├── List
├── List.mproj
├── List.pq
└── README.md
├── Number
├── Number.mproj
├── Number.pq
└── README.md
├── Splitter
├── README.md
├── Splitter.mproj
└── Splitter.pq
├── Table
├── README.md
├── Table.mproj
└── Table.pq
├── Text
├── README.md
├── Text.mproj
└── Text.pq
└── Value
├── README.md
├── Value.mproj
└── Value.pq
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/PowerQueryExtensions.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26507.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "Table", "src\Table\Table.mproj", "{BC5E50DE-5AE8-481C-B6BE-CC500490770C}"
7 | EndProject
8 | Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "List", "src\List\List.mproj", "{BF55FED0-B51D-420A-84A0-F63F677CCA2C}"
9 | EndProject
10 | Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "Date", "src\Date\Date.mproj", "{5579955A-C61D-4FBD-80DB-95C20E2AE00D}"
11 | EndProject
12 | Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "Text", "src\Text\Text.mproj", "{CDA74E9B-E9EE-493A-BD2E-C93AA250EF3B}"
13 | EndProject
14 | Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "Number", "src\Number\Number.mproj", "{1916BB62-D744-4EB8-86B2-33D0B9315A15}"
15 | EndProject
16 | Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "Splitter", "src\Splitter\Splitter.mproj", "{846B0CC1-2F97-4E7D-BEEB-3E9869AC76A0}"
17 | EndProject
18 | Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "Value", "src\Value\Value.mproj", "{412F037C-A922-4EB2-BB7B-5F1D509FC400}"
19 | EndProject
20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FA2BABA9-6435-428E-9848-C1C7920A0266}"
21 | ProjectSection(SolutionItems) = preProject
22 | README.md = README.md
23 | EndProjectSection
24 | EndProject
25 | Global
26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
27 | Debug|x86 = Debug|x86
28 | Release|x86 = Release|x86
29 | EndGlobalSection
30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
31 | {BC5E50DE-5AE8-481C-B6BE-CC500490770C}.Debug|x86.ActiveCfg = Debug|x86
32 | {BC5E50DE-5AE8-481C-B6BE-CC500490770C}.Debug|x86.Build.0 = Debug|x86
33 | {BC5E50DE-5AE8-481C-B6BE-CC500490770C}.Release|x86.ActiveCfg = Release|x86
34 | {BC5E50DE-5AE8-481C-B6BE-CC500490770C}.Release|x86.Build.0 = Release|x86
35 | {BF55FED0-B51D-420A-84A0-F63F677CCA2C}.Debug|x86.ActiveCfg = Debug|x86
36 | {BF55FED0-B51D-420A-84A0-F63F677CCA2C}.Debug|x86.Build.0 = Debug|x86
37 | {BF55FED0-B51D-420A-84A0-F63F677CCA2C}.Release|x86.ActiveCfg = Release|x86
38 | {BF55FED0-B51D-420A-84A0-F63F677CCA2C}.Release|x86.Build.0 = Release|x86
39 | {5579955A-C61D-4FBD-80DB-95C20E2AE00D}.Debug|x86.ActiveCfg = Debug|x86
40 | {5579955A-C61D-4FBD-80DB-95C20E2AE00D}.Debug|x86.Build.0 = Debug|x86
41 | {5579955A-C61D-4FBD-80DB-95C20E2AE00D}.Release|x86.ActiveCfg = Release|x86
42 | {5579955A-C61D-4FBD-80DB-95C20E2AE00D}.Release|x86.Build.0 = Release|x86
43 | {CDA74E9B-E9EE-493A-BD2E-C93AA250EF3B}.Debug|x86.ActiveCfg = Debug|x86
44 | {CDA74E9B-E9EE-493A-BD2E-C93AA250EF3B}.Debug|x86.Build.0 = Debug|x86
45 | {CDA74E9B-E9EE-493A-BD2E-C93AA250EF3B}.Release|x86.ActiveCfg = Release|x86
46 | {CDA74E9B-E9EE-493A-BD2E-C93AA250EF3B}.Release|x86.Build.0 = Release|x86
47 | {1916BB62-D744-4EB8-86B2-33D0B9315A15}.Debug|x86.ActiveCfg = Debug|x86
48 | {1916BB62-D744-4EB8-86B2-33D0B9315A15}.Debug|x86.Build.0 = Debug|x86
49 | {1916BB62-D744-4EB8-86B2-33D0B9315A15}.Release|x86.ActiveCfg = Release|x86
50 | {1916BB62-D744-4EB8-86B2-33D0B9315A15}.Release|x86.Build.0 = Release|x86
51 | {846B0CC1-2F97-4E7D-BEEB-3E9869AC76A0}.Debug|x86.ActiveCfg = Debug|x86
52 | {846B0CC1-2F97-4E7D-BEEB-3E9869AC76A0}.Debug|x86.Build.0 = Debug|x86
53 | {846B0CC1-2F97-4E7D-BEEB-3E9869AC76A0}.Release|x86.ActiveCfg = Release|x86
54 | {846B0CC1-2F97-4E7D-BEEB-3E9869AC76A0}.Release|x86.Build.0 = Release|x86
55 | {412F037C-A922-4EB2-BB7B-5F1D509FC400}.Debug|x86.ActiveCfg = Debug|x86
56 | {412F037C-A922-4EB2-BB7B-5F1D509FC400}.Debug|x86.Build.0 = Debug|x86
57 | {412F037C-A922-4EB2-BB7B-5F1D509FC400}.Release|x86.ActiveCfg = Release|x86
58 | {412F037C-A922-4EB2-BB7B-5F1D509FC400}.Release|x86.Build.0 = Release|x86
59 | EndGlobalSection
60 | GlobalSection(SolutionProperties) = preSolution
61 | HideSolutionNode = FALSE
62 | EndGlobalSection
63 | EndGlobal
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryExtensions/93ee49369829fe90bc8ce856b36ea8f87b65ce24/README.md
--------------------------------------------------------------------------------
/build/Date.mez:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryExtensions/93ee49369829fe90bc8ce856b36ea8f87b65ce24/build/Date.mez
--------------------------------------------------------------------------------
/build/List.mez:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryExtensions/93ee49369829fe90bc8ce856b36ea8f87b65ce24/build/List.mez
--------------------------------------------------------------------------------
/build/Number.mez:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryExtensions/93ee49369829fe90bc8ce856b36ea8f87b65ce24/build/Number.mez
--------------------------------------------------------------------------------
/build/Splitter.mez:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryExtensions/93ee49369829fe90bc8ce856b36ea8f87b65ce24/build/Splitter.mez
--------------------------------------------------------------------------------
/build/Table.mez:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryExtensions/93ee49369829fe90bc8ce856b36ea8f87b65ce24/build/Table.mez
--------------------------------------------------------------------------------
/build/Text.mez:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryExtensions/93ee49369829fe90bc8ce856b36ea8f87b65ce24/build/Text.mez
--------------------------------------------------------------------------------
/build/Value.mez:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryExtensions/93ee49369829fe90bc8ce856b36ea8f87b65ce24/build/Value.mez
--------------------------------------------------------------------------------
/media/Modules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hugoberry/PowerQueryExtensions/93ee49369829fe90bc8ce856b36ea8f87b65ce24/media/Modules.png
--------------------------------------------------------------------------------
/src/Date/Date.mproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Debug
4 | 2.0
5 | {5579955a-c61d-4fbd-80db-95c20e2ae00d}
6 | Exe
7 | MyRootNamespace
8 | MyAssemblyName
9 | False
10 | False
11 | False
12 | False
13 | False
14 | False
15 | False
16 | False
17 | False
18 | False
19 | 1000
20 | Yes
21 | Date
22 |
23 |
24 | false
25 |
26 | bin\Debug\
27 |
28 |
29 | false
30 | ..\..\build\
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Code
41 |
42 |
43 |
44 |
45 | Content
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/Date/Date.pq:
--------------------------------------------------------------------------------
1 | section Date;
2 | /////////////////////////
3 | // Date //
4 | /////////////////////////
5 | // Basic calendar
6 | shared Date.Calendar =
7 | Document(
8 | "Date.Calendar",
9 | "Generate a calendar table for a given date span - can be text or proper dates. Current columns are Date, DayOfWeek, Month, MonthNum, WeekStartData, WeekStart, Year, YearMonth",
10 | {[ Description = "2016 calendar", Code ="PBI[Date.Calendar](""1/1/2016"", ""12/31/2016""", Result = "2016 calendar"]},
11 | (start as any, end as any) =>
12 | let
13 | StartDate = Date.From(start),
14 | EndDate = Date.From(end),
15 | Source = Date.DatesBetween(StartDate, EndDate),
16 | FromList = Table.FromList(Source, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
17 | Date = Table.RenameColumns(FromList,{{"Column1", "Date"}}),
18 | DayOfWeek = Table.AddColumn(Date, "Day of Week", each Date.DayName([Date])),
19 | Month = Table.AddColumn(DayOfWeek, "Month", each Date.MonthName([Date])),
20 | MonthNum = Table.AddColumn(Month, "MonthNumber", each Date.Month([Date])),
21 | WeekStartDate = Table.AddColumn(MonthNum, "WeekStartDate", each Date.StartOfWeek([Date])),
22 | WeekStart = Table.AddColumn(WeekStartDate, "Week Start", each [Month] & " " & Text.From(Date.Day([WeekStartDate]))),
23 | Year = Table.AddColumn(WeekStart, "Year", each Date.Year([Date])),
24 | YearMonth = Table.AddColumn(Year, "YearMonth", each Number.From(Text.From([Year]) & (if [MonthNumber] < 10 then "0" else "") & Text.From([MonthNumber]))),
25 | Result = YearMonth
26 | in
27 | Result
28 | );
29 | shared Date.DatesBetween = Document(
30 | "Date.DatesBetween",
31 | "Returns a list of dates in a given span (inclusive). Start and end parameters can be any order",
32 | {[Description = "Date range", Code = "PBI[Date.DatesBetween](""1/1/2016"", ""1/3/2016"")", Result="{""1/1/2016"", ""1/2/2016"", ""1/3/2016""}" ]},
33 | (start as any, end as any) =>
34 | let
35 | StartDate = Date.From(start),
36 | EndDate = Date.From(end),
37 | adjustedStart = List.Min({StartDate, EndDate}),
38 | adjustedEnd = List.Max({StartDate, EndDate}),
39 | GetDates = (start as date, end as date, dates as list)=> if start > end then dates else @GetDates(Date.AddDays(start, 1), end, List.Combine({dates, {start}})),
40 | Dates = GetDates(adjustedStart, adjustedEnd, {})
41 | in Dates
42 | );
43 |
44 | shared Date.DayName = Document(
45 | "Date.DayName",
46 | "Returns the English day of the week name for a date",
47 | {[ Description = "Get the day name", Code="Date.DayName(""9/9/2016"")", Result="Friday"]},
48 | (date as any) => Switch(Date.DayOfWeek(DateTime.From(date)), {0, 1, 2, 3, 4, 5, 6}, {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}, null)
49 | );
50 |
51 | /////////////////////////
52 | // Dependencies //
53 | /////////////////////////
54 |
55 | Document = (name as text, description as text, valueOrExample as any, optional valueIfExample as any) =>
56 | let
57 | value = if valueIfExample is null then valueOrExample else valueIfExample,
58 | examples = if valueIfExample is null then {} else valueOrExample
59 | in
60 | Value.ReplaceType(value, Value.Type(value) meta [
61 | Documentation.Name = name,
62 | Documentation.Description = description,
63 | // [Description = "", Code="", Result =""]
64 | Documentation.Examples = examples
65 | ]);
66 |
67 | Switch =
68 | Document(
69 | "Switch",
70 | "Given a value, find it's paired item
"&
71 | "Switch(value as any, cases as list, results as list, optional default as any)
" &
72 | "Switch(value as any, pairs as list, optional default as any)",
73 | {
74 | [ Description = "Using separate lists", Code = "Switch(1, {1, 2, 3}, {""A"", ""B"", ""C""})", Result = "A"],
75 | [ Description = "Using one paired list", Code = "Switch(1, {{1, ""A""}, {2, ""B""}, {3, ""C""}})", Result = "A"]
76 | },
77 | (value as any, casesOrPairs as list, optional resultsOrDefault as any, optional default as any) =>
78 | let
79 | hasPairs = List.First(casesOrPairs) is list,
80 | usingPairs =
81 | let
82 | targetPosition = List.PositionOf(casesOrPairs, value, Occurrence.First, (case, theValue) => theValue = case{0})
83 | in
84 | if targetPosition = -1 then resultsOrDefault else casesOrPairs{targetPosition}{1},
85 | usingCases =
86 | let
87 | cases = casesOrPairs,
88 | results = resultsOrDefault
89 | in
90 | if List.IsEmpty(cases) or List.IsEmpty(results) then default else if value = List.First(cases) then List.First(results) else @Switch(value, List.Skip(cases, 1), List.Skip(results, 1), default)
91 | in
92 | if hasPairs then usingPairs else usingCases
93 | );
--------------------------------------------------------------------------------
/src/Date/README.md:
--------------------------------------------------------------------------------
1 | # Date Module
2 |
3 | ## Date.Calendar
4 | Generate a calendar table for a given date span - can be text or proper dates. Current columns are Date, DayOfWeek, Month, MonthNum, WeekStartData, WeekStart, Year, YearMonth
5 | ## Date.DatesBetween
6 | Returns a list of dates in a given span (inclusive). Start and end parameters can be any order
7 | ## Date.DayName
8 | Returns the English day of the week name for a date
9 |
--------------------------------------------------------------------------------
/src/List/List.mproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Debug
4 | 2.0
5 |
6 |
7 | Exe
8 | MyRootNamespace
9 | MyAssemblyName
10 | False
11 | False
12 | False
13 | False
14 | False
15 | False
16 | False
17 | False
18 | False
19 | False
20 | 1000
21 | Yes
22 | List
23 |
24 |
25 | false
26 |
27 | bin\Debug\
28 |
29 |
30 | false
31 | ..\..\build\
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Code
42 |
43 |
44 |
45 |
46 | Content
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/List/List.pq:
--------------------------------------------------------------------------------
1 | section List;
2 | /////////////////////////
3 | // List //
4 | /////////////////////////
5 | shared List.Flatten = Document(
6 | "List.Flatten",
7 | "Recursively flattens list elements. The end result is a single list",
8 | { [ Description = "Flattening nested lists into one", Code = "List.Flatten({ 1, 2, Table.FromRecords({[x=1]}), {3, 4, 5} })", Result = "{ 1, 2, Table.FromRecords({[x=1]}), 3, 4, 5}"] },
9 | (list as list) => List.Accumulate(list, {}, (state, current) =>
10 | let
11 | currentListContent = if current is list then @List.Flatten(current) else {current}
12 | in
13 | List.Combine({state, currentListContent})
14 | )
15 | );
16 | shared List.From = Document(
17 | "List.From",
18 | "Converts a text representation of a list into a list of the elements. Items are considered to be split by ,",
19 | { [ Description = "Convert a text list", Code = "List.From(""{A, B, C}"")", Result = "{ ""A"", ""B"", ""C"" }"] },
20 | (simpleTextList as text) =>
21 | let
22 | trimWhitespace = Text.Trim(simpleTextList),
23 | listToSplit = Text.TrimEnd(Text.TrimStart(trimWhitespace, "{"), "}"),
24 | Result = List.Transform(Text.Split(listToSplit, ","), each Text.Trim(_))
25 | in
26 | Result
27 | );
28 | shared List.ToText = Document(
29 | "List.ToText",
30 | "Converts a list to a textual representation. Inverse of List.From",
31 | { [ Description = "Conver to text", Code = "List.ToText({ 1, 2, 3})", Result = """{1, 2, 3}"""] },
32 | (list as list) =>
33 | List.Accumulate(list, "{", (state, current) => current & Text.From(current)) & "}"
34 | );
35 |
36 | shared List.Swap = (_,_from as number ,_to as number) =>
37 | let
38 | from = List.Min({_from,_to}),
39 | to = List.Max({_from,_to})
40 | in if from=to then _ else
41 | List.Range(_,0,from)
42 | &{_{to}}
43 | &List.Range(_,from+1,to-from-1)
44 | &{_{from}}
45 | &List.Range(_,to+1);
46 |
47 | shared List.Shuffle = (n) =>
48 | List.Accumulate({0..(n-2)},{0..n},(_,iterator)=>
49 | List.Swap(List.Buffer(_),
50 | Number.Round(Number.RandomBetween(iterator,n)),
51 | iterator
52 | ));
53 | /////////////////////////
54 | // Dependencies //
55 | /////////////////////////
56 |
57 | Document = (name as text, description as text, valueOrExample as any, optional valueIfExample as any) =>
58 | let
59 | value = if valueIfExample is null then valueOrExample else valueIfExample,
60 | examples = if valueIfExample is null then {} else valueOrExample
61 | in
62 | Value.ReplaceType(value, Value.Type(value) meta [
63 | Documentation.Name = name,
64 | Documentation.Description = description,
65 | // [Description = "", Code="", Result =""]
66 | Documentation.Examples = examples
67 | ]);
--------------------------------------------------------------------------------
/src/List/README.md:
--------------------------------------------------------------------------------
1 | # List Module
2 |
3 | ## List.Flatten
4 | Recursively flattens list elements. The end result is a single list
5 |
6 | ## List.From
7 | Converts a text representation of a list into a list of the elements. Items are considered to be split by ,
8 |
9 | ## List.ToText
10 | Converts a list to a textual representation. Inverse of List.From
11 |
12 | ## List.Swap
13 | Swaping the elements of a list
14 |
15 | ## List.Shuffle
16 | Generate a random shuffle of the elements in a list
--------------------------------------------------------------------------------
/src/Number/Number.mproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Debug
4 | 2.0
5 |
6 |
7 | Exe
8 | MyRootNamespace
9 | MyAssemblyName
10 | False
11 | False
12 | False
13 | False
14 | False
15 | False
16 | False
17 | False
18 | False
19 | False
20 | 1000
21 | Yes
22 | Number
23 |
24 |
25 | false
26 |
27 | bin\Debug\
28 |
29 |
30 | false
31 | ..\..\build\
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Code
42 |
43 |
44 |
45 |
46 | Content
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/Number/Number.pq:
--------------------------------------------------------------------------------
1 | section Number;
2 | /////////////////////////
3 | // Number //
4 | /////////////////////////
5 | shared Number.Digits = {0,1,2,3,4,5,6,7,8,9};
6 | shared Number.ParseText = Document(
7 | "Number.ParseText",
8 | "Returns the first number (1+ consecutive digits) in the given text. Optionally, provide allowed characters to ignore them",
9 | {[ Description = "Allow commas", Code="PBI[Number.ParseText](""It's over 9,000!"", 0, {"",""})", Result = "9000" ]},
10 | (text as text, optional startIndex as number, optional allowCharacters as list) =>
11 | let
12 | consider = if startIndex is null then text else Text.Range(text,startIndex),
13 | _allowCharacters = if allowCharacters is null then {} else allowCharacters,
14 | numberSeries = List.FirstN(List.Skip(Text.ToList(consider), each not Text.IsNumber(_)), each Text.IsNumber(_) or List.Contains(_allowCharacters, _))
15 | in
16 | if text is null then null else Text.FromList(numberSeries)
17 | );
18 | shared Number.ToLetters = Document(
19 | "Number.ToLetters",
20 | "Converts a number (starting at 1) to an alphabet representation. Works like column headers in Excel.",
21 | {[
22 | Description = "Column 27",
23 | Code = "PBI[Number.ToLetters](27)",
24 | Result = "AB"
25 | ]},
26 | (value as number) =>
27 | let
28 | GetLetter = (num as number) =>
29 | let
30 | number = Number.Mod(num, 26),
31 | val = if number = 0 then 26 else number,
32 | valid = number < 26 and number > 0
33 | in
34 | if valid then Text.At(Text.Alphabet, val - 1) else error "Can't get letter for " & Text.From(num),
35 | func = (value as number, factor as number) =>
36 | let
37 | ThisLetter = GetLetter(Number.RoundDown(value/Number.Power(26, factor))),
38 | Result = if value <= Number.Power(26, factor) then "" else @func(value, factor+1) & ThisLetter
39 | in
40 | Result
41 | in
42 | if value <= 26 then GetLetter(value) else func(value, 1) & GetLetter(value)
43 | );
44 | shared Number.Reverse8BitInt = (x) =>
45 | Number.Mod(Number.BitwiseAnd((x* 0x0202020202),0x010884422010),1023);
46 | shared Number.Reverse32BitInt = (x) =>
47 | let
48 | b0 = Number.BitwiseAnd(x,0xff),
49 | b1 = Number.BitwiseShiftRight(Number.BitwiseAnd(x,0xff00),8),
50 | b2 = Number.BitwiseShiftRight(Number.BitwiseAnd(x,0xff0000),16),
51 | b3 = Number.BitwiseShiftRight(Number.BitwiseAnd(x,0xff000000),24)
52 | in
53 | Number.BitwiseOr(
54 | Number.BitwiseOr(
55 | Number.BitwiseShiftLeft(Number.Reverse8BitInt(b0),24),
56 | Number.BitwiseShiftLeft(Number.Reverse8BitInt(b1),16)),
57 | Number.BitwiseOr(
58 | Number.BitwiseShiftLeft(Number.Reverse8BitInt(b2),8),
59 | Number.Reverse8BitInt(b3)));
60 |
61 | shared Number.DecToBin = (num as number)=>
62 | List.Last(
63 | List.Generate(
64 | () =>[reminder = num,
65 | binString= Number.ToText(Number.BitwiseAnd(reminder,1))],
66 | each [reminder]> 0,
67 | each [reminder = Number.BitwiseShiftRight([reminder],1),
68 | binString= Number.ToText(Number.BitwiseAnd(reminder,1))&[binString]],
69 | each [binString]
70 | ));
71 |
72 | shared Number.HexToDec = (hexString as text)
73 | => Expression.Evaluate("0x"&hexString);
74 | /////////////////////////
75 | // Dependencies //
76 | /////////////////////////
77 |
78 | Document = (name as text, description as text, valueOrExample as any, optional valueIfExample as any) =>
79 | let
80 | value = if valueIfExample is null then valueOrExample else valueIfExample,
81 | examples = if valueIfExample is null then {} else valueOrExample
82 | in
83 | Value.ReplaceType(value, Value.Type(value) meta [
84 | Documentation.Name = name,
85 | Documentation.Description = description,
86 | // [Description = "", Code="", Result =""]
87 | Documentation.Examples = examples
88 | ]);
89 |
90 | Text.Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
91 | Text.FromList = (list as list) => List.Accumulate(list, "", (state, current) => state & Text.From(current));
92 | Text.IsNumber = (text as text) => try Number.FromText(text) is number otherwise false;
--------------------------------------------------------------------------------
/src/Number/README.md:
--------------------------------------------------------------------------------
1 | # Number Module
2 |
3 | ## Number.Digits
4 |
5 | ## Number.ParseText
6 | Returns the first number (1+ consecutive digits) in the given text. Optionally, provide allowed characters to ignore them
7 |
8 | ## Number.ToLetters
9 | Converts a number (starting at 1) to an alphabet representation. Works like column headers in Excel.
10 |
11 | ## Number.Reverse8BitInt
12 | Reverses the bits in an 8bit representation of a number
13 |
14 | ## Number.Reverse32BitInt
15 | Reverses the bits in a 32bit representation of a number
16 |
17 | ## Number.DecToBin
18 | Base 10 to binary conversion
19 |
20 | ## Number.HexToDec
21 | Changing hexadecimal representation of a number to decimal
22 |
--------------------------------------------------------------------------------
/src/Splitter/README.md:
--------------------------------------------------------------------------------
1 | # Splitter Module
2 |
3 | ## Splitter.SplitTextByNonAlpha
4 | Splits text by characters that aren't [A-Za-z]
5 |
6 | ## Splitter.SplitTextByNotIn
7 | Splits text on any characters that aren't the provided 'safe' characters
8 |
--------------------------------------------------------------------------------
/src/Splitter/Splitter.mproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Debug
4 | 2.0
5 |
6 |
7 | Exe
8 | MyRootNamespace
9 | MyAssemblyName
10 | False
11 | False
12 | False
13 | False
14 | False
15 | False
16 | False
17 | False
18 | False
19 | False
20 | 1000
21 | Yes
22 | Splitter
23 |
24 |
25 | false
26 |
27 | bin\Debug\
28 |
29 |
30 | false
31 | ..\..\build\
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Code
42 |
43 |
44 |
45 |
46 | Content
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/Splitter/Splitter.pq:
--------------------------------------------------------------------------------
1 | section Splitter;
2 | /////////////////////////
3 | // Splitters //
4 | /////////////////////////
5 | shared Splitter.SplitTextByNonAlpha = Document(
6 | "Splitter.SplitTextByNonAlpha",
7 | "Splits text by characters that aren't [A-Za-z]",
8 | {[ Description = "Split text", Code="PBI[Splitter.SplitTextByNonAlpha](""A1B,C"")", Result = "{ ""A"", ""B"", ""C"" }"]},
9 | (line as text) => Splitter.SplitTextByNotIn(Text.Alphabet)
10 | );
11 |
12 | shared Splitter.SplitTextByNotIn = Document(
13 | "Splitter.SplitTextByNotIn",
14 | "Splits text on any characters that aren't the provided 'safe' characters",
15 | {[ Description = "Split on non-alphanumeric", Code = "PBI[Splitter.SplitTextByNotIn](PBI[Text.Alphanumeric])(""Power BI is #1"")", Result = "{""Power BI is "", ""1""}"]},
16 | (safeCharacters as text) => (line as nullable text) =>
17 | if line is null then
18 | {}
19 | else
20 | List.Accumulate(Text.ToList(line), {null} , (state, current) =>
21 | let
22 | doSkip = not Text.Contains(safeCharacters, current),
23 | lastItem = List.Last(state),
24 | appendLast = lastItem<>null
25 | in
26 | if doSkip then
27 | if lastItem is null then
28 | state
29 | else
30 | List.Combine({state, {null}})
31 | else if appendLast then
32 | List.Combine({List.RemoveLastN(state, 1), {lastItem & current}})
33 | else
34 | List.Combine({List.RemoveLastN(state, 1), {current}}))
35 | );
36 |
37 | /////////////////////////
38 | // Dependencies //
39 | /////////////////////////
40 | Document = (name as text, description as text, valueOrExample as any, optional valueIfExample as any) =>
41 | let
42 | value = if valueIfExample is null then valueOrExample else valueIfExample,
43 | examples = if valueIfExample is null then {} else valueOrExample
44 | in
45 | Value.ReplaceType(value, Value.Type(value) meta [
46 | Documentation.Name = name,
47 | Documentation.Description = description,
48 | // [Description = "", Code="", Result =""]
49 | Documentation.Examples = examples
50 | ]);
51 | Text.Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
--------------------------------------------------------------------------------
/src/Table/README.md:
--------------------------------------------------------------------------------
1 | # Table Module
2 |
3 | ## Table.AddBlankRow
4 | Transforms a column's value into its nested value -- if it eventually finds only one
5 |
6 | ## Table.DrillIntoColumn
7 |
8 | ## Table.EnterDataFormula
9 | Convert a table into a formula that's compatible with the Enter Data UI. This function returns an expression you can copy and paste to edit the table using Enter Data. Paste the formula, then click the gear icon next to the step name. This is an easy way to allow people to customize a table you've queried
10 |
11 | ## Table.FromListCrossJoin
12 | Perform a cross join of lists
13 |
14 | ## Table.JsonDecode
15 |
16 | ## Table.JsonEncode
17 |
18 | ## Table.ReplaceValueIf
19 | Replaces a value if it matches a predicate
20 |
21 | ## Table.SplitColumnNames
22 | Splits camelCased and PascalCased column names.
23 |
24 | ## Table.SplitColumnText
25 | Splits camelCased and PascalCased text in a column.
26 |
27 | ## Table.TransformColumn
28 |
29 | ## Table.RenameColumn
30 |
31 | ## Table.RenameAndTransformColumn
32 |
33 | ## Table.ToXml
34 | Transforms a table into an XML document
--------------------------------------------------------------------------------
/src/Table/Table.mproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Debug
4 | 2.0
5 |
6 |
7 | Exe
8 | MyRootNamespace
9 | MyAssemblyName
10 | False
11 | False
12 | False
13 | False
14 | False
15 | False
16 | False
17 | False
18 | False
19 | False
20 | 1000
21 | Yes
22 | Table
23 |
24 |
25 | false
26 |
27 | bin\Debug\
28 |
29 |
30 | false
31 | ..\..\build\
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Code
42 |
43 |
44 |
45 |
46 | Content
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/Table/Table.pq:
--------------------------------------------------------------------------------
1 | section Table;
2 | /////////////////////////
3 | // Table //
4 | /////////////////////////
5 |
6 | shared Table.AddBlankRow = (table as table) => Table.Combine({table, Table.FromRecords({[]})});
7 | // Transforms a column's value into its nested value -- if it eventually finds only one. Consider the following column:
8 | // MyCol
9 | // -----
10 | // "a"
11 | // {{{"b"}}}
12 | // Table.FromRecords({[MyCol=Table.FromRecords({[col=2]})})
13 | // {}
14 | //
15 | // Table.DrillColumn(table, "MyCol") will convert it to
16 | //
17 | // MyCol
18 | // -----
19 | // "a"
20 | // "b"
21 | // 2
22 | // null
23 | shared Table.DrillIntoColumn = (table as table, columnName as text) =>
24 | let
25 | FindValue = (value as any) =>
26 | if value is list then
27 | if List.Count(value) = 1 then @FindValue(List.First(value))
28 | else if List.Count(value) = 0 then null
29 | else error "Couldn't find single value"
30 | else if value is table then
31 | if Table.RowCount(value) = 1 then @FindValue(List.First(Table.ToColumns(value)))
32 | else if Table.RowCount(value) = 0 then null
33 | else error "Couldn't find single value"
34 | else value,
35 | Result = Table.TransformColumns(table, {{columnName, FindValue}})
36 | in
37 | Result;
38 |
39 | shared Table.EnterDataFormula =
40 | Document(
41 | "Table.EnterDataFormula",
42 | "Convert a table into a formula that's compatible with the Enter Data UI. This function returns an expression you can copy and paste to editthe " &
43 | " table using Enter Data. Paste the formula, then click the gear icon next to the step name. This is an easy way to allow people to customize a table you've queried",
44 | {[
45 | Description = "Simple table",
46 | Code = "Web.Page(Web.Contents(""https://en.wikipedia.org/wiki/List_of_Super_Bowl_champions"")){3}[Data]",
47 | Result = "Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText(""jVVtb9s2EP4rRD4aRqI3S9TH2Gm7ZWkb1MGGom6Aq83IhBkyoOik2a8fT6Ski6Fu+yDdWea9Pffc8du3M342P7uVzrU/jrbZs7UTQgnb+q+lfzL/nFeLxIu0ror55pglWcK8viA6J3o96vVwJksSqvNRT5Ognn2fh1yuQClo2cq8/DCvmMbCPzmmUWaLkEYyn2GkNIhFEFUQvBN1FkQexGI2uP8kXtg73SjQO3YLzkrjMEgRn/NFEmrlpL66JLmnRM+JXhC9IvWR82lxWqvQz8KypTV6a9pYJxZ5nlex1mpMg5dEp98p5OR7TWEmqaYLkgY2eQ2avbegt7LdGlbUof1dApgLz/MASQCcF0EEpH3wDuJiNkfXWUAabT9YITRbwqvHeXsIPovBZ4S5jDWl3kdZjbqHfBbKmXUkGd1iA78ae2AfJGg36ZRH46QzTqKIrtLR1Wc4IA82FzemZZe68cRv2ReQu5Bs3vO/7JOt5kixEt886d45Vp1kg8u/oN1L3Tij2Rexaw9ST3qqAj95FIGmPFC4Jhl+lPAo2ZVRT/vgKeuHoeg9pWT2MqLnhCDkO6ccROSWoJx8NFZsLn7XO48pPBklcQCV6yNmdDJKPrbJj+LIejol9UmUj1Jr0RoH7E/pQWnQc9LPXNJ7ronngEkVyFaVszHh48MDKMOWUqlJN3VCZoDAUxMY6pwkiIASCmwu1u6c3ZijRDY8tpFeHQh5nIUq0r4O3Iotw//WApxTgnm5h5fDhHW3DGdhKDtRBOuMdsNHfhZDy7vCYoEdo8dhzkghePQP0C3uT+le2WovxUOfQfqmieX8Hkeuvh8M/eEtNB5XAXbShoesk3JM93YvFewE0hPYO2g8ejHbjHaExxEkha6kXzdag5M+oG5ATRuS9nF+UukKrGeqBr9ftNuHmT3xgDs6LKZ4BaR0g1wL1xdKAUaK3w+n7+DxCbo1tjxut6CFsFNGuAXehvhslfC9YGuQejIOjslogkv4SgrfgNUebDOWk77hNh1f/OfS+RXmx+o9qK3R0zZ8jHIncA5bIdiddPCL8/VpDCv/Nh5nD/jO460mrPBSD1Hw10qJZ9Fdsf5qexmi4IOuUxzXT5uLy8HgSjhrpGM30vyP09f+PmmNfvYLQLBraI5g/9voN3NscS3fiZ/wbzG+/wM="", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type text) meta [Serialized.Text = true]) in type table [Appearances = _t, Team = _t, Wins = _t, Losses = _t, Winning
48 | percentage = _t, Season(s) = _t])"
49 | ]},
50 | (table as table) =>
51 | let
52 | Encoded = Table.JsonEncode(table),
53 | ColumnMeta= "[" & Text.Range(List.Accumulate(Table.ColumnNames(table), "", (state, current) => state & ", " & current & " = _t"), 2) & "]",
54 | Text =
55 | "Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText(""" & Encoded & """, BinaryEncoding.Base64), Compression.Deflate))," &
56 | " let _t = ((type text) meta [Serialized.Text = true]) in type table " & ColumnMeta & ")"
57 | in
58 | Text
59 | );
60 | // if fieldNames aren't specified, use the field names from the first row of the column.
61 |
62 | // Perform a cross join of lists. Example usage:
63 | // Table.FromListCrossJoin({ {ColorsTable[ColorName], "Color"}, {NumbersTable[Number], "Number"}})
64 | // Will give me a new table with two columns, "Color" and "Number" which contains one row for each possible
65 | // combination of colors and numbers
66 | // Table.FromListCrossJoin({{"Red", "Blue"}, "Color"}, {{1,2,3}, "Number"}}) =
67 | // Table.FromRecords({[Color="Red", Number=1],[Color="Red", Number = 2],[Color="Red", Number = 3],[Color="Blue", Number=1],[Color="Blue", Number=2],[Color="Blue", Number=3]})
68 | shared Table.FromListCrossJoin = (listColumnNamePairs as any) =>
69 | let
70 | remainingPairs = List.Skip(listColumnNamePairs, 1),
71 | current = List.First(listColumnNamePairs),
72 | theList = List.First(current),
73 | columnName = List.First(List.Skip(current),1),
74 | firstTable = Table.FromList(theList, null, {columnName}),
75 | doStuff = (table as table, remainingPairs as list) =>
76 | if List.Count(remainingPairs) <= 0 then table else
77 | let
78 | current = List.First(remainingPairs),
79 | theList = List.First(current),
80 | columnName = List.First(List.Skip(current), 1),
81 | nextTable = Table.ExpandListColumn(Table.AddColumn(table, columnName, each theList), columnName)
82 | in @doStuff(nextTable, List.Skip(remainingPairs, 1)),
83 | Result = doStuff(firstTable, remainingPairs)
84 | in
85 | Result;
86 | shared Table.JsonDecode = (encoded as text) =>
87 | let
88 | Decompressed = Binary.Decompress(Binary.FromText(encoded, BinaryEncoding.Base64), Compression.Deflate),
89 | Decoded = Table.FromRows(Json.Document(Decompressed), let _t = ((type text) meta [Serialized.Text = true]) in type table [Name = _t, Number = _t])
90 | in
91 | Decoded;
92 | shared Table.JsonEncode = (table as table) =>
93 | let
94 | Rows = Table.ToRows(table),
95 | Json = Json.FromValue(Rows),
96 | Compressed = Binary.Compress(Json, Compression.Deflate),
97 | Encoded = Binary.ToText(Compressed, BinaryEncoding.Base64)
98 | in
99 | Encoded;
100 | // Replaces a value if it matches a predicate
101 | shared Table.ReplaceValueIf = (table as table, replaceIf as function, after as any, columnNameOrList as any) =>
102 | Table.ReplaceValue(table, null,after, (text, old, new)=>if replaceIf(text) then new else text, if columnNameOrList is list then columnNameOrList else {columnNameOrList});
103 | // Splits camelCased and PascalCased column names.
104 | shared Table.SplitColumnNames = (table as table) => Table.RenameColumns(table, List.Transform(Table.ColumnNames(table), each {_, Text.SplitCamelCase(_)}));
105 | // Splits camelCased and PascalCased text in a column.
106 | shared Table.SplitColumnText = (table as table, columns as list) => if List.Count(columns) = 0 then table else Table.TransformColumns(@Table.SplitColumnText(table, List.Skip(columns, 1)), {{List.First(columns), Text.SplitCamelCase}});
107 | shared Table.TransformColumn = (table as table, column as text, transform as function) => Table.TransformColumns(table, {{column, transform}});
108 | shared Table.RenameColumn = (table as table, column as text, newName as text) => Table.RenameColumns(table, {{column, newName}});
109 | shared Table.RenameAndTransformColumn = (table, currentName as text, newName as text, transform as function) => Table.TransformColumn(Table.RenameColumns(table, {currentName, newName}), newName, transform);
110 |
111 | shared Table.ToXml = (t) =>
112 | "
"&
113 | {List.Accumulate(
114 | Table.ToRecords(t),
115 | "",
116 | (state,curr)=>state&
117 | ""&
118 | {List.Accumulate(
119 | Record.FieldNames(curr),
120 | "",
121 | (s,c)=>s&Text.Format("<#{0}>#{1}#{0}>",
122 | {c,
123 | Record.Field(curr,c)})
124 | )}
125 | &"
"
126 | )}
127 | &"
";
128 | /////////////////////////
129 | // Dependencies //
130 | /////////////////////////
131 |
132 | Document = (name as text, description as text, valueOrExample as any, optional valueIfExample as any) =>
133 | let
134 | value = if valueIfExample is null then valueOrExample else valueIfExample,
135 | examples = if valueIfExample is null then {} else valueOrExample
136 | in
137 | Value.ReplaceType(value, Value.Type(value) meta [
138 | Documentation.Name = name,
139 | Documentation.Description = description,
140 | // [Description = "", Code="", Result =""]
141 | Documentation.Examples = examples
142 | ]);
143 | Text.Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
144 | Text.IsUpperCase = (text as text) => List.AllTrue(List.Transform(Text.ToList(text), (letter)=>Text.Contains(Text.Alphabet, letter) and letter = Text.Upper(letter)));
145 | Text.SplitCamelCase = (text as nullable text) => if text is null then null else List.Accumulate(Text.ToList(text),"", (state, current) =>
146 | let
147 | PreviousLetter = Text.End(state, 1),
148 | Ignore = (text as text) => text = " " or text = "."
149 | in
150 | state &
151 | (if
152 | not Text.IsUpperCase(PreviousLetter) and
153 | not Ignore(PreviousLetter) and
154 | not Ignore(current) and
155 | Text.IsUpperCase(current)
156 | then
157 | " " else "" ) &
158 | current);
--------------------------------------------------------------------------------
/src/Text/README.md:
--------------------------------------------------------------------------------
1 | # Text Module
2 |
3 | ## Text.Alphabet
4 | The alphabet
5 |
6 | ## Text.AlphaNumeric
7 | The alphabet and digits
8 |
9 | ## Text.IsUpperCase
10 | Is text all uppercase? returns false if any non-alpha characters are present
11 |
12 | ## Text.IsAlpha
13 |
14 | ## Text.RemoveExtraWhitespace
15 |
16 | ## Text.SplitCamelCase
17 | Splits camelCased and PascalCased text and separates by a space. Ex: "thisIsAColumn" -> "this Is A Column"
18 |
19 | ## Text.SplitOnNotIn
20 |
21 | ## Text.SplitOnNonAlpha
22 |
23 | ## Text.Substring
24 |
25 | ## Text.IsNumber
26 |
27 | ## Text.PositionAfter
28 |
29 | ## Text.Until
30 |
31 | ## Text.AsciiOnly
32 | Filters out all non-ascii characters from a string
33 |
34 | ## Text.Between
35 | Grabs the substring between the specified 'after' and 'before' strings
36 |
37 | ## Text.ContainsAny
38 | Check if a string contains any of the keywords from a given list
39 |
40 | ## Text.Count
41 | Returns the number of occurrences of a substring (needle) within another string (haystack).
42 |
43 | ## Text.EachBetween
44 | Grabs the substring between the specified 'after' and 'before' strings
45 |
46 | ## Text.EachFromTo
47 | Grabs the substring between the specified 'after' and 'before' strings
48 |
49 | ## Text.FromTo
50 | Grabs the first substring from the specified 'From' up to the 'UpTo' string
51 |
52 | ## Text.Like
53 | Allows doing fuzzy string comparisons akin to SQL's LIKE
54 |
55 | ## Text.PowerTrim
56 | Function removes double presents of specified characters.By default remove double spaces and leading + ending spaces. Like TRIM function in Excel
57 |
58 | ## Text.RemoveSymbols
59 | Remove all uicode symbols from text
60 |
61 | ## Text.ReplaceAll
62 | Do multiple text replacements in one function call, passing the replacements as a list of lists
63 |
64 | ##
--------------------------------------------------------------------------------
/src/Text/Text.mproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Debug
4 | 2.0
5 |
6 |
7 | Exe
8 | MyRootNamespace
9 | MyAssemblyName
10 | False
11 | False
12 | False
13 | False
14 | False
15 | False
16 | False
17 | False
18 | False
19 | False
20 | 1000
21 | Yes
22 | Text
23 |
24 |
25 | false
26 |
27 | bin\Debug\
28 |
29 |
30 | false
31 | ..\..\build\
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Code
42 |
43 |
44 |
45 |
46 | Content
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/Text/Text.pq:
--------------------------------------------------------------------------------
1 | section Text;
2 |
3 | /////////////////////////
4 | // Text //
5 | /////////////////////////
6 | shared Text.Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
7 | shared Text.AlphaNumeric = Text.Alphabet & "0123456789";
8 | shared Text.FromList = (list as list) => List.Accumulate(list, "", (state, current) => state & Text.From(current));
9 | // Is text all uppercase? returns false if any non-alpha characters are present
10 | shared Text.IsUpperCase = (text as text) => List.AllTrue(List.Transform(Text.ToList(text), (letter)=>Text.Contains(Text.Alphabet, letter) and letter = Text.Upper(letter)));
11 | shared Text.IsAlpha = (text as text) => List.MatchesAll(Text.ToList(text), each Text.Contains(Text.Alphabet, _));
12 | shared Text.RemoveExtraWhitespace = (text as text) => Text.Combine(Splitter.SplitTextByWhitespace()(text)," ");
13 | // Splits camelCased and PascalCased text and separates by a space. Ex: "thisIsAColumn" -> "this Is A Column"
14 | shared Text.SplitCamelCase = (text as nullable text) => if text is null then null else List.Accumulate(Text.ToList(text),"", (state, current) =>
15 | let
16 | PreviousLetter = Text.End(state, 1),
17 | Ignore = (text as text) => text = " " or text = "."
18 | in
19 | state &
20 | (if
21 | not Text.IsUpperCase(PreviousLetter) and
22 | not Ignore(PreviousLetter) and
23 | not Ignore(current) and
24 | Text.IsUpperCase(current)
25 | then
26 | " " else "" ) &
27 | current);
28 | shared Text.SplitOnNotIn = (line as nullable text, validCharacters as text) => Splitter.SplitTextByNotIn(validCharacters)(line);
29 | shared Text.SplitOnNonAlpha = (line as nullable text) =>
30 | if line is null then null else List.Accumulate(Text.ToList(line), {null} , (state, current) =>
31 | let
32 | doSkip = not Text.Contains(Text.Alphabet, current),
33 | lastItem = List.Last(state),
34 | appendLast = lastItem<>null
35 | in
36 | if doSkip then
37 | if lastItem is null then
38 | state
39 | else
40 | List.Combine({state, {null}})
41 | else
42 | if appendLast then
43 | List.Combine({List.RemoveLastN(state, 1), {lastItem & current}})
44 | else
45 | List.Combine({List.RemoveLastN(state, 1), {current}}));
46 | shared Text.Substring = (text as text, start as number, optional count as number) =>
47 | let
48 | start = if start >= 0 then start else error "start index should be >= 0",
49 | end = if count = null then Text.Length(text) else if count <= Text.Length(text) then count else error "count should be <= text length",
50 | textList = Text.ToList(text),
51 | substr = Text.FromList(List.FirstN(List.Skip(textList, start), end - start))
52 | in substr;
53 | shared Text.IsNumber = (text as text) => try Number.FromText(text) is number otherwise false;
54 | shared Text.PositionAfter = (text as nullable text, substring as text) =>
55 | let
56 | firstIndex = Text.PositionOf(text, substring),
57 | indexAfter = if firstIndex >=0 then firstIndex + Text.Length(substring) else -1
58 | in
59 | if text is null then -1 else if indexAfter >= 0 and indexAfter < Text.Length(text) then indexAfter else -1;
60 | shared Text.Until = (text as text, endDelimiter as text, optional startIndex as number) =>
61 | let
62 | start = if startIndex = null then 0 else startIndex,
63 | textFromStart = Text.Substring(text, start),
64 | delimPosition = if Text.PositionOf(textFromStart, endDelimiter) >= 0 then Text.PositionOf(textFromStart, endDelimiter) else Text.Length(textFromStart)
65 | in
66 | if text is null then null else Text.Range(textFromStart, 0, delimPosition);
67 | shared Text.AsciiOnly = (s as text) as text =>
68 | let
69 | Listified = Text.ToList(s),
70 | Numbered = List.Transform(Listified, each Character.ToNumber(_)),
71 | Filtered = List.Select(Numbered, each _ <= 255),
72 | Stringified = List.Transform(Filtered, each Character.FromNumber(_)),
73 | Joined = Text.Combine(Stringified, ""),
74 | Return = Joined
75 | in
76 | Return;
77 |
78 | shared Text.Between = (Haystack as text, After as text, Before as text) as text =>
79 | let
80 | CutAfter = Text.Split(Haystack, After),
81 | CutBefore = Text.Split(CutAfter{1}, Before),
82 | Needle = if List.Count(CutAfter) > 1
83 | then (if List.Count(CutBefore) > 1 then CutBefore{0} else Error.Record("FindTextFailed","The text did not contain the keyword " & Before, Haystack))
84 | else error Error.Record("FindTextFailed","The text did not contain the keyword " & After, Haystack)
85 | in Needle;
86 |
87 | shared Text.ContainsAny = (str as text, needles as list) as logical =>
88 | let
89 | count = List.Count(needles)
90 | in
91 |
92 | List.AnyTrue(
93 | List.Generate(
94 | ()=>[i=0],
95 | each [i] < count,
96 | each [i=[i]+1],
97 | each Text.Contains(str,needles{[i]})
98 | )
99 | );
100 |
101 | shared Text.Count = (Haystack as text, Needle as text) as number =>
102 | List.Count(Text.Split(Haystack, Needle)) - 1;
103 |
104 | shared Text.EachBetween = (Haystack as text, After as text, Before as text) as list =>
105 | let
106 | CutAfter = Text.Split(Haystack, After),
107 | SkipFirst = List.Skip(CutAfter),
108 | CutEach = List.Transform(SkipFirst, each Text.Split(_, Before){0})
109 | in
110 | CutEach;
111 |
112 | shared Text.EachFromTo =
113 | (Haystack as text, After as text, Before as text) as text =>
114 | let
115 | CutAfter = Text.Split(Haystack, After),
116 | SkipFirst = List.Skip(CutAfter),
117 | CutEach = List.Transform(SkipFirst, each After & Text.Split(_, Before){0} & Before)
118 | in
119 | CutEach;
120 |
121 | shared Text.FromTo =
122 | (Haystack as text, From as text, UpTo as text) as text =>
123 | let
124 | CutAfter = Text.Split(Haystack, From),
125 | CutBefore = Text.Split(CutAfter{1}, UpTo),
126 | Needle = if List.Count(CutAfter) > 1
127 | then (if List.Count(CutBefore) > 1 then From & CutBefore{0} & UpTo else Error.Record("FindTextFailed","The text did not contain the keyword " & UpTo, Haystack))
128 | else error Error.Record("FindTextFailed","The text did not contain the keyword " & From, Haystack)
129 | in Needle;
130 |
131 | shared Text.Like = (Phrase as text, Pattern as text) as logical =>
132 | //Originally written by Chris Webb: https://cwebbbi.wordpress.com/2014/05/27/implementing-a-basic-likewildcard-search-function-in-power-query/
133 | let
134 | //Split pattern up into a list using % as a delimiter
135 | PatternList = Text.Split(Pattern, "%"),
136 | //if the first character in the pattern is % then the first item in the list is an empty string
137 | StartsWithWc = (List.First(PatternList)=""),
138 | //if the last character in the pattern is % then the last item in the list is an empty string
139 | EndsWithWc = (List.Last(PatternList)=""),
140 | //if the first character is not % then we have to match the first string in the pattern with the opening characters of the phrase
141 | StartsTest = if (StartsWithWc=false)
142 | then Text.StartsWith(Phrase, List.First(PatternList))
143 | else true,
144 | //if the last item is not % then we have to match the final string in the pattern with the final characters of the phrase
145 | EndsText = if (EndsWithWc=false)
146 | then Text.EndsWith(Phrase, List.Last(PatternList))
147 | else true,
148 | //now we also need to check that each string in the pattern appears in the correct order in the phrase and to do this we need to declare a function PhraseFind
149 | PhraseFind = (Phrase as text, SearchString as list) =>
150 | let
151 | //does the first string in the pattern appear in the phrase?
152 | StringPos = Text.PositionOf(Phrase, SearchString{0}, Occurrence.First),
153 | PhraseFindOutput =
154 | if
155 | //if string not find then return false
156 | (StringPos=-1)
157 | then false
158 | else if
159 | //we have found the string in the pattern, and if this is the last string in the pattern, return true
160 | List.Count(SearchString)=1
161 | then true
162 | else
163 | //if it isn't the last string in the pattern test the next string in the pattern by removing the first string from the pattern list and all text up to and including the string we have found in the phrase
164 | (true and
165 | @PhraseFind(
166 | Text.RemoveRange(Phrase, 0, StringPos + Text.Length(SearchString{0})),
167 | List.RemoveRange(SearchString, 0, 1)))
168 | in
169 | PhraseFindOutput,
170 | //return true if we have passed all tests
171 | Output = StartsTest and EndsText and PhraseFind(Phrase, PatternList)
172 | in
173 | Output;
174 |
175 | shared Text.PowerTrim =
176 | //Original is taked from Ken Puls's blog http://www.excelguru.ca/blog/2015/10/08/clean-whitespace-in-powerquery/
177 | (text as text, optional char_to_trim as text) =>
178 | let
179 | char = if char_to_trim = null then " " else char_to_trim,
180 | split = Text.Split(text, char),
181 | removeblanks = List.Select(split, each _ <> ""),
182 | result=Text.Combine(removeblanks, char)
183 | in
184 | result;
185 |
186 | shared Text.RemoveSymbols =
187 | //Originally written by Chris Webb: https://cwebbbi.wordpress.com/2014/08/18/removing-punctuation-from-text-in-power-query/
188 | (inputtext as text) as text =>
189 | let
190 | //get a list of lists containing the numbers of Unicode punctuation characters
191 | numberlists = {{0..31},{33..47},{58..64},{91..96},{123..191}},
192 | //turn this into a single list
193 | combinedlist = List.Combine(numberlists),
194 | //get a list of all the punctuation characters that these numbers represent
195 | punctuationlist = List.Transform(combinedlist, each Character.FromNumber(_)),
196 | //some text to test this on
197 | //inputtext = "Hello! My name is Chris, and I'm hoping that this *cool* post will help you!",
198 | //the text with punctuation removed
199 | outputtext = Text.Remove(inputtext, punctuationlist)
200 | in
201 | outputtext;
202 |
203 | shared Text.ReplaceAll =
204 | (str as text, Replacements as list) as text =>
205 | let
206 | count = List.Count(Replacements)
207 | in
208 | List.Last(
209 | List.Generate(
210 | ()=>[i=0, s=str],
211 | each [i] <= count,
212 | each [
213 | s=Text.Replace([s],Replacements{[i]}{0},Replacements{[i]}{1}),
214 | i=[i]+1
215 | ],
216 | each [s]
217 | )
218 | );
219 |
220 |
221 | /////////////////////////
222 | // Dependencies //
223 | /////////////////////////
224 |
225 |
226 | Splitter.SplitTextByNotIn = (safeCharacters as text) => (line as nullable text) =>
227 | if line is null then
228 | {}
229 | else
230 | List.Accumulate(Text.ToList(line), {null} , (state, current) =>
231 | let
232 | doSkip = not Text.Contains(safeCharacters, current),
233 | lastItem = List.Last(state),
234 | appendLast = lastItem<>null
235 | in
236 | if doSkip then
237 | if lastItem is null then
238 | state
239 | else
240 | List.Combine({state, {null}})
241 | else if appendLast then
242 | List.Combine({List.RemoveLastN(state, 1), {lastItem & current}})
243 | else
244 | List.Combine({List.RemoveLastN(state, 1), {current}}));
--------------------------------------------------------------------------------
/src/Value/README.md:
--------------------------------------------------------------------------------
1 | # Value Module
2 |
3 | ## Value.TypeText
4 |
5 | ## Value.ToText
6 | Returns a string representation of a value, which works even on containers, unlike the built-in Text.From()
7 |
8 | ## Value.TypeToText
9 | Returns a simple string representation of a value's type, which allows easy filtering, unlike the built-in Value.Type()
10 |
11 | ## Value.WaitFor
12 | Retry mechanism for function call
--------------------------------------------------------------------------------
/src/Value/Value.mproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Debug
4 | 2.0
5 |
6 |
7 | Exe
8 | MyRootNamespace
9 | MyAssemblyName
10 | False
11 | False
12 | False
13 | False
14 | False
15 | False
16 | False
17 | False
18 | False
19 | False
20 | 1000
21 | Yes
22 | Value
23 |
24 |
25 | false
26 |
27 | bin\Debug\
28 |
29 |
30 | false
31 | ..\..\build\
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Code
42 |
43 |
44 |
45 |
46 | Content
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/Value/Value.pq:
--------------------------------------------------------------------------------
1 | section Value;
2 |
3 | /////////////////////////
4 | // Value //
5 | /////////////////////////
6 | shared Value.TypeText = (value as any) =>
7 | if value is binary then "binary" else
8 | if value is date then "date" else
9 | if value is datetime then "datetime" else
10 | if value is datetimezone then "datetimezone" else
11 | if value is duration then "duration" else
12 | if value is function then "function" else
13 | if value is list then "list" else
14 | if value is logical then "logical" else
15 | if value is none then "none" else
16 | if value is null then "null" else
17 | if value is number then "number" else
18 | if value is record then "record" else
19 | if value is table then "table" else
20 | if value is text then "text" else
21 | if value is time then "time" else
22 | if value is type then "type" else
23 | if value is any then "any"
24 | else error "unknown -- not a primitive type!";
25 |
26 | shared Value.ToText =
27 | (Val as any, optional RecursTypes as logical) as text =>
28 | let
29 | RecursTypes = if (RecursTypes<>null) then RecursTypes else false,
30 | Tried = (try Val),
31 | Value = if Tried[HasError] then Tried[Error] else Tried[Value],
32 | /*
33 | DurationVals = {Duration.Days, Duration.Hours, Duration.Minutes, Duration.Seconds},
34 | DateVals = {Date.Year, Date.Month, Date.Day},
35 | TimeVals = {Time.Hour, Time.Minute, Time.Second},
36 | ZoneVals = {DateTimeZone.ZoneHours, DateTimeZone.ZoneMinutes},
37 | GetNumbers = (vals as list, obj as any) as text => Text.Combine(List.Transform(vals, each Number.ToText(Function.Invoke(_, {obj}))), ","),
38 | */
39 | CaseValues = {
40 | //{ (x)=> (try x)[HasError], "error " & @Value.ToText((try Value)[Error], RecursTypes) },
41 | { (x)=> Value.Is(x, type type), Type.ToText(Value, RecursTypes) },
42 | { (x)=> Value.Is(x, type function),
43 | let
44 | Type = Value.Type(Value),
45 | Params = Type.FunctionParameters(Type),
46 | Reqd = Type.FunctionRequiredParameters(Type),
47 | Ret = Type.FunctionReturn(Type)
48 | in
49 | "function (" &
50 | Record.TransformJoin(Params, (k,v) =>
51 | (if List.PositionOf(Record.FieldNames(Params), k) >= Reqd then "optional " else "") &
52 | k & " as " & @Value.ToText(v, RecursTypes)
53 | )
54 | & ") as " & @Value.ToText(Ret, RecursTypes)
55 | },
56 | { (x)=> Value.Is(x, type table), "#table(" & @Value.ToText(Table.ColumnNames(Value), RecursTypes) & ", " & @Value.ToText(Table.ToRows(Value), RecursTypes) & ")"},
57 | { (x)=> Value.Is(x, type record), "[" &
58 | Record.TransformJoin(Value, (k,v) => k & "=" & @Value.ToText(v, RecursTypes))
59 | & "]" },
60 | { (x)=> Value.Is(x, type list), "{" & Text.Combine(List.Transform(Value, each @Value.ToText(_, RecursTypes)), ", ") & "}" },
61 | { (x)=> x = null, "null" },
62 | /*
63 | { (x)=> Value.Is(x, type text), """" & Value & """" },
64 | { (x)=> Value.Is(x, type binary), "#binary(""" & Binary.ToText(Value) & """)" },
65 | { (x)=> Value.Is(x, type date), "#date(" & GetNumbers(DateVals, Value) & ")" }, //alt: Date.ToText(Value)
66 | { (x)=> Value.Is(x, type time), "#time(" & GetNumbers(TimeVals, Value) & ")" }, //alt: Time.ToText(Value)
67 | { (x)=> Value.Is(x, type datetime),
68 | let
69 | Date = DateTime.Date(Value),
70 | Time = DateTime.Time(Value)
71 | in
72 | "#datetime(" & GetNumbers(DateVals, Date) & ", " & GetNumbers(TimeVals, Time) & ")"
73 | }, //alt: DateTime.ToText(Value)
74 | { (x)=> Value.Is(x, type datetimezone),
75 | let
76 | DateTime = DateTimeZone.RemoveZone(Value),
77 | Date = DateTime.Date(DateTime),
78 | Time = DateTime.Time(DateTime)
79 | in
80 | "#datetimezone(" & GetNumbers(DateVals, Date) & ", " & GetNumbers(TimeVals, Time) & ", " & GetNumbers(ZoneVals, Value) & ")"
81 | }, //alt: DateTimeZone.ToText(Value)
82 | { (x)=> Value.Is(x, type duration), "#duration(" & GetNumbers(DurationVals, Value) & ")" }, //alt: Duration.ToText(Value)
83 | // { (x)=> Value.Is(x, type logical), Logical.ToText(Value) },
84 | // { (x)=> Value.Is(x, type number), Number.ToText(Value) },
85 | { (x)=> true, Text.From(Value) }
86 | */
87 | { (x)=> true, Expression.Constant(Value) }
88 | },
89 | Return = List.First(List.Select(CaseValues, each _{0}(Value))){1}
90 | in Return;
91 |
92 | shared Value.TypeToText =
93 | (Value as any, optional Recurs as logical) as text =>
94 | let
95 | Recurs = if (Recurs<>null) then Recurs else false,
96 | Type = Value.Type(Value),
97 | ToText = if Value.Is(Value, type type) and Recurs then
98 | "type " & Type.ToText(Value, Recurs)
99 | else
100 | Type.ToText(Type, Recurs),
101 | Return = ToText
102 | in Return;
103 |
104 | shared Value.WaitFor =
105 | //author: Curt Hagenlocher https://gist.github.com/CurtHagenlocher/68ac18caa0a17667c805
106 | (producer as function, interval as function, optional count as number) as any =>
107 | let
108 | list = List.Generate(
109 | //start: first try, no result
110 | () => {0, null},
111 | //condition: stop if we have the result (try count null'd) or we've exceeded the max tries
112 | (state) => state{0} <> null and (count = null or state{0} < count),
113 | //next: stop try tally if we have our result, otherwise check again and tally a try
114 | (state) => if state{1} <> null
115 | then {null, state{1}}
116 | else {1 + state{0}, Function.InvokeAfter(() => producer(state{0}), interval(state{0}))},
117 | //transformer: only return the result, not try tally
118 | (state) => state{1})
119 | in
120 | List.Last(list);
121 | /////////////////////////
122 | // Dependencies //
123 | /////////////////////////
124 |
125 | Type.ToText =
126 | (Type as any, optional Recurs as logical) as text =>
127 | let
128 | Recurs = if (Recurs<>null) then Recurs else false,
129 |
130 | CaseValues = {
131 | { (x)=> (try x)[HasError], "error" },
132 | { (x)=> Type.Is(x, type type), "type"}, //if Recurs then else
133 | { (x)=> Type.Is(x, type function), "function"},
134 | { (x)=> Type.Is(x, type table), if Recurs then "table " & @Type.ToText(Type.TableRow(NonNull), Recurs) else "table"},
135 | { (x)=> Type.Is(x, type record), if Recurs then
136 | let
137 | Record = Type.RecordFields(NonNull)
138 | in "[" & Record.TransformJoin(Record, (k,v) =>
139 | (if v[Optional] then "optional " else "") & Expression.Identifier(k) & " = " & @Type.ToText(v[Type], Recurs)
140 | ) & "]"
141 | else "record"},
142 | { (x)=> Type.Is(x, type list), if Recurs then "{" & @Type.ToText(Type.ListItem(NonNull), Recurs) & "}" else "list"},
143 | { (x)=> Type.Is(x, type binary), "binary"},
144 | { (x)=> Type.Is(x, type logical), "logical"},
145 | { (x)=> Type.Is(x, type number), "number"},
146 | { (x)=> Type.Is(x, type text), "text"},
147 | { (x)=> Type.Is(x, type date), "date"},
148 | { (x)=> Type.Is(x, type time), "time"},
149 | { (x)=> Type.Is(x, type datetime), "datetime"},
150 | { (x)=> Type.Is(x, type datetimezone), "datetimezone"},
151 | { (x)=> Type.Is(x, type duration), "duration"},
152 | { (x)=> Type.Is(type anynonnull, x), "anynonnull"},
153 | { (x)=> Type.Is(type null, x), "null"},
154 | { (x)=> Type.Is(None.Type, x), "none"},
155 | // { (x)=> Type.Is(type any, x), "any"},
156 | { (x)=> true, "?"}
157 | },
158 | NonNull = Type.NonNullable(Type),
159 | Return = if Type.Is(type any, Type) then "any"
160 | else (if Type.IsNullable(Type) then "nullable " else "")
161 | & List.First(List.Select(CaseValues, each _{0}(NonNull))){1}
162 | in Return;
163 |
164 | Record.TransformJoin =
165 | (Rec as record, Lambda as function, optional Delimiter as text) as text =>
166 | let
167 | Delimiter = if (Delimiter<>null) then Delimiter else ", ",
168 |
169 | Keys = Record.FieldNames(Rec),
170 | Transformed = List.Transform(Keys, each Lambda(_, Record.Field(Rec,_))),
171 | Combined = Text.Combine(Transformed, Delimiter),
172 |
173 | Return = Combined
174 | in
175 | Return;
--------------------------------------------------------------------------------